This seems compliant with the C++ standard.
C++17 standard
From the C++17 draft standard, section 8.16:
- If either the second or the third operand has type
void
, [...]
- Otherwise, if the second and third operand are glvalue bit-fields of the same value category, [...]
Otherwise, if the second and third operand have different types and either has (possibly cv-qualified) class type, [...] an attempt is made to form an implicit conversion sequence (16.3.3.1) from each of those operands to the type of the other. [...] Attempts are made to form an implicit conversion sequence from an operand expression E1
of type T1
to a target type related to the type T2
of the operand expression E2
as follows
- If
E2
is an lvalue, the target type is “lvalue reference to T2
”, subject to the constraint that in the conversion the reference must bind directly to an lvalue
- If
E2
is an xvalue, [...]
- If
E2
is a prvalue or if neither of the conversion sequences above can be formed and at least one of the operands has (possibly cv-qualified) class type
- If
T1
and T2
are the same class type (ignoring cv-qualification), [...]
- otherwise, the target type is the type that
E2
would have after applying the lvalue-to-rvalue,array-to-pointer, and function-to-pointer standard conversions.
Using this process, it is determined whether an implicit conversion sequence can be formed from the second operand to the target type determined for the third operand, and vice versa. If both sequences can be formed, or one can be formed but it is the ambiguous conversion sequence, the program is ill-formed. If no conversion sequence can be formed, the operands are left unchanged and further checking is performed as described below. Otherwise, if exactly one conversion sequence can be formed, that conversion is applied to the chosen operand and the converted operand is used in place of the original operand for the remainder of this section.
- If the second and third operands are glvalues of the same value category and have the same type, [...]
- 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, [...]
Lvalue-to-rvalue (7.1), array-to-pointer (7.2), and function-to-pointer (7.3) standard conversions are performed on the second and third operands. After those conversions, one of the following shall hold:
- The second and third operands have the same type; [...]
- The second and third operands have arithmetic or enumeration type; the usual arithmetic conversions are performed to bring them to a common type, and the result is of that type
[...]
First case:
A a1;
auto c1 = b ? a1 : 0;
0
is an prvalue of type int
a1
is an lvalue of type A
Rule 4 applies:
- E1=
0
to E2=a1
: Attempt to apply rule 4.1. The target type is A&
. However there is no implicit conversion from int
to an lvalue A&
.
- E1=
a1
to E2=0
: rule 4.3.2 applies. The target type is int
. There is an implicit conversion, using A::operator unsigned long long()
.
Therefore following rule 4, the return type of this conditional expression is int
.
Second case:
unsigned long long a2 = 1ull << 32;
auto c2 = b ? a2 : 0;
0
is an prvalue of type int
a2
is an lvalue of type unsigned long long
Rule 7.2 applies: the return type of the conditional operator is determined by the arithmetic conversions rules of the two expressions. Here it is unsigned long long
.
About compiler warnings
Both clang (demo) and g++ (demo) will raise a warning with the option -Wconversion
. I've no experience of MSVC, but it also seems to have warnings for dangerous arithmetic conversions: C4242, C4365. Check that you have them enabled.