以下代码可使用大多数现代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"}}});
}
但是,为什么要首先编译它?列出的编译器如何解释该代码?
为什么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"}}});
除了解释编译器的行为外,我将尝试解释该标准的内容。
直接初始化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"}}
-> [over.best.ics] / 4const B&
不存在:
但是,如果目标是
—构造函数的第一个参数,或者
—用户定义的转换函数的隐式对象参数
并且构造函数或用户定义的转换函数是
— [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&)
声明了构造函数,结果也与主要示例相同。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句