Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constructor inheritance and direct member initialisation [duplicate]

I am trying to use a combination of the C++ 11 direct data member initialisation and the "using" syntax to inherit the constructors of a base class. Now with gcc 5.4.0 (on Ubuntu 16.04) I observe a strange error, if the data member type has no default constructor. It is probably easiest to understand when looking on the following minimal example:

#include <iostream>

struct Foo {
  Foo(int arg) { std::cout << "Foo::Foo(" << arg << ")" << std::endl; }
};

struct Base {
  Base(int arg) { std::cout << "Base::Base(" << arg << ")" << std::endl; }
};

struct Derived : public Base {
  using Base::Base;
  Foo foo{42};
};

int main() {
  Derived derived{120};
}

This code compiles and executes with the expected behaviour with clang. With gcc it does not compile, because the compiler deletes the constructor Derived::Derived(int):

ttt.cpp: In function ‘int main()’:
ttt.cpp:17:22: error: use of deleted function ‘Derived::Derived(int)’
   Derived derived{120};
                      ^
ttt.cpp:12:15: note: ‘Derived::Derived(int)’ is implicitly deleted because the default definition would be ill-formed:
   using Base::Base;
               ^
ttt.cpp:12:15: error: no matching function for call to ‘Foo::Foo()’
ttt.cpp:4:3: note: candidate: Foo::Foo(int)
   Foo(int arg) { std::cout << "Foo::Foo(" << arg << ")" << std::endl; }
   ^
ttt.cpp:4:3: note:   candidate expects 1 argument, 0 provided
ttt.cpp:3:8: note: candidate: constexpr Foo::Foo(const Foo&)
 struct Foo {
        ^
ttt.cpp:3:8: note:   candidate expects 1 argument, 0 provided
ttt.cpp:3:8: note: candidate: constexpr Foo::Foo(Foo&&)
ttt.cpp:3:8: note:   candidate expects 1 argument, 0 provided

If I add a default constructor to Foo like this:

  Foo() { std::cout << "Foo::Foo()" << std::endl; };

also gcc can compile it. The code behaves exactly in the same way, in particular the added default constructor of Foo never gets executed.

So my question is now, is this valid C++ 11? If yes, I probably have found a bug in gcc. Otherwise, shouldn't both gcc and clang give me an error message that this is not valid C++ 11?

Edit after the question was nicely answered by @vlad-from-moscow: This bug seems to be present also in gcc 6.2, so I will file a bug report.

2nd edit: There already is a bug, which I didn't find in the first search: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054

like image 567
Martin Hierholzer Avatar asked Dec 02 '16 13:12

Martin Hierholzer


2 Answers

The gcc does not satisfy the C++ Standard. The inherited constructor of the class Derived should call the Base constructor in its mem-initializer list with the argument specified for the Derived inherited constructor.

There is written in the C++ Standard (12.9 Inheriting constructor)

8 An inheriting constructor for a class is implicitly defined when it is odr-used (3.2) to create an object of its class type (1.8). An implicitly-defined inheriting constructor performs the set of initializations of the class that would be performed by a user-written inline constructor for that class with a mem-initializer-list whose only mem-initializer has a mem-initializer-id that names the base class denoted in the nested-name-specifier of the using-declaration and an expression-list as specified below, and where the compound-statement in its function body is empty (12.6.2). If that user-written constructor would be ill-formed, the program is ill-formed. Each expression in the expression-list is of the form static_cast(p), where p is the name of the corresponding constructor parameter and T is the declared type of p.

Also according to the section (12.6.2 Initializing bases and members)

8 In a non-delegating constructor, if a given non-static data member or base class is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has noctor-initializer) and the entity is not a virtual base class of an abstract class (10.4), then

— if the entity is a non-static data member that has a brace-or-equal-initializer, the entity is initialized as specified in 8.5;

like image 93
Vlad from Moscow Avatar answered Oct 13 '22 10:10

Vlad from Moscow


It looks like you're right, there's a bug in gcc

From §12.9 [class.inhctor]:

A using-declaration (7.3.3) that names a constructor implicitly declares a set of inheriting constructors. The candidate set of inherited constructors from the class X named in the using-declaration consists of actual constructors and notional constructors that result from the transformation of defaulted parameters as follows:

  • all non-template constructors of X

So this means that your Derived class should definnitely get a constructor from its base that accepts an int. Following the normal rules of in-class member initialization, constructing an instance of Derived shouldn't be a problem without a default constructor for Foo because it's not being used. Hence, there's a bug in gcc:

§13.3.1.3 Initialization by constructor [over.match.ctor]

When objects of class type are direct-initialized (8.5) [...], overload resolution selects the constructor. For direct-initialization, the candidate functions are all the constructors of the class of the object being initialized.

So the constructor Foo::Foo(int) should have been selected, which it clearly wasn't in gcc.


One question I had after reading this was "Does this cause the default constructor for Derived to be deleted?" The answer is no.

Conveniently, the standard provides an example below this excerpt (I'm excising what's not needed):

struct B1 {
   B1(int);
};

struct D1 : B1 {
   using B1::B1;
};

The set of constructors present in D1 is [Emphasis mine]

  • D1(), implicitly-declared default constructor, ill-formed if odr-used
  • D1(const D1&), implicitly-declared copy constructor, not inherited
  • D1(D1&&), implicitly-declared move constructor, not inherited
  • D1(int), implicitly-declared inheriting constructor
like image 5
AndyG Avatar answered Oct 13 '22 08:10

AndyG