For some reason this doesn't compile:
#include <iostream>
struct X { };
struct Y : X { };
int main() {
using CY = const Y;
true ? X() : CY(); // error: different types 'X' and 'const Y'
}
Here's the rule from standard draft C++20, which I think should applied in this case:
7.6.16 Conditional operator [expr.cond]
...
(4.3.2) otherwise, if T2 is a base class of T1, the target type is cv1 T2, where cv1 denotes the cv-qualifiers of T1,
...
Where T2
would be X
and T1
- Y
, so target type, after also applying cv1, should be: const X
, so it should compile.
But I know that rule above only for determining that implicit conversion sequence could be formed and conversion itself maybe ill-formed (even though I can't think of any example of such case), but still maybe subtle point in this part:
7.6.16 Conditional operator [expr.cond]
...
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.
...
Will conclude with this:
using CX = const X;
true ? CX() : CY(); // compiles
true ? CX() : Y(); // even this compiles
true ? X() : CY(); // according to the rule becomes:
// true ? 'type X here' : 'type const X here'
// which should compile, but doesn't
Every comment there is true for any compiler, which makes me think I wrong somewhere. But another possibility is that every compiler has a bug, because ?: operator not that important, but idk.
The wording you quote originates from CWG2321, which seemingly aimed to make exactly this case well-formed.
Unfortunately, I'm not convinced the accepted resolution actually achieves that goal. If we work through the rest of [expr.cond] after establishing that the operand of the derived class type can be converted to the (adjusted) type of the other operand (but not vice versa), we reach [expr.cond]/4.6:
... 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 subclause.
This leaves us with prvalue operands of types "Base
" (unchanged) and "const Base
" (converted) - note the preserved cv-qualification of the converted operand.
Moving on, we arrive at [expr.cond]/6:
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. If the overload resolution fails, the program is ill-formed.
The difference in cv-qualification means the types are not the same, so this sentence applies. Overload resolution is performed with a set of built-in operator?:
candidates ([over.built]), none of which are viable in this case, which means the expression is (still) ill-formed.