显式转换运算符和常量引用限定

问题描述

下面的例子在 godbolt 上编译得很好,带有 clang,但在 msvc 和 gcc 上编译失败:

-std=c++17

据我所知,运算符上的 struct Foo { }; struct Bar { explicit operator Foo() // w/o explicit qualification all are happy { return Foo(); } }; int main() { Bar b; const Foo& foo = static_cast<const Foo&>(b); // only clang happy with this const Foo& foo2 = static_cast<const Foo&>(static_cast<Foo>(b)); // clang / msvc / gcc happy with this return 0; } 只是禁止 implicit conversions,并且由于页面列出了其中的限定转换,我认为在这种情况下,clang 是完全错误的?还是我遗漏了什么?

我最好坚持使用单一转换,因为我在模板化代码中使用它,如果常量引用转换运算符可用,则使用它。但我也可以删除 explicit 约束。

解决方法

OP 的例子并不简单。一个真正最小的示例使用,而不是 static_cast 和初始化,只需:

const Foo& foo(b);

无论如何,这就是 static_cast<const Foo&>(b) 应该做的。

再次,Clang 接受而 GCC 拒绝 [godbolt]。如果将其更改为复制初始化 (const Foo& foo = b;),则 Clang 和 GCC 都会拒绝。

看来Clang是正确的:在直接初始化的情况下,为什么不应该使用explicit转换函数?

问题被标记为 c++11,因此请参见 C++11 [over.match.ref]/1:

在 8.5.3 中指定的条件下,引用可以直接绑定到泛左值或类纯右值,这是将转换函数应用于初始化表达式的结果。重载解析用于选择要调用的转换函数。假设“cv1 T”是被初始化的引用的底层类型,而“cv S”是初始化表达式的类型,以S为类类型,候选函数选择如下:

  • 考虑S及其基类的转换函数,除了复制初始化,只考虑非显式转换函数。那些没有隐藏在 S 中并产生类型“lvalue reference to cv2 T2”(当 8.5.3 需要左值结果时)或“cv2”的那些em> T2”或“rvalue reference to cv2 T2”(当 8.5.3 需要右值结果时),其中“cv1 {{ 1}}”与“cv2 T”的引用兼容 (8.5.3),是候选函数。

8.5.3 中的措辞是 defect report 的主题,该主题在 C++14 中得到解决,以阐明引用绑定不使用“隐式转换”,而是使用“转换”(其中[over.match.ref] 仅在复制初始化的情况下排除 T2 函数)。这个决议应该被认为是对 C++11 的追溯。

每个版本的标准都有不同的措辞,但我认为结论是一样的。

我查找了有关此问题的现有 GCC 错误报告,但令人惊讶的是找不到。我认为打开错误报告是个好主意,如果 GCC 开发人员坚持 GCC 是正确的,他们会解释他们为什么这么认为。