When I run this code:
struct X { int a; }; struct Y : public X {}; X x = {0}; Y Y = {0};
I get:
error: could not convert ‘{0}’ from ‘<brace-enclosed initializer list>’ to ‘Y’
Why does brace initialization work for the base class but not the derived class?
Using designated initializers, a C99 feature which allows you to name members to be initialized, structure members can be initialized in any order, and any (single) member of a union can be initialized. Designated initializers are described in detail in Designated initializers for aggregate types (C only).
If a class has non-default constructors, the order in which class members appear in the brace initializer is the order in which the corresponding parameters appear in the constructor, not the order in which the members are declared (as with class_a in the previous example).
When initializing a struct, the first initializer in the list initializes the first declared member (unless a designator is specified) (since C99), and all subsequent initializers without designators (since C99)initialize the struct members declared after the one initialized by the previous expression.
When we define a struct (or class) type, we can provide a default initialization value for each member as part of the type definition. This process is called non-static member initialization, and the initialization value is called a default member initializer.
Answer for C++ standard versions before C++17:
Your problem has to do with aggregate initialization: struct X
is an aggregate while struct Y
is not. Here is the standard quote about aggregates (8.5.1):
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equal-initializers for non-static data members (9.2), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).
This clause specifies that if a class
has a base class, then it's not an aggregate. Here, struct Y
has struct X
as a base class and thus cannot be an aggregate type.
Concerning the particular problem you have, take the following clause from the standard:
When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause. If the initializer-clause is an expression and a narrowing conversion (8.5.4) is required to convert the expression, the program is ill-formed.
When you do X x = {0}
, aggregate initialization is used to initialize a
to 0
. However, when you do Y y = {0}
, since struct Y
is not an aggregate type, the compiler will look for an appropriate constructor. Since none of the implicitely generated constructors (default, copy and move) can do anything with a single integer, the compiler rejects your code.
Concerning this constructors lookup, the error messages from clang++ are a little bit more explicit about what the compiler is actually trying to do (online example):
Y Y = {0}; ^ ~~~ main.cpp:5:8: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'int' to 'const Y &' for 1st argument struct Y : public X {}; ^ main.cpp:5:8: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'int' to 'Y &&' for 1st argument struct Y : public X {}; ^ main.cpp:5:8: note: candidate constructor (the implicit default constructor) not viable: requires 0 arguments, but 1 was provided
Note that there is a proposal to extend aggregate initialization to support your use case, and it made it into C++17. If I read it correctly, it makes your example valid with the semantics you expect. So... you only have to wait for a C++17-compliant compiler.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With