12

最新版本的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 将接受代码:

  • RemoveY的移动构造函数
  • const从转换运算符中移除
  • 替换Y y(x)Y y = x

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

4

3 回答 3

4

我认为这是一个clang错误。

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

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

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

来自 [dcl.init.ref]:

— 如果初始化表达式 [...] 具有类类型(即 T2 是类类型),其中 T1 与 T2 没有引用相关,并且可以转换为“cv3 T3”类型的右值,其中“ cv1 T1”与“cv3 T3”(见 13.3.1.6)是引用兼容的,那么引用在第一种情况下绑定到初始化表达式的值,在第二种情况下绑定到转换结果。

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

对于直接初始化,那些不隐藏在 S 中的显式转换函数分别产生类型“lvalue reference to cv2 T2”或“cv2 T2”或“rvalue reference to cv2 T2”,其中 T2 与 T 或可以通过限定转换 (4.5) 转换为类型 T,也是候选函数。

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


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

于 2016-10-12T19:59:52.713 回答
4

当我们枚举构造函数并检查它们的可行性时 - 即是否存在隐式转换序列 - 对于移动构造函数,[dcl.init.ref]/5落入最后一个要点(5.2.2),即由核心问题16041571(按此顺序)修改。

这些决议的底线是

如果T1orT2是一个类类型并且与T1不相关T2,则使用T1用户定义的转换(8.6、13.3.1.4、 13.3.1.5);如果相应的非参考复制初始化格式错误,则程序格式错误。调用转换函数的结果,如针对非引用复制初始化所描述的,然后用于直接初始化引用。

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

If T1is reference-related to T2:
cv1应与cv2具有相同的 cv-qualification 或大于 cv-qualification ;和

然而,这不再属于我们最初的重载决议,它只看到转换运算符将用于直接初始化引用。在您的示例中,重载解析选择移动构造函数,因为[over.ics.rank]/(3.2.5),然后上面的段落使程序格式错误。这是一个缺陷,已作为核心问题 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失败得足够浅)并且选择了复制构造函数。

于 2016-10-13T00:21:38.173 回答
-1

当您编写代码时两个体面的编译器不同意它是否合法时,您的编程距离边缘太近了。假设我是一名支持该代码的维护程序员。如果连 gcc 和 clang 都不能达成一致,你怎么指望我知道这是否合法,以及这段代码的语义到底是什么?

更改您的代码。使其更简单,以便不太“聪明”的程序员和编译器都能毫无问题地理解它。成为最“聪明”的程序员是没有奖品的。

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

于 2016-10-13T08:26:42.803 回答