显式构造函数和嵌套初始化列表

Ext*_*t3h 10 c++ overload-resolution c++11 list-initialization

以下代码成功编译了大多数现代C++ 11兼容编译器(GCC> = 5.x,Clang,ICC,MSVC).

#include <string>

struct A
{
        explicit A(const char *) {}
        A(std::string) {}
};

struct B
{
        B(A) {}
        B(B &) = delete;
};

int main( void )
{
        B b1({{{"test"}}});
}
Run Code Online (Sandbox Code Playgroud)

但是为什么它首先编译,以及列出的编译器如何解释该代码?

为什么MSVC能够在没有的情况下编译它B(B &) = delete;,但其他3个编译器都需要它?

为什么在删除复制构造函数的不同签名时,除了MSVC之外的所有编译器都会失败,例如B(const B &) = delete;

编译器甚至都选择相同的构造函数吗?

为什么Clang会发出以下警告?

17 : <source>:17:16: warning: braces around scalar initializer [-Wbraced-scalar-init]
        B b1({{{"test"}}});
Run Code Online (Sandbox Code Playgroud)

xsk*_*xzr 5

我将尝试解释标准所说的内容,而不是解释编译器的行为.

主要例子

直接初始化b1{{{"test"}}},重载适用于选择最佳的构造B.因为从没有隐式转换{{{"test"}}}B&(列表初始化是不是一个左值),构造B(B&)是不可行的.然后我们专注于构造函数B(A),并检查它是否可行.

确定从隐式转换序列{{{"test"}}}A(我会用符号{{{"test"}}}- > A为简单起见),重载适用于选择最佳的构造函数A,所以我们需要比较{{"test"}}- > const char*{{"test"}}- > std::string(注意括号的最外层会被省略)根据[over.match.list]/1:

当非聚合类类型T的对象被列表初始化,使得[dcl.init.list]指定根据本子条款中的规则执行重载解析时,重载决策分两个阶段选择构造函数:

  • 最初,候选函数是类T的初始化列表构造函数([dcl.init.list])...

  • 如果找不到可行的初始化列表构造函数,则再次执行重载解析,其中候选函数是类T的所有构造函数,参数列表由初始化列表的元素组成.

...在复制列表初始化中,如果选择了显式构造函数,则初始化是错误的.

注意,无论说明符如何,都会考虑所有构造函数explicit.

{{"test"}}- > const char*根据[over.ics.list]/10[over.ics.list]/11不存在:

否则,如果参数类型不是类:

  • 如果初始化列表有一个元素本身不是初始化列表 ...

  • 如果初始化列表没有元素......

在除上述列举之外的所有情况下,都不可能进行转换.

要确定{{"test"}}- > std::string,将采用相同的过程,并且重载决策选择std::string带有类型参数的构造函数const char*.

结果,{{{"test"}}}- > A通过选择构造函数完成A(std::string).


变化

如果explicit被删除怎么办?

这个过程不会改变.GCC将选择构造函数,A(const char*)而Clang将选择构造函数A(std::string).我认为这是GCC的一个错误.

如果初始化器中只有两层括号b1怎么办?

注意{{"test"}}- > const char*不存在但是{"test"}- > const char*存在.因此,如果初始化器中只有两层大括号,则选择b1构造函数A(const char*),因为{"test"}- > const char*优于{"test"}- > std::string.其结果,一个显式的构造被选择在复制列表初始化(参数的初始化A在构造B(A){"test"}),然后是形成不良的节目.

如果构造函数B(const B&)被声明怎么办?

请注意,如果B(B&)删除声明,也会发生这种情况.这次我们需要比较{{{"test"}}}- > A{{{"test"}}}- > const B&,或{{{"test"}}}- > const B等效.

为了确定{{{"test"}}}- > const B,采用上述过程.我们需要比较{{"test"}}- > A{{"test"}}- > const B&.注意{{"test"}}- > const B&根据[over.best.ics]/4不存在:

但是,如果目标是

- 构造函数的第一个参数或

- 用户定义的转换函数的隐式对象参数

并且构造函数或用户定义的转换函数是候选者

- [over.match.ctor],当参数是类复制初始化的第二步中的临时参数时,

- [over.match.copy],[over.match.conv]或[over.match.ref](在所有情况下),或者

- [over.match.list]的第二阶段,当初始化列表只有一个元素本身就是一个初始化列表时,目标是类X的构造函数的第一个参数,转换是X或引用cv X,

不考虑用户定义的转换序列.

为了确定{{"test"}}- > A,再次采用上述过程.这与我们在前一小节中谈到的情况几乎相同.结果,A(const char*)选择了构造函数.请注意,此处选择构造函数来确定{{{"test"}}}- > const B,并且实际上不适用.尽管构造函数是显式的,但这是允许的.

结果,{{{"test"}}}- > const B通过选择构造函数B(A),然后选择构造函数来完成A(const char*).现在,{{{"test"}}}- > A{{{"test"}}}- > const B都是用户定义的转换序列,两者都不比另一个好,所以初始化b1是不明确的.

如果括号被括号替换怎么办?

根据前面小节中引用的[over.best.ics]/4,不考虑用户定义的转换{{{"test"}}}- > const B&.因此,即使B(const B&)声明了构造函数,结果也与主要示例相同.