This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.
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: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.
[expr.cond]/4.2 cannot apply since neither operand is an xvalue.
[expr.cond]/4.3.1 cannot apply since it would imply that the operands have the same type.
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.
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.
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:[expr.cond]/7.1 applies if the operands now have the same type, which is the type of the conditional expression.
[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.
[expr.cond]/7.3 applies if the operands have pointer type; the conditional expression yields their composite pointer type.
[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.
[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.
Modify 21.3.8.7 [meta.trans.other] as indicated:
-2- Let:
(2.1) — CREF(A) be add_lvalue_reference_t<const remove_reference_t<A>>,(2.2) — […]
[…]
(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:
(3.1) — […]
(3.2) — […]
(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.
(3.3.1) — […]
(3.3.2) — […]
(3.3.3) — Otherwise, if
decay_t<decltype(false ? declval<D1>() : declval<D2>())>denotes a valid type, let C denote that type.
(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 th
eat typedecay_t<COND-RES(CREF(D1), CREF(D2))>.(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: […]