C++ 中模板转换 `operator const T &` 有什么属性?

Fed*_*dor 8 c++ templates type-conversion language-lawyer

每个 C++ 类都可以声明其他类型的转换运算符,特别是该运算符可以是模板并且可以转换为 const 引用类型。但现代编译器似乎以不同的方式对待这个运算符。下一个例子:

struct B {};
struct A {
    template <typename T> operator const T &();
    operator B() = delete;
};

int main() { 
    A a;
    [[maybe_unused]] B b(a); //error in Clang
    [[maybe_unused]] int c(a); //error in GCC
}
Run Code Online (Sandbox Code Playgroud)

已被 MSVC 完全接受。

AGCC 接受从到 的转换,B尽管A::operator B我希望首选的 non-template 已被显式删除。

同时 GCC 拒绝从A到 的转换int

cannot convert 'A' to 'int' in initialization
Run Code Online (Sandbox Code Playgroud)

演示: https: //gcc.godbolt.org/z/WvafjPPjr

我个人更喜欢 Clang 在这里的行为,但这实际上是正确的吗?

cpp*_*ner 2

B b(a);是类类型的直接初始化。根据[dcl.init.general]B重载决策中会考虑 的构造函数。

B有三个隐式生成的构造函数:

B();         // #1
B(const B&); // #2
B(B&&);      // #3
Run Code Online (Sandbox Code Playgroud)

根据[over.match.viable],只有 #2 和 #3 可行,因为 #1 没有足够的参数。

重载解析尝试形成隐式转换序列a重载解析尝试为每个构造函数

根据[一般转化],转换为引用相当于引用初始化。

根据[dcl.init.ref],在 的初始化中,如果源表达式可以通过以下方式const B&转换为类型的左值const Boperator const B&类型的左值,则引用将绑定到转换结果。

同样,根据[dcl.init.ref],在 的初始化中B&&,如果源表达式可以转换为Bvia类型的右值operator B,则引用将绑定到转换结果。否则,再次按照[dcl.init.ref],考虑其他用户定义的转换。

由于a无法以任何方式转换为B&&,并且可以转换为const B&,因此 #2 获胜。soa被转换为const B&然后用于b通过复制构造函数进行初始化。

int c(a);是非类类型的直接初始化。根据[dcl.init.general],在重载决策中考虑 的转换函数A

根据[temp.deduct.conv], 的模板参数operator const T &可以推导为int

由于 的结果operator const int&可以转换为int,因此初始化是格式良好的。

所以我相信这两个初始化都是有效的。也就是说,MSVC 是对的。然而,[dcl.init.ref] 中的“可以转换”措辞很草率,可能意味着理论上可以通过删除的转换函数来转换值,在这种情况下,Clang 可能是正确的。