3205. decay_t in the new common_type fallback should be remove_cvref_t

Section: 21.3.8.7 [meta.trans.other] Status: New Submitter: Casey Carter Opened: 2019-05-12 Last modified: 2022-04-25

Priority: 3

View all other issues in [meta.trans.other].

View all issues with New status.

Discussion:

P0898R4 "The One Ranges Proposal" added a new fallback case to the definition of common_type in 21.3.8.7 [meta.trans.other], bullet 3.3.4:

Otherwise, if COND_RES(CREF(D1), CREF(D2)) denotes a type, let C denote the type decay_t<COND_RES(CREF(D1), CREF(D2))>.

Per para 3.3, D1 and D2 are decayed types. If both are void, bullet 3.3.4 is not reached. If either is an abominable function type or void, the COND_RES type expression above is ill-formed and bullet 3.3.4 does not apply. In all cases in which the COND_RES expression is well-formed, D1 and D2 denote cv-unqualified non-array object types. Given that fact, (1) CREF(D1) and CREF(D2) are equivalent to const D1& and const D2&, respectively, and (2) the COND_RES expression is equivalent to decltype(false ? declval<const D1&>() : declval<const D1&>()), i.e., the second and third operands of the conditional operator are lvalues of type const D1 and const D2, respectively.

[expr.cond]/3 cannot apply since the operands are not glvalue bit-fields.

If D1 and D2 are the same type, [expr.cond]/4 does not apply. If D1 and D2 are different types, there are a few cases to consider:

  1. If [expr.cond]/4.1 applies, one operand is converted into an lvalue reference to the type of the other, i.e., both resulting operands are lvalues of type either const D1 or const D2.

  2. [expr.cond]/4.2 cannot apply since neither operand is an xvalue.

  3. [expr.cond]/4.3.1 cannot apply since it would imply that the operands have the same type.

  4. If [expr.cond]/4.3.2 applies — if either D1 or D2 is a base class of the other — again the resulting operands are lvalues of type either const D1 or const D2.

  5. If [expr.cond]/4.3.3 applies, the either the const D1& operand converts to const D2 or the const D2& operand converts to const D1.

  6. If none of the sub-bullets in [expr.cond]/4 applies, the operands are left unchanged.

[expr.cond]/5 applies if the operands initially had the same type, or in cases 1 and 4 above. The conditional expression is an lvalue of type const D1 or const D2, and the COND_RES expression yields const D1& or const D2&.

Only cases 5 and 6 reach [expr.cond]/6. This paragraph performs overload resolution, which may result in converting both operands to the same non-class type to invoke a builtin conditional operator "overload".

[expr.cond]/7 applies standard conversions including array-to-pointer and function-to-pointer conversion to the operands. Consequently, the operands are once more "decayed" if [expr.cond]/6 converted them to an array or function type. Again case-by-case:

  1. [expr.cond]/7.1 applies if the operands now have the same type, which is the type of the conditional expression.

  2. [expr.cond]/7.2 applies if the operands have arithmetic or enumeration type; the conditional expression yields the result of applying the usual arithmetic conversions.

  3. [expr.cond]/7.3 applies if the operands have pointer type; the conditional expression yields their composite pointer type.

  4. [expr.cond]/7.4 applies if the operands have pointer-to-member type; the conditional expression applies some more standard conversions and yields their composite pointer type.

  5. [expr.cond]/7.5 applies if one operand has type nullptr_t and the other is either a null pointer constant or has type nullptr_t; the conditional expression yields nullptr_t.

In every case above, the conditional expression is either ill-formed, an lvalue of type const D1 or const D2, or a prvalue of a non-array non-function type. Consequently the COND_RES type expression always yields a non-array non-function type, for which decay_t and remove_cvref_t are equivalent. We can therefore replace COND_RES(CREF(D1), CREF(D2)) in [meta.trans.other]/3.3.4 with decltype(false ? declval<const D1&>() : declval<const D2&>()), and replace the usage of decay_t with remove_cvref_t.

Furthermore, there are now quite a few different cases describing the behavior of common_type. It's not clear that common_type<T...>::type is always a decayed type without in-depth analysis. We should non-normatively clarify that fact.

[2019-06-12 Priority set to 3 after reflector discussion]

[2020-05-01; Daniel adjusts wording to recent working draft]

[2022-04-25; Daniel adjusts wording to recent working draft]

Proposed resolution:

This wording is relative to N4910.

  1. Modify 21.3.8.7 [meta.trans.other] as indicated:

    -2- Let:

    1. (2.1) — CREF(A) be add_lvalue_reference_t<const remove_reference_t<A>>,

    2. (2.2) — […]

    3. […]

    4. (2.9) — […]

    If any of the types computed above is ill-formed, then COMMON-REF(A, B) is ill-formed.

    -3- Note A: For the common_type trait applied to a template parameter pack T of types, the member type shall be either defined or not present as follows:

    1. (3.1) — […]

    2. (3.2) — […]

    3. (3.3) — If sizeof...(T) is two, let the first and second types constituting T be denoted by T1 and T2, respectively, and let D1 and D2 denote the same types as decay_t<T1> and decay_t<T2>, respectively.

      1. (3.3.1) — […]

      2. (3.3.2) — […]

      3. (3.3.3) — Otherwise, if

        decay_t<decltype(false ? declval<D1>() : declval<D2>())>
        

        denotes a valid type, let C denote that type.

      4. (3.3.4) — Otherwise, if COND-RES(CREF(D1), CREF(D2))

        remove_cvref_t<decltype(false ? declval<const D1&>() : declval<const D2&>())>
        

        denotes a type, let C denote theat type decay_t<COND-RES(CREF(D1), CREF(D2))>.

    4. (3.4) — […]

    [Note: Whenever the qualified-id common_type<T...>::type is valid, it denotes the same type as decay_t<common_type<T...>::type>. — end note]

    -4- Note B: […]