三元运算符的奇怪隐式转换

Strange implicit conversions with the ternary operator

我有以下代码:

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 编译该代码会导致以下错误:

error C2446: :: no conversion from B to A

note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

...这令人惊讶,因为我希望它将它们都转换为通用类型,在本例中为 int

clang (4.0) 成功编译相同的代码,没有任何错误或警告。

在这种情况下,两者中哪一个是正确的,为什么?

TL;DR; clang 是对的,因为 AB 之间没有可能的转换,所以使用了重载解析以确定要应用于操作数的转换,并选择以下(虚构的)重载运算符:

int operator?:(bool, int, int);

对于任何一对算术类型,都存在 ?: 运算符的这种(同样是虚构的)重载(请参阅下面的参考资料)。


标准规则:

由于您无法将 A 转换为 B 或将 B 转换为 A,因此以下内容适用:

[expr.cond]

Otherwise, the result is a prvalue. If the second and third operands do not have the same type, and either has (possibly cv-qualified) class type, overload resolution is used to determine the conversions (if any) to be applied to the operands ([over.match.oper], [over.built]). If the overload resolution fails, the program is ill-formed. Otherwise, the conversions thus determined are applied, and the converted operands are used in place of the original operands for the remainder of this subclause.

这又回到了这个:

[over.match.oper]

If either operand has a type that is a class or an enumeration, a user-defined operator function might be declared that implements this operator or a user-defined conversion can be necessary to convert the operand to a type that is appropriate for a built-in operator.

[...]

The set of candidate functions for overload resolution is the union of the member candidates, the non-member candidates, and the built-in candidates.

If a built-in candidate is selected by overload resolution, the operands of class type are converted to the types of the corresponding parameters of the selected operation function, except that the second standard conversion sequence of a user-defined conversion sequence is not applied. Then the operator is treated as the corresponding built-in operator and interpreted according to [expr.compound].

在你的情况下,有一个 built-in 候选人:

[over.built#27]

For every pair of promoted arithmetic types L and R, there exist candidate operator functions of the form

LR      operator?:(bool, L, R);

where LR is the result of the usual arithmetic conversions ([expr.arith.conv]) between types L and R. [ Note: As with all these descriptions of candidate functions, this declaration serves only to describe the built-in operator for purposes of overload resolution. The operator “?:” cannot be overloaded. — end note ]


额外的细节:

由于无法重载 ?: 运算符,这意味着您的代码只有在两种类型都可以转换为算术类型(例如 int)时才有效。作为 "counter" 的例子,下面的代码是 ill-formed:

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'

另请注意,如果其中一种类型可转换为两种不同的算术类型,例如 intfloat:

,您将得到一个模棱两可的 "call"
struct A { operator int() const; };
struct B { 
    operator int() const; 
    operator float() const;
};

auto c = true ? A{} : B{};

错误(来自 gcc)实际上充满了信息:

error: no match for ternary 'operator?:' (operand types are 'bool', 'A', and 'B')

auto c = true ? A{} : B{};
~~~~~^~~~~~~~~~~
  • note: candidate: operator?:(bool, float, int) <built-in>
  • note: candidate: operator?:(bool, float, float) <built-in>