为什么我不能对继承自另一个结构体的结构体使用大括号初始化?

52

当我运行这段代码时:

struct X {
    int a;
};

struct Y : public X {};

X x = {0};
Y Y = {0};

我理解为:

error: could not convert ‘{0}’ from ‘<brace-enclosed initializer list>’ to ‘Y’

为什么花括号初始化对基类有效,而对派生类无效?

4个回答

56

C++17之前的标准版本的回答:

您的问题与聚合初始化有关: struct X是一个聚合体,而struct Y不是。以下是关于聚合体(8.5.1)的标准引用:

聚合体是数组或没有用户提供的构造函数(12.1),非静态数据成员没有大括号或等于符号初始化程序(9.2),没有私有或受保护非静态数据成员(第11条),没有基类(第10条)和没有虚拟函数(10.3)的类(第9条)。

该条款规定了如果一个类具有基类,则它不是聚合体。在这里,struct Ystruct X作为基类,因此不能是聚合类型。

关于您所遇到的特定问题,请参阅标准中的以下条款:

当使用初始化程序列表初始化聚合体时,如8.5.4中所指定,初始化程序列表的元素按递增的下标或成员顺序作为聚合体的成员的初始值。每个成员从相应的初始化程序子句中进行复制初始化。如果初始化程序子句是表达式并需要缩小转换(8.5.4)来转换表达式,则程序无法形成。

当您执行X x = {0}时,使用聚合初始化将a初始化为0。但是,当您执行Y y = {0}时,由于struct Y不是聚合类型,编译器将查找适当的构造函数。由于隐式生成的构造函数(默认值,复制和移动)都不能对单个整数执行任何操作,因此编译器会拒绝您的代码。


关于这个构造函数查找,clang++的错误消息更加明确地说明了编译器实际上要做什么(在线示例):

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

请注意,有一个提案来扩展聚合初始化以支持您的用例,并且它已经被纳入C++17。如果我理解正确,它使您的示例具有您期望的语义有效。因此...您只需等待符合C++17的编译器。


2
clang++ -std=c++1z 版本 4.0.1 可以编译, g++ -std=c++17 版本 7.2.1 可以编译, 但是 Visual Studio 2017 的 cl 版本 19.12.25816 还不能。 - Patrick Fromberg
1
应该从今年夏天开始,使用VS 2017 15.7版本就已经得到支持了。 - mirh
C++14用户最干净的替代方案是什么? - gatis paeglis

6

使用C++17及更高版本,您可以使用大括号初始化派生结构体。

您的代码现在可以编译(GodBolt)。

使用大括号进行初始化会得到一个警告。因此,初始化Y的推荐方式是:

Y y = { {0} };

而不是;

Y y = { 0 };

这是因为这些继承结构现在被视为“聚合类型”(支持此类型的聚合初始化)。请参见2015年的这篇论文

1

在C++14及更低版本中,构造函数应该使用默认初始化。

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 最适合的用途是:

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

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

1
这个问题仍然是谷歌搜索结果的前几名,所以我在这里回答。在C++17中是可能的,但只适用于没有构造函数、虚函数和私有/受保护成员的派生POD类型。
注意:今年(2021年)的STM32(CubeIDE)eclipse仍然没有提供C++17的菜单选项,但可以手动设置,例如-std=gnu++17。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接