Why can I not brace initialize a struct derived from another struct?

C++C++11StructList Initialization

C++ Problem Overview


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?

C++ Solutions


Solution 1 - C++

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 - C++

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.

Solution 3 - C++

This question is still in top google results, so I will say here. It is possible in C++17. only no-constructors, no-virtual, no-private/protected, but for derived PODS it is now possible.

Note: this year (2021) eclipse for STM32 (CubeIDE) still does not provide menu choice for c++17 but one can set it manually, e.g. -std=gnu++17

Solution 4 - C++

C++14 and lower should use a default initializer in the constructor.

struct Base
{
  int member1{-54}; 
  std::string member2{"Base"};  
};  


struct OtherType : Base
{
  OtherType = default;
  OtherType(int i,std::string s, int j) : Base{i,s}, member3{j} {}
        
  int member3{5};
};

int main()
{

  OtherType ot{ 45, "other", 13} ;

  OtherType ot = { 13, "heynow", -1};

  OtherType ot;

  std::cout << "{" << ot.member1
	    << ", " << ot.member2 
	    << ", " << ot.member3 << "}\n";

  return 0;
}

C++17 -20 works best as:

struct Base
    {
      int member1{-54}; 
      std::string member2{"Base"};  
    };

struct OtherType : Base
    {       
      int member3{5};
    };

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEricView Question on Stackoverflow
Solution 1 - C++MorwennView Answer on Stackoverflow
Solution 2 - C++einpoklumView Answer on Stackoverflow
Solution 3 - C++Alex OmelyanchukView Answer on Stackoverflow
Solution 4 - C++Chris ReidView Answer on Stackoverflow