GCC与Clang转换函数重载选择不一致,哪种符合C++标准?
该初始化代码存在歧义,属于非良构程序,GCC和Clang目前的实现都不符合C++标准要求,正确的行为应该是编译器报重载决议歧义错误。
#include <iostream> struct T{ T() = default; T(T const&) =default; }; T global; struct S{ operator T(){ std::cout<<"#1\n"; return T{}; } operator T&(){ std::cout<<"#2\n"; return global; } }; int main(){ S s; T obj(s); }
候选集枚举逻辑
T obj(s)属于直接初始化场景,首先枚举T的单参数构造函数作为候选,唯一可行的构造函数是T(const T&)。要调用该构造函数,需要将S类型左值s转换为可绑定到const T&的实参,因此按[over.match.copy]规则枚举S的转换函数:
当初始化表达式类型为类类型「cv S」时,会考虑S及其基类的非显式转换函数。若初始化的临时对象要绑定到构造函数的首个参数,该参数类型为「到cv2 T的引用」,且构造函数是在「cv3 T」类型对象直接初始化场景下以单参数调用,也会考虑显式转换函数。未在S中隐藏、产出的类型cv无修饰版本与T相同或为其派生类的函数为候选函数。返回「到X的引用」的转换函数调用是X类型的左值,因此在选择候选函数的过程中,此类转换函数视为产出X类型。
两个转换函数都符合候选要求:
operator T()返回T类型纯右值,可绑定到const T&operator T&()返回T类型左值,也可绑定到const T&
最优解判定逻辑
按照[over.match.best]的规则,两个候选函数无法区分优先级:
- 首先比较参数的转换序列:两个转换函数的隐式对象参数都是
S&,实参是S类型左值,转换序列都是完全匹配的身份转换,无法区分优先级。 - 再比较转换函数返回值到目标类型的转换序列:
上下文为用户定义转换初始化(见[dcl.init]、[over.match.conv]、[over.match.ref]),且F1返回类型到目标类型(即被初始化实体的类型)的标准转换序列,优于F2返回类型到目标类型的标准转换序列。
两个转换函数的返回值到const T&的转换都是身份转换等级,没有优先级差异。
因此重载决议出现歧义,程序非良构,编译器应该报错。
目前GCC和Clang都没有正确识别该歧义,属于实现缺陷。部分高版本编译器开启-Weverything等最高警告等级时,会给出歧义相关的提示。
内容的提问来源于stack exchange,提问作者xmh0511

