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 在这里的行为,但这实际上是正确的吗?
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 可能是正确的。