Based on my understanding, all compilers are wrong, and the program should be ill-formed.
Related work
There is a submitted but not yet numbered CWG issue where @BrianBi explains this scenario, and also concludes that the wording implies ill-formedness.
On the other hand, @Barry has compiled these combinations of types in the conditional operator in P3177R0, and claims that const S
would be produced.
However, this data cannot be used to conclude anything because it was collected by simply taking GCC output, not by reviewing standard wording.
Language Lawyering
To determine the correct type in the end, consider the rules for determining the type of the conditional operator in [expr.cond].
Among other things, in ... ? f() : g()
, the compiler attempts to convert f()
to g()
and g()
to f()
, as described in [expr.cond] p4.
Step 1: Determining target types
For the purpose of the aforementioned conversions, the compiler determines target types for both implicit conversion sequences.
Note that:
f()
is of type const S&&
, which would turn into an xvalue of type const S
prior to any analysis ([expr.type] p1), and
g()
is a prvalue of type S
.
Conversion from f()
to a type related to S
This conversion takes place according to [expr.cond] p4.3:
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: [...]
g()
is a prvalue, so this is the relevant case.
Since const S
and S
are the same class type (ignoring cv-qualification), but S
is less cv-qualified, [expr.cond] p4.3.1 does not apply, but rather, lvalue-to-rvalue conversion is applied to E2, which is g()
([expr.cond] p4.3.3):
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.
Result: Target type is S
Note: E2
is g()
and none of the listed conversions are applicable, however, that should be fine. Since you obviously can't apply all three listed conversions, the intent of the wording is that any of these conversion are applied, if possible. In other words, the target type is simply E2
with nothing applied.
Conversion from g()
to a type related to const S
If E2 is an xvalue, the target type is “rvalue reference to T2”, but an implicit conversion sequence can only be formed if the reference would bind directly.
- [expr.cond] p4.2
All of the conditions here are satisfied, given that f()
is an xvalue, and a reference const S&&
can bind directly to a prvalue of type S
([dcl.init.ref] p5.3.1).
Result: Target type is "rvalue reference to const S
"
Step 2: Implicit conversions
Now, the compiler checks if implicit conversions can be performed with the given target types.
f()
can be converted to S
using the implicitly-defined (trivial) copy constructor which would be called as part of a user-defined conversion sequence, and
g()
can be converted to const S&&
by simply binding a reference.
We should now run into [expr.cond] p4, sentence 5:
If both sequences can be formed, or one can be formed but it is the ambiguous conversion sequence, the program is ill-formed.
The compiler should reject the code at this point, however, none do.
The GCC/clang perspective
If we kept going as if the program wasn't well-formed, we would run into [expr.cond] p6:
Otherwise, the result is a prvalue. [...] 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.
The result is a prvalue and we apply one final round of lvalue-to-rvalue conversion to make g()
(since it has been converted to an rvalue reference).
Apparently, GCC thinks that this prvalue should be of type const S
, but we should have never gotten to this point.
Since the result is a prvalue and decltype
is not being applied to an unparanthesized id-expression, decltype
should produce S
or const S
([dcl.type.decltype] p1.6).
The MSVC perspective
MSVC probably thinks that one of the two conversions failed, in which case the result can be const S&&
:
If the second and third operands are glvalues of the same value category and have the same type, the result is of that type and value category and it is a bit-field if the second or the third operand is a bit-field, or if both are bit-fields.
- [expr.cond] p5
However, it's unclear why MSVC thinks that f()
cannot be converted to S
, which would have resulted in a scenario where both operands are xvalues of type const S&&
now.
f
function returns not-const rvalue referenceS&& f()
, then all three compilers agree that the result of ternary operator has not-reference typeS
. So GCC/Clang are at least more consistent in your case, and MSVC changes its mind unexpectedly. – Moynahan