最新版本的clang(3.9)在f
的第二行拒绝此代码;最新版本的gcc(6.2)接受它:
struct Y {
Y();
Y(const Y&);
Y(Y&&);
};
struct X {
operator const Y();
};
void f() {
X x;
Y y(x);
}
如果进行了任何这些更改,clang将接受代码:
Y
的移动构造函数const
Y y(x)
替换为Y y = x
原始示例合法吗?哪个编译器错了?在检查标准中关于转换函数和重载分辨率的部分后,我无法找到明确的答案。
答案 0 :(得分:4)
我认为这是一个铿锵的错误。
我们从[over.match.ctor]开始:
当类类型的对象被直接初始化(8.6)时,从相同或a的表达式复制初始化 派生类类型(8.6)或默认初始化(8.6),重载决策选择构造函数。用于直接初始化 或者不在复制初始化上下文中的默认初始化,即候选函数 是正在初始化的对象类的所有构造函数。
因此我们考虑复制构造函数。复制构造函数是否可行?
来自[dcl.init.ref]:
- 如果初始化表达式[...]具有类类型(即T2是类类型),其中T1与T2不是引用相关的,并且可以是 转换为“cv3 T3”类型的右值,其中“cv1 T1”与“cv3”引用兼容 T3“(见13.3.1.6)然后引用绑定到第一种情况下的初始化表达式的值和 第二种情况下转换的结果。
[over.match.ref]中的候选函数是:
对于直接初始化,那些显式转换函数 不隐藏在S和yield类型“左值引用cv2 T2”或“cv2 T2”或“rvalue reference to cv2 T2“,其中T2与T的类型相同,或者可以转换为具有限定条件的类型T. 转换(4.5),也是候选函数。
其中包括我们的operator const Y()
。因此,复制构造函数 是可行的。移动构造函数不是(因为你不能将非const
右值引用绑定到const
rvalue),所以我们只有一个可行的候选者,这使程序格式良好。
呃,作为一个后续,这是LLVM bug 16682,这使得它看起来比我最初制定的要复杂得多。
答案 1 :(得分:4)
当我们枚举构造函数并检查它们的可行性时 - 即是否存在隐式转换序列 - 对于移动构造函数,[dcl.init.ref]/5落到最后一个项目符号点(5.2.2),该点被修改核心问题1604和1571(按此顺序)。
这些决议的底线是
如果
T1
或T2
是类类型且T1
与T2
无参考相关,则用户定义的转化次数为 通过用户定义的转换(8.6,13.3.1.4,13.3.1.5)考虑使用“ cv1T1
”类型的对象的复制初始化规则;如果相应的非参考拷贝初始化将是不正确的,则程序是不正确的。 调用转换函数的结果,如非参考拷贝初始化所述,然后用于直接初始化参考。
第一部分只是选择了转换运算符。因此,根据粗体部分,我们使用const Y
来直接初始化Y&&
。同样,我们会直到最后一个子弹点,由于(5.2.2.3)而失败:
如果
T1
与T2
的参考相关:
- cv1 应相同 cv-qualification为,或者比 cv2 更高的cv资格;和
但是,这不再符合我们原来的重载分辨率,只能看到转换运算符用于直接初始化引用。在您的示例中,重载决策选择移动构造函数,因为[over.ics.rank]/(3.2.5),然后上面的段落使程序格式错误。这是一个缺陷,已归档为core issue 2077。一个明智的解决方案是在重载解析期间丢弃移动构造函数。
所有这些对你的修复都有意义:删除const
会阻止掉落,因为类型现在是引用兼容的,并且删除移动构造函数会留下复制构造函数,它具有const引用(即同样有效)。最后,当我们写Y y = x;
时,而不是[dcl.init] /(17.6.2),(17.6.3)适用;
否则(即,对于剩余的复制初始化情况),可以如上所述枚举可以从源类型转换为目标类型或(当使用转换函数时)到其派生类的用户定义的转换序列。在13.3.1.4中,通过重载决策(13.3)选择最好的一个。 [...]。 该调用用于根据上述规则直接初始化作为复制初始化目标的对象。
即。初始化实际上与成功的Y y(x.operator const Y());
相同,因为移动构造函数不可行(Y&& y = const Y
足够失败)并且选择了复制构造函数。
答案 2 :(得分:-1)
当你编写代码时,两个不错的编译器不同意它是否合法,你编程太接近边缘了。让我们说我是支持该代码的维护程序员。您如何期望我知道这是否合法,以及这段代码的语义究竟是什么,即使gcc和clang也不同意?
更改您的代码。使它更简单,以便更少"聪明"程序员和编译器可以毫无问题地理解它。没有任何奖项可以成为最聪明的"聪明的"程序员。
看看Columbo的回答:我毫不怀疑他对情况的分析是完全正确的。但我不想支持需要非常聪明的50行分析来证明它是正确的代码。如果您正在编写C ++编译器,那么您应该仔细研究他的答案。如果您正在编写应用程序代码,则不应编写需要查看其答案的代码。