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

Clang and GCC disagree on legality of direct initialization with conversion operator

本文关键字:合法性 初始化 存在 GCC 运算符 转换 Clang      更新时间:2023-10-16

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的move构造函数
  • 从转换运算符中删除const
  • Y y = x替换Y y(x)

原来的例子合法吗?哪个编译器错了?在检查了标准中关于转换函数和过载解决方案的部分后,我一直未能找到明确的答案。

当我们枚举构造函数并检查它们的可行性时,即是否存在移动构造函数的隐式转换序列时,[dcl.init.ref]/5一直到最后一个要点(5.2.2(,该要点已由核心问题1604和1571(按顺序(修改。

这些决议的底线是

如果T1T2是类类型,并且T1不是与T2相关的引用,则用户定义的转换为考虑通过用户定义的转换使用">cv1T1"类型对象的复制初始化规则(8.6、13.3.1.4、13.3.1.5(;如果相应的非引用副本初始化将是格式不良的,则程序是格式不良调用转换函数的结果,如非引用副本初始化所述,然后用于直接初始化引用

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

如果T1是与T2相关的参考:
--cv1应相同cv资格为,或大于,cv2;和

然而,这不再适用于我们最初的重载解决方案,它只看到转换运算符应用于直接初始化引用。在您的示例中,重载解析选择移动构造函数是因为[over.ics.rank]/(3.2.5(,然后上面的段落使程序格式不正确。这是一个缺陷,已作为核心问题2077提交。一个合理的解决方案是在重载解析期间丢弃move构造函数。

所有这些对您的修复都是有意义的:删除const可以防止失败,因为类型现在是引用兼容的,删除move构造函数会留下copy构造函数,它有一个const引用(即也可以工作(。最后,当我们编写Y y = x;时,应用(17.6.3(而不是[dcl.init]/(17.6.2(;

否则(即,对于剩余的复制初始化情况(,如13.3.1.4所述,枚举可以从源类型转换为目标类型或(当使用转换函数时(转换为其派生类的用户定义转换序列,并通过过载解析(13.3(选择最佳转换序列。[…].调用用于直接初始化,根据上面的规则,复制初始化的目标对象

也就是说,初始化实际上与成功的Y y(x.operator const Y());相同,因为移动构造函数是不可行的(Y&& y = const Y失败得足够浅(,并且选择了复制构造函数。

我认为这是一个clang错误。

我们从[over.match.ctor]开始:

当类类型的对象被直接初始化时(8.6(,从相同或派生类类型(8.6(或默认初始化(8.6(,重载解析选择构造函数。用于直接初始化或不在复制初始化上下文中的默认初始化,候选函数是正在初始化的对象的类的所有构造函数。

例如,我们考虑复制构造函数。复制构造函数可行吗?

来自[dcl.init.ref]:

--如果初始值设定项表达式[…]具有类类型(即,T2是类类型(,其中T1不是与T2相关的引用,并且可以是转换为类型为"cv3T3"的右值,其中"cv1T1"是与"cv3"兼容的参考T3"(见13.3.1.6(,则引用绑定到第一种情况下的初始值设定项表达式的值,并绑定到第二种情况下的转换结果。

[over.match.ref]中的候选函数是:

对于直接初始化,那些显式转换函数不隐藏在S中,并且收益类型为"对cv2 T2的左值引用"或"对cv2 T2的右值引用"cv2 T2",其中T2与T类型相同,或者可以通过限定转换为T类型转换(4.5(也是候选函数。

其中包括我们的operator const Y()。因此,复制构造函数是可行的。move构造函数不是(因为你不能将非const右值引用绑定到const右值(,所以我们只有一个可行的候选者,这使得程序格式良好。


呃,作为后续,这是LLVM错误16682,这使它看起来比我最初布局的要复杂得多。

当您编写的代码中,两个优秀的编译器对它是否合法存在分歧时,您的编程过于接近边缘。假设我是一名支持该代码的维护程序员。如果连gcc和clang都不能达成一致,你怎么希望我知道这是否合法,以及这段代码的确切语义是什么?

更改代码。让它变得更简单,这样不那么"聪明"的程序员和编译器都能毫无问题地理解它。周围最"聪明"的程序员是没有奖品的。

看看科伦坡的回答:我毫不怀疑他对形势的分析是完全正确的。但我不想支持需要非常聪明的50行分析来证明它是正确的代码。如果你正在编写C++编译器,你应该仔细研究他的答案。如果您正在编写应用程序代码,则永远不应该编写需要查看其答案的代码。