TL;DR; clang
is right, since there are no possible conversions between A
and B
, overload resolution is used to determine the conversions to be applied to the operands, and the following (fictive) overloaded operator is selected:
int operator?:(bool, int, int);
There exists such (again, fictive) overload of the ?:
operator for any pair of arithmetic types (see references below).
Standard rules:
Since you cannot convert A
to B
or B
to A
, then the following applies:
[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.
This falls back to this:
[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].
In your case, there is a built-in candidate:
[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 ]
Extra details:
Since the ?:
operator cannot be overloaded, this means that your code only works if both types can be converted to an arithmetic type (e.g., int
). As a "counter"-example, the following code is 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'
Also note that you would get an ambiguous "call" if one of the type is convertible to two different arithmetic types, e.g., int
and float
:
struct A { operator int() const; };
struct B {
operator int() const;
operator float() const;
};
auto c = true ? A{} : B{};
The error (from gcc) is actually full of information:
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>