Why can I not brace initialize a struct derived from another struct?
Solution 1:
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.
Solution 2:
With C++17 and later, you can brace-initialize your derived struct.
Your code now compiles (GodBolt).
You do get a warning about using braces. So, the recommended way of initializing a Y
would be:
Y y = { {0} };
rather than;
Y y = { 0 };
This is because such inheriting structures are now considered "aggregate types" (which support this type of aggregate initialization). See this paper from 2015.