为什么我不能括起来初始化从另一个结构派生的结构?

Eri*_*ric 37 c++ struct c++11 list-initialization

当我运行此代码时:

struct X {
    int a;
};

struct Y : public X {};

X x = {0};
Y Y = {0};
Run Code Online (Sandbox Code Playgroud)

我明白了:

error: could not convert ‘{0}’ from ‘<brace-enclosed initializer list>’ to ‘Y’
Run Code Online (Sandbox Code Playgroud)

为什么大括号初始化适用于基类而不适用于派生类?

Mor*_*enn 47

您的问题与聚合初始化有关:struct X是聚合而struct Y不是聚合.以下是有关聚合的标准报价(8.5.1):

聚合是一个数组或类(第9节),没有用户提供的构造函数(12.1),非静态数据成员(9.2)没有大括号或等于初始值,没有私有或受保护的非静态数据成员(第11条),没有基类(第10条),没有虚函数(10.3).

该子句指定如果a class具有基类,则它不是聚合.在此,struct Y具有struct X作为基类,因此不能是聚合类型.

关于您遇到的特殊问题,请遵循以下标准:

当初始化程序列表初始化聚合时,如8.5.4中所述,初始化程序列表的元素将作为聚合成员的初始化程序,增加下标或成员顺序.每个成员都是从相应的initializer子句复制初始化的.如果initializer子句是表达式并且转换表达式需要缩小转换(8.5.4),则程序格式错误.

执行此操作时X x = {0},将使用聚合初始化初始化a0.但是,当您这样做时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
Run Code Online (Sandbox Code Playgroud)

请注意,有一个建议是扩展聚合初始化以支持您的用例,并将其转换为C++ 17.如果我正确地阅读它,它会使您的示例与您期望的语义一致.所以......你只需要等待符合C++ 17标准的编译器.

  • 可以编译`clang ++ -std = c ++ 1z`版本4.0.1,`编译g ++ -std = c ++ 17`版本7.2.1,但是Visual Studio 2017`cl`版本19.12.25816尚未 (2认同)

ein*_*ica 7

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

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

您确实会收到有关使用大括号的警告。因此,推荐的初始化 a 的方法Y是:

Y y = { {0} };
Run Code Online (Sandbox Code Playgroud)

而不是;

Y y = { 0 };
Run Code Online (Sandbox Code Playgroud)

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