显式默认构造函数

gen*_*ata 20 c++ language-lawyer c++11 c++14

此代码与GCC 5.X,MSVC编译良好,但GCC 6.X给出错误:

"从初始化列表转换为'a'将使用显式构造函数'a :: a()'",clang "选择的构造函数在复制初始化中是显式的".

删除explicit或更改以a c{}解决问题,但我很好奇为什么它以这种方式工作.

class a
{
public:
    explicit a () {}
};
struct b
{
    a c;
};

int main() {
    b d{};
}
Run Code Online (Sandbox Code Playgroud)

Pra*_*ian 22

b是一个聚合.使用初始化列表初始化它时,列表中的元素将初始化聚合的前n个成员,其中n是列表中元素的数量.聚合的其余元素是copy-list-initialized.

因此在您的示例中,c将进行copy-list-initialized,但如果选择的构造函数是explicit错误的,那么这是错误的.

相关的标准报价是

[dcl.init.aggr]/3

当聚合由[dcl.init.list]中指定的初始化列表初始化时,初始化列表的元素将被视为聚合元素的初始化器.聚合的显式初始化元素确定如下:
...
- 如果初始化列表是初始化列表,则聚合的显式初始化元素是聚合的前n个元素,其中n是元素的数量初始化列表.
- 否则,初始化列表必须是{},并且没有显式初始化的元素.

[dcl.init.aggr]/5

对于非联合聚合,每个不是显式初始化元素的元素都按如下方式初始化:
...
- 否则,如果该元素不是引用,则从空初始化列表中复制初始化该元素([dcl. init.list]).

c从空初始化列表中复制初始化的效果如下所述

[dcl.init.list]/3

对象或类型引用的列表初始化T定义如下:
...
- 否则,如果初始化列表没有元素并且T是具有默认构造函数的类类型,则对象是值初始化的.

[dcl.init]/8

值初始化类型的对象T是指:
...
-如果T是具有或者没有默认的构造([class.ctor])或一个默认的构造(可能CV修饰)类类型是用户提供的或删除,则该对象是默认初始化的;

[dcl.init]/7

缺省初始化类型的对象T是指:
-如果T是(可能CV修饰)类类型,构造函数被考虑.枚举适用的构造函数([over.match.ctor]),并通过重载决策选择初始化程序()的最佳构造函数.使用空参数列表调用如此选择的构造函数来初始化对象.

[over.match.ctor]

...对于复制初始化,候选函数是该类的所有转换构造函数.

[class.conv.ctor]/1

声明没有函数说明 explicit符的构造函数指定从其参数类型(如果有)到其类的类型的转换.这样的构造函数称为转换构造函数.

在上面的示例中,a没有转换构造函数,因此重载解析失败.[class.conv.ctor]/2中的(非规范)示例甚至包含一个非常类似的情况

  struct Z {
    explicit Z();
    explicit Z(int);
    explicit Z(int, int);
  };

  Z c = {};                       // error: copy-list-initialization
Run Code Online (Sandbox Code Playgroud)

您可以通过提供默认成员初始值设定项来避免错误 c

struct b
{
    a c{};  // direct-list-initialization, explicit ctor is OK
};
Run Code Online (Sandbox Code Playgroud)

  • over.match.ctor说"对于不在复制初始化上下文中的直接初始化或默认初始化,候选函数是正在初始化的对象的类的所有构造函数.对于复制初始化,候选函数是该类的所有转换构造函数.参数列表是初始化程序的表达式列表或赋值表达式." 这意味着我们只考虑转换构造函数. (2认同)