左值右值专属转换运算符重载歧义原因咨询
我定义了带左值/右值限定的类型转换运算符,原本预期临时Wrapper对象会始终调用右值转换运算符,但在直接构造和赋值时出现编译歧义,只有拷贝初始化能正常通过。代码在GCC 8.3可编译,但GCC 8.4及以上版本、Clang、MSVC均失败,具体代码如下:
struct MyData { /*..*/ }; template < typename T > struct Wrapper { T m_value; operator T const & () const & { return m_value; } operator T && () && { return ::std::move(m_value); } }; using MyWrapper = Wrapper< MyData >; MyWrapper foo () { return {}; } int main () { MyData first(foo()); // error: call of overloaded 'MyData(MyWrapper)' is ambiguous MyData second = foo(); // ok second = foo(); // error: ambiguous overload for 'operator=' return 0; }
问题原因分析
直接构造的歧义:
直接初始化时,编译器会枚举所有可行的转换路径:临时Wrapper对象作为右值,既可以调用右值限定的operator T&& () &&得到MyData&&(匹配移动构造),也可以调用const左值限定的operator T const& () const&——因为const成员函数允许被右值调用——得到const MyData&(匹配拷贝构造)。这两个构造路径的重载优先级完全相同,编译器无法抉择,因此报错。拷贝初始化正常的原因:
拷贝初始化的规则是先将右侧表达式转换为目标类型的临时对象。此时编译器会选择最匹配的用户定义转换序列:右值对象调用右值限定的转换运算符,比调用const左值转换运算符的匹配度更高(限定符完全匹配),因此只会生成一条有效转换路径,无歧义。赋值操作的歧义:
赋值时,编译器会同时匹配两个重载:通过右值转换得到MyData&&匹配移动赋值,通过const左值转换得到const MyData&匹配拷贝赋值。这两个重载的匹配度一致,导致歧义报错。GCC 8.3的特殊情况:
GCC 8.3对右值对象调用const左值限定转换运算符的处理不符合C++标准,错误地赋予了右值限定运算符更高的优先级,因此没有触发歧义。后续版本的GCC以及其他编译器严格遵循标准,所以编译失败。
内容的提问来源于stack exchange,提问作者ssoft

