c++ - conditional operator expression with base and const derived class doesn't compile, why? - Stack Overflow

admin2025-03-14  2

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.

Share Improve this question edited Jan 31 at 17:10 sweenish 5,2023 gold badges14 silver badges28 bronze badges asked Jan 31 at 13:13 Sneed DeensSneed Deens 998 bronze badges 15
  • I don't think you have implicit conversion from derived class to base class. Only from pointer to derived class to pointer to base class. (works also with references) – Oersted Commented Jan 31 at 13:22
  • C++ compilers don't have trivial bugs in areas like this. – MSalters Commented Jan 31 at 13:46
  • @Oersted: The point is that you can copy-initialize a new X from X const&. Since Y const& converts to X const&, you can slice an Y to create an X. – MSalters Commented Jan 31 at 13:59
  • 1 @Oersted, if so why this compiles true ? CX() : CY();? – Sneed Deens Commented Jan 31 at 14:03
  • @MSalters, to the point about bugs, I agree mostly, but here's comparable example: using 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
 |  Show 10 more comments

3 Answers 3

Reset to default 4

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:

With, X being a public base of Y, and:

using CY = const Y;
using CX = const X;
CX cx;
X x;
CY cy;
Y y;

OK cases:

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);

BAD cases:

// 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
转载请注明原文地址:http://conceptsofalgorithm.com/Algorithm/1741918576a187620.html

最新回复(0)