Search code examples
c++conditional-statementslanguage-lawyerconditional-operator

Why the result type of conditional expression is const reference?


There's a snippet of code:

int x = 0;
const int y = 0;
decltype(auto) z = true ? x : y;
static_assert(std::is_same_v<const int&, decltype(z)>);

According to [expr.cond], the second operand x is implicitly converted to const int&, and the third operand y is of type const int, which is different from const int&, so the program should be ill-formed.

Obviously I'm wrong, but I don't know what's wrong? Or, what is the specific process that determines the expression type as const int&?


Solution

  • This conditional expression is governed by [expr.cond]/4, which I quote in part:

    Otherwise, if the second and third operand have different types and either has (possibly cv-qualified) class type, or if both are glvalues of the same value category and the same type except for cv-qualification, an attempt is made to form an implicit conversion sequence 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”, but an implicit conversion sequence can only be formed if the reference would bind directly ([dcl.init.ref]) to a glvalue.
    • [...]

    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 subclause. [...]

    x and y are "glvalues of the same value category" (because they are both lvalues) and same type except for cv-qualification, so this bulleted list applies. Taking E1 to be x and E2 to be y, since y is an lvalue, the first bullet ([expr.cond]/4.1) applies: the target type to which to convert x is "lvalue reference to const int". Since that conversion is possible (and the reverse conversion isn't(1)), the conversion is applied.

    After this step, it is as if the conditional expression were true ? static_cast<const int&>(x) : y.

    Note that the second and third operand are now both lvalues of type const int. Expressions do not have reference type; an expression that appears to have an lvalue reference type is really just an lvalue of the referenced type (see [expr.type]/1).

    So then we go to [expr.cond]/5:

    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.

    The second and third operand are now glvalues of the same value category (lvalue) and same type (const int) so the result is an lvalue of const int and we are done.

    When you apply decltype to such an expression, it gives you the type and then slaps a & on top of it to tell you that the expression is an lvalue ([dcl.type.decltype]/1.5).


    (1) The reverse conversion from y to "lvalue reference to int" isn't possible because it would discard a const qualifier.