我有以下代码:
class A { public: operator int() const { return 5; } }; class B { public: operator int() const { return 6; } }; int main() { A a; B b; int myInt = true ? a : b; return 0; }
尝试使用Visual Studio 2017 RC编译该代码会导致以下错误:
错误C2446 ::
:
没有转换B
为A
注意:没有可用于执行此转换的用户定义转换运算符,或者无法调用运算符
...这是令人惊讶的,因为在这种情况下,我希望它将它们转换为通用类型int
.
clang
(4.0)成功编译相同的代码,没有任何错误或警告.
在这种情况下,哪两个是正确的,为什么?
TL; DR; clang
是正确的,因为A
和之间没有可能的转换B
,重载决策用于确定要应用于操作数的转换,并选择以下(虚构)重载运算符:
int operator?:(bool, int, int);
?:
对于任何算术类型对,都存在运算符的这种(再次,虚构的)重载(参见下面的参考文献).
既然你不能转换A
到B
或B
至A
,然后出现以下情况:
[expr.cond]
否则,结果是prvalue.如果第二个和第三个操作数不具有相同的类型,并且具有(可能是cv限定的)类类型,则使用重载决策来确定要应用于操作数的转换(如果有)([over.match.oper] ],[over.built]).如果重载决策失败,则程序格式错误.否则,应用如此确定的转换,并使用转换的操作数代替本子条款其余部分的原始操作数.
这可以追溯到这个:
[over.match.oper]
如果任一操作数具有类或枚举类型,则可以声明用户定义的操作符函数来实现此运算符,或者可能需要用户定义的转换将操作数转换为适合于构造函数的类型.在运营商.
[...]
重载决策的候选函数集是成员候选者,非成员候选者和内置候选者的联合.
如果通过重载决策选择内置候选,则类类型的操作数被转换为所选操作函数的相应参数的类型,除了不应用用户定义的转换序列的第二标准转换序列.然后将运算符视为相应的内置运算符,并根据[expr.compound]进行解释.
在您的情况下,有一个内置的候选人:
[over.built#27]
对于每对提升的算术类型,
L
并且R
存在表单的候选运算符函数LR operator?:(bool, L, R);类型和之间
LR
通常的算术转换([expr.arith.conv])的结果在哪里.[ 注意:与候选函数的所有这些描述一样,此声明仅用于描述内置运算符以用于重载解析.运算符" "不能超载.- 结束说明 ]L
R
?:
由于?:
运算符不能重载,这意味着只有两种类型都可以转换为算术类型(例如int
)时,代码才有效.作为"计数器"示例,以下代码格式错误:
struct C { }; struct A { operator C() const; }; struct B { operator C() const; }; auto c = true ? A{} : B{}; // error: operands to ?: have different types 'A' and 'B'
另请注意,如果其中一种类型可转换为两种不同的算术类型,则会出现模糊的"调用",例如,int
和float
:
struct A { operator int() const; }; struct B { operator int() const; operator float() const; }; auto c = true ? A{} : B{};
错误(来自gcc)实际上充满了信息:
错误:三元'运算符不匹配?:'(操作数类型是'bool','A'和'B')
auto c = true ? A{} : B{}; ~~~~~^~~~~~~~~~~
注:候选人:
operator?:(bool, float, int)
注:候选人:
operator?:(bool, float, float)