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

4

53

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?

Tyrothricin answered 7/6, 2013 at 11:55 Comment(0)
A
56

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.

Adlare answered 7/6, 2013 at 12:16 Comment(3)
clang++ -std=c++1z version 4.0.1 compiles, g++ -std=c++17 version 7.2.1 compiles, but Visual Studio 2017 cl version 19.12.25816 not yetProcambium
Should be now supported since this summer with VS 2017 15.7Demetrius
What is the cleanest alternative for c++14 users?Amazon
A
7

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.

Absa answered 18/9, 2020 at 19:38 Comment(0)
H
1

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

Hayton answered 8/9, 2021 at 8:46 Comment(0)
C
1

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};
    };
Cornflower answered 20/11, 2021 at 22:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.