Inheriting Constructors and Brace-Or-Equal Initializers

Inheriting constructors and brace-or-equal initializers

This is a gcc bug, #67054. Comparing the bug report by alltaken380 to the OP's case:

// gcc bug report                        // OP
struct NonDefault struct NoDefCTor
{ {
NonDefault(int) {} NoDefCTor(int) {}
}; };

struct Base struct Base
{ {
Base(int) {} Base(float) {}
}; };

struct Derived : public Base struct Derived : Base
{ {
NonDefault foo = 4; NoDefCTor n2{ 4 };

using Base::Base; using Base::Base;
}; };

auto test() int main()
{ {
auto d = Derived{ 5 }; Derived d(1.2f);
} }

We can even try this on recent gcc 6.0 versions, and it still fails to compile. clang++3.6 and, according to the OP, MSVC14 accept this program.

Constructor inheritance and direct member initialisation

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;

Inheriting constructors with initializer_list from multiple base classes deletes constructor

As per cppref:,

If overload resolution selects one of the inherited constructors when initializing an object of such derived class, then the Base subobject from which the constructor was inherited is initialized using the inherited constructor, and all other bases and members of Derived are initialized as if by the defaulted default constructor (default member initializers are used if provided, otherwise default initialization takes place).

Other base classes are required to be default constructed (and only one default constructor otherwise resolution will be ambiguous).

In your case, how does the compiler know how to correctly initialize base_B since you don't provide a default constructor for it?

A simple fix is to make a default constructor for base_B.

struct base_B
{
base_B(std::initializer_list<keyval> = {}) {}
};

or

struct base_B
{
base_B(std::initializer_list<keyval>) {}
base_B() {}
};

Demo

Unexpected behaviour when inheriting constructors with using keyword

Per CppReference:

If the using-declaration refers to a constructor of a direct base of the class being defined (e.g. using Base::Base;), constructors of that base class are inherited, according to the following rules:

1) A set of candidate inheriting constructors is composed of

[rules here]

2) All candidate inherited constructors that aren't the default constructor or the copy/move constructor and whose signatures do not match user-defined constructors in the derived class, are implicitly declared in the derived class. The default parameters are not inherited

So you cannot inherit a default constructor. When a class has declared a user-defined constructor, the compiler does not generate a default constructor for that class, unless you explicitly ask for one using Derived() = default. When you remove your user-defined constructor, the compiler can then generate a default constructor for you.

brace-or-equal-initializer in mem-initializer

Like others have said, it's simple not an allowed syntax in ctor initialization.

Note that, for non-primitive classes, the ctor() : xyz(...) form of initialization is actually a ctor call on the type of the member xyz. While the ctor(): xyz{...} is for aggregate initialization of either an array/list or for member initialization of structs/unions that have no ctors.

A ctor() : xyz = something would seem to have to do two things: 1) a default ctor call and 2) then call the operator= for the type of member xyz all of which would be double initialization. That is probably why it is disallowed. Of course, for non-primitive cases, I guess it could be reduced to a copy-ctor call to eliminate the double initialization.

Now, you could maybe convince some that allowing it for primitive types only would be ok, but that would likely introduce other complications into the language and compilers.

Brace-or-equal-initializers in anonymous struct does not work on VS2013

Non-static data member initialisers being silently ignored in nested anonymous structs is a confirmed bug in Visual C++ 2013, fixed in Visual C++ 2015 RTM.

Inheriting constructors w / wo their default arguments?

From [class.inhctor]:

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 and
ellipsis parameter specifications as follows:

— [...]

— for each non-template constructor of X that has at least one parameter with a default argument, the set
of constructors that results from omitting any ellipsis parameter specification and successively omitting
parameters with a default argument
from the end of the parameter-type-list, and

— [...]

We have two non-template constructors of Base. The default constructor of Base introduces the candidate:

Derived() : Base() { }

And the other constructor of Base introduces one candidate for each successively omitted parameter. Namely:

Derived(int x, int y, int z) : Base(x, y, z) { }
Derived(int x, int y) : Base(x, y) { }
Derived(int x) : Base(x) { }

The default arguments are not inherited - we just get a different constructor for each number of arguments. So Derived(5) simply calls Base(5), not Base(5, 88, 99).

The end result is the same - just how we get there is a bit different.



Related Topics



Leave a reply



Submit