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.
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.
The simplified rule is that you must be able to convert from one type to the other. From Derived
to const Base
is possible: derived-to-base followed by adding const. From const Derived
to Base
is not possible. You can't remove the const
.
Now it appears that in true ? X() : CY()
there's a common type that you can convert both sides to, i.e., const X
. But in general, that requires too much search effort for the compiler. It only checks two conversions, X to CY
and CY to X
. It does not try to solve T such that both X to T and CY to T exist
, let alone proving that there is only one such T
.
According to the [expr.cond#4] rules, the compiler is performing the following attempts, some can work while others can't:
using CY = const Y;
using CX = const X;
CX cx;
X x;
CY cy;
Y y;
true ? x : y;
// attempting:
// true ? x : static_cast<X&>(y);
true ? x : Y();
// attempting:
// true ? X(x) : X(static_cast<X&&>(Y()));
true ? X() : y;
// attempting:
// true ? X() : X(static_cast<X&&>(y));
true ? X() : Y();
// attempting:
// true ? X() : X(static_cast<X&&>(Y()));
true ? CX() : Y();
// attempting:
// true ? CX() : CX(static_cast<const X&>(Y()));
true ? CX() : CY();
// attempting:
// true ? CX() : CX(static_cast<const X&>(Y()));
true ? cx : y;
// attempting:
// true ? cx : static_cast<const X&>(y);
// true ? X() : CY();
// attempting:
// true ? X() : X(static_cast<X&&>(CY())); // removing const is not allowed
// true ? x : CY();
// attempting:
// true ? X(x) : X(static_cast<X&&>(CY())); // removing const is not allowed
// true ? x : cy;
// attempting:
// true ? x : static_cast<X&>(cy); // removing const is not allowed
X
fromX const&
. SinceY const&
converts toX const&
, you can slice an Y to create an X. – MSalters Commented Jan 31 at 13:59true ? CX() : CY();
? – Sneed Deens Commented Jan 31 at 14:03using Arr = int[3]; Arr{1,2,3} != nullptr;
, doesn't compile with "taking address of temporary array", this is true at least for gcc >= 12.2 and <= 13.3, fixed in gcc >=14.1. And its allowed by the standard and not a gcc extension. – Sneed Deens Commented Jan 31 at 14:13