Clang和GCC对转换运算符直接初始化的合法性存在分歧

时间:2016-10-12 19:34:48

标签: c++ initialization type-conversion language-lawyer conversion-operator

最新版本的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

原始示例合法吗?哪个编译器错了?在检查标准中关于转换函数和重载分辨率的部分后,我无法找到明确的答案。

3 个答案:

答案 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),该点被修改核心问题16041571(按此顺序)。

这些决议的底线是

  

如果T1T2是类类型且T1T2无参考相关,则用户定义的转化次数为   通过用户定义的转换(8.6,13.3.1.4,13.3.1.5)考虑使用“ cv1 T1”类型的对象的复制初始化规则;如果相应的非参考拷贝初始化将是不正确的,则程序是不正确的。 调用转换函数的结果,如非参考拷贝初始化所述,然后用于直接初始化参考。

第一部分只是选择了转换运算符。因此,根据粗体部分,我们使用const Y来直接初始化Y&&。同样,我们会直到最后一个子弹点,由于(5.2.2.3)而失败:

  

如果T1T2的参考相关:
- 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 ++编译器,那么您应该仔细研究他的答案。如果您正在编写应用程序代码,则不应编写需要查看其答案的代码。