This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Resolved status.

### 2465. SFINAE-friendly `common_type` is nearly impossible to specialize correctly and regresses key functionality

Section: 20.15.8.7 [meta.trans.other] Status: Resolved Submitter: Eric Niebler Opened: 2015-01-12 Last modified: 2017-09-07

Priority: 2

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

View all issues with Resolved status.

Discussion:

I think there's a defect regarding `common_type` and its specializations. Unless I've missed it, there is nothing preventing folks from instantiating `common_type` with cv-qualified types or reference types. In fact, the wording in N3797 explicitly mentions cv `void`, so presumably at least cv qualifications are allowed.

Users are given license to specialize `common_type` when at least of of the types is user-defined. (A separate issue is the meaning of user-defined. In core, I believe this is any class/struct/union/enum, but in lib, I think it means any type not defined in std, right?) There is at least one place in the standard that specializes `common_type` (time.traits.specializations) on time_point and duration. But the specializations are only for non-cv-qualified and non-reference specializations of `time_point` and `duration`.

If the user uses, say, `common_type<duration<X,Y> const, duration<A,B> const>`, they're not going to get the behavior they expect.

Suggest we clarify the requirements of `common_type`'s template parameters. Also, perhaps we can add blanket wording that `common_type<A [cv][&], B [cv][&]>` is required to be equivalent to `common_type<A,B>` (if that is in fact the way we intent this to work).

Also, the change to make `common_type` SFINAE-friendly regressed key functionality, as noted by Agustín K-ballo Bergé in c++std-lib-37178. Since `decay_t` is not applied until the very end of the type computation, user specializations are very likely to to be found.

Agustín says:

> Consider the following snippet:

> >     struct X {};
> >     struct Y { explicit Y(X){} };

> >     namespace std {
> >       template<> struct common_type<X, Y> { typedef Y type; };
> >       template<> struct common_type<Y, X> { typedef Y type; };
> >     }

> >     static_assert(is_same<common_type_t<X, Y>, Y>()); // (A)
> >     static_assert(is_same<common_type_t<X, Y, Y>, Y>()); // (B)
> >     static_assert(is_same<common_type_t<X, X, Y>, Y>()); // (C)

> Under the original wording, all three assertion holds. Under the current wording,

> - (A) picks the user-defined specialization, so the assertion holds.

> - (B) goes to the third bullet and, ignoring the user-defined specialization, looks for `decltype(true ? declval<X>() : declval<Y>())`; since it is ill-formed there is no common type.

> - (C) goes to the third bullet and yields `common_type_t<X&&, Y>`, which again misses the user-defined specialization.

The discussion following c++std-lib-35636 seemed to cohere around the idea that the primary `common_type` specialization should have the effect of stripping top-level ref and cv qualifiers by applying `std::decay_t` to its arguments and, if any of them change as a result of that transformation, re-dispatching to `common_type` on those transformed arguments, thereby picking up any user-defined specializations. This change to `common_type` would make the specializations in time.traits.specializations sufficient.

Suggested wording:

I'm afraid I don't know enough to suggest wording. But for exposition, the following is my best shot at implementing the suggested resolution. I believe it also fixes the regression noted by Agustín K-ballo Bergé in c++std-lib-37178.

>     namespace detail
>     {
>         template<typename T, typename U>
>         using default_common_t =
>             decltype(true? std::declval<T>() : std::declval<U>());

>         template<typename T, typename U, typename Enable = void>
>         struct common_type_if
>         {};

>         template<typename T, typename U>
>         struct common_type_if<T, U,
>           void_t<default_common_t<T, U>>>
>         {
>           using type = decay_t<default_common_t<T, U>>;
>         };

>         template<typename T, typename U,
>            typename TT = decay_t<T>, typename UU = decay_t<U>>
>         struct common_type2
>           : common_type<TT, UU> // Recurse to catch user specializations
>         {};

>         template<typename T, typename U>
>         struct common_type2<T, U, T, U>
>           : common_type_if<T, U>
>         {};

>         template<typename Meta, typename Enable = void>
>         struct has_type
>           : std::false_type
>         {};

>         template<typename Meta>
>         struct has_type<Meta, void_t<typename Meta::type>>
>           : std::true_type
>         {};

>         template<typename Meta, typename...Ts>
>         struct common_type_recurse
>           : common_type<typename Meta::type, Ts...>
>         {};

>         template<typename Meta, typename...Ts>
>         struct common_type_recurse_if
>           : std::conditional<
>               has_type<Meta>::value,
>               common_type_recurse<Meta, Ts...>,
>               empty
>             >::type
>         {};
>     }

>     template<typename ...Ts>
>     struct common_type
>     {};

>     template<typename T>
>     struct common_type<T>
>     {
>       using type = std::decay_t<T>;
>     };

>     template<typename T, typename U>
>     struct common_type<T, U>
>       : detail::common_type2<T, U>
>     {};

>     template<typename T, typename U, typename... Vs>
>     struct common_type<T, U, Vs...>
>       : detail::common_type_recurse_if<common_type<T, U>, Vs...>
>     {};

[2016-08 Chicago]

Walter and Nevin provide wording.

Previous resolution [SUPERSEDED]:

> [This also resolves the first part of 2460]

> In Table 46 of N4604, entry for `common_type`:

> > ... may specialize this trait if at least one template parameter in the specialization is a user-defined type and no template parameter is cv-qualified.

> In [meta.trans.other] bullet 3.3:

> > ... whose second operand is an xvalue of type `T1` `decay_t<T1>`, and whose third operand is an xvalue of type `T2` `decay_t<T2>`. If ...

[2016-08-02, Chicago: Walt, Nevin, Rob, and Hal provide revised wording]

Previous resolution [SUPERSEDED]:

> This wording is relative to N4606.

> [This also resolves the first part of LWG 2460]

> 1. In Table 46 — "Other transformations" edit the entry for `common_type`:

>    > Table 46 — Other transformations
>    > Template | Comments
>    > --- | ---
>    > `…`
>    > `template <class... T> struct common_type;` | The member typedef `type` shall be defined or omitted as specified below. If it is omitted, there shall be no member `type`. All types in the parameter pack `T` shall be complete or (possibly cv) `void`. A program may specialize this trait for two cv-unqualified non-reference types if at least one template parameter in the specialization of them is a user-defined type. [Note: Such specializations are needed when only explicit conversions are desired among the template arguments. — end note]
>    > `…`

> 2. Edit 20.15.8.7 [meta.trans.other] p3 (and its subbullets) as shown below

>    > For the `common_type` trait applied to a parameter pack `T` of types, the member `type` shall be either defined or not present as follows:

>    > - If `sizeof...(T)` is zero, there shall be no member `type`.

>    > - If `sizeof...(T)` is one, let `T0` denote the sole type in the pack `T`. The member typedef `type` shall denote the same type as `decay_t<T0>`.

>    > - If `sizeof...(T)` is two, let `T1` and `T2`, respectively, denote the first and second types comprising `T`, and let `D1` and `D2`, respectively, denote `decay_t<T1>` and `decay_t<T2>`.

>    >   - If `is_same_v<T1, D1>` and `is_same_v<T2, D2>`, and if there is no specialization `common_type<T1, T2>`, let `C` denote the type, if any, of an unevaluated conditional expression (7.6.16 [expr.cond]) whose first operand is an arbitrary value of type `bool`, whose second operand is an xvalue of type `D1`, and whose third operand is an xvalue of type `D2`. If there is such a type `C`, the member typedef `type` shall denote `C`. Otherwise, there shall be no member `type`.

>    >   - If `not is_same_v<T1, D1>` or `not is_same_v<T2, D2>`, the member typedef `type` shall denote the same type, if any, as `common_type_t<D1, D2>`. Otherwise, there shall be no member `type`.

>    > - If `sizeof...(T)` is greater than one two, let `T1`, `T2`, and `R`, respectively, denote the first, second, and (pack of) remaining types comprising `T`. [Note: `sizeof...(R)` may be zero. — end note] Let `C` denote the type, if any, of an unevaluated conditional expression (7.6.16 [expr.cond]) whose first operand is an arbitrary value of type `bool`, whose second operand is an xvalue of type `T1`, and whose third operand is an xvalue of type `T2`. Let `C` denote `common_type_t<T1, T2>`. If there is such a type `C`, the member typedef `type` shall denote the same type, if any, as `common_type_t<C, R...>`. Otherwise, there shall be no member `type`.

[2016-08-03 Chicago LWG]

LWG asks for minor wording tweaks and for an added Note. Walter revises the Proposed Resolution accordingly.

Previous resolution [SUPERSEDED]:

> This wording is relative to N4606.

> [This also resolves the first part of LWG 2460]

> 1. In Table 46 — "Other transformations" edit the entry for `common_type`:

>    > Table 46 — Other transformations
>    > Template | Comments
>    > --- | ---
>    > `…`
>    > `template <class... T> struct common_type;` | The member typedef `type` shall be defined or omitted as specified below. If it is omitted, there shall be no member `type`. All types in the parameter pack `T` shall be complete or (possibly cv) `void`. A program may specialize this trait for two cv-unqualified non-reference types if at least one template parameter in the specialization of them is a user-defined type. [Note: Such specializations are needed when only explicit conversions are desired among the template arguments. — end note]
>    > `…`

> 2. Edit 20.15.8.7 [meta.trans.other] p3 (and its subbullets) as shown below

>    > For the `common_type` trait applied to a parameter pack `T` of types, the member `type` shall be either defined or not present as follows:

>    > 1. (3.1) — If `sizeof...(T)` is zero, there shall be no member `type`.

>    > 2. (3.2) — If `sizeof...(T)` is one, let `T0` denote the sole type in the pack `T`. The member typedef `type` shall denote the same type as `decay_t<T0>`.

>    > 3. (3.3) — If `sizeof...(T)` is two, let `T1` and `T2`, respectively, denote the first and second types comprising `T`, and let `D1` and `D2`, respectively, denote `decay_t<T1>` and `decay_t<T2>`.

>    >    1. (3.3.1) — If `is_same_v<T1, D1>` and `is_same_v<T2, D2>`, let `C` denote the type of an unevaluated conditional expression (7.6.16 [expr.cond]) whose first operand is an arbitrary value of type `bool`, whose second operand is an xvalue of type `D1`, and whose third operand is an xvalue of type `D2`. [Note: This will not apply if there is a specialization `common_type<D1, D2>`. — end note]

>    >    2. (3.3.2) — Otherwise, let `C` denote the type `common_type_t<D1, D2>`.

>    >    In either case, if there is such a type `C`, the member typedef `type` shall denote `C`. Otherwise, there shall be no member `type`.

>    > 4. (3.4) — If `sizeof...(T)` is greater than one two, let `T1`, `T2`, and `R`, respectively, denote the first, second, and (pack of) remaining types comprising `T`. [Note: `sizeof...(R)` may be zero. — end note] Let `C` denote the type, if any, of an unevaluated conditional expression (7.6.16 [expr.cond]) whose first operand is an arbitrary value of type `bool`, whose second operand is an xvalue of type `T1`, and whose third operand is an xvalue of type `T2`. Let `C` denote `common_type_t<T1, T2>`. If there is such a type `C`, the member typedef `type` shall denote the same type, if any, as `common_type_t<C, R...>`. Otherwise, there shall be no member `type`.

[2016-08-04 Chicago LWG]

Alisdair notes that 16.4.5.2.1 [namespace.std] p.1 seems to prohibit some kinds of specializations that we want to permit here and asks that the Table entry be augmented so as to specify the precise rules that a specialization is required to obey. Walter revises Proposed Resolution accordingly.

[2016-08-03 Chicago]

Fri PM: Move to Tentatively Ready

[2016-08-11 Daniel comments]

LWG 2763 presumably provides a superiour resolution that also fixes another bug in the Standard.

[2016-08-12]

Howard request to reopen this issue because of the problem pointed out by LWG 2763.

[2016-08-13 Tim Song comments]

In addition to the issue pointed out in LWG 2763, the current P/R no longer decays the type of the conditional expression. However, that seems harmless since 7 [expr]/5 means that the "type of an expression" is never a reference type, and 7.6.16 [expr.cond]'s rules appear to ensure that the type of the conditional expression will never be "decay-able" when fed with two xvalues of cv-unqualified non-array object type. Nonetheless, a note along the lines of "[Note: `C` is never a reference, function, array, or cv-qualified type. — end note]" may be appropriate, similar to the note at the end of [dcl.decomp]/1.

[2016-11-12, Issaquah]

Resolved by P0435R1

Proposed resolution:

This wording is relative to N4606.

[This also resolves the first part of LWG 2460]

1. In Table 46 — "Other transformations" edit the entry for `common_type`:

   > Table 46 — Other transformations
   > Template | Comments
   > --- | ---
   > `…`
   > `template <class... T> struct common_type;` | Unless this trait is specialized (as specified in Note B, below), t The member typedef `type` shall be defined or omitted as specified in Note A, below. If it is omitted, there shall be no member `type`. All types in the parameter pack `T` shall be complete or (possibly cv) `void`. A program may specialize this trait if at least one template parameter in the specialization is a user-defined type. [Note: Such specializations are needed when only explicit conversions are desired among the template arguments. — end note]
   > `…`

2. Edit 20.15.8.7 [meta.trans.other] p3 (and its subbullets) as shown below

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

   > 1. (3.1) — If `sizeof...(T)` is zero, there shall be no member `type`.

   > 2. (3.2) — If `sizeof...(T)` is one, let `T0` denote the sole type in the pack `T`. The member typedef `type` shall denote the same type as `decay_t<T0>`.

   > 3. (3.3) — If `sizeof...(T)` is two, let `T1` and `T2`, respectively, denote the first and second types comprising `T`, and let `D1` and `D2`, respectively, denote `decay_t<T1>` and `decay_t<T2>`.

   >    1. (3.3.1) — If `is_same_v<T1, D1>` and `is_same_v<T2, D2>`, let `C` denote the type of an unevaluated conditional expression (7.6.16 [expr.cond]) whose first operand is an arbitrary value of type `bool`, whose second operand is an xvalue of type `D1`, and whose third operand is an xvalue of type `D2`. [Note: This will not apply if there is a specialization `common_type<D1, D2>`. — end note]

   >    2. (3.3.2) — Otherwise, let `C` denote the type `common_type_t<D1, D2>`.

   >    In either case, if there is such a type `C`, the member typedef `type` shall denote `C`. Otherwise, there shall be no member `type`.

   > 4. (3.4) — If `sizeof...(T)` is greater than one two, let `T1`, `T2`, and `R`, respectively, denote the first, second, and (pack of) remaining types comprising `T`. [Note: `sizeof...(R)` may be zero. — end note] Let `C` denote the type, if any, of an unevaluated conditional expression (7.6.16 [expr.cond]) whose first operand is an arbitrary value of type `bool`, whose second operand is an xvalue of type `T1`, and whose third operand is an xvalue of type `T2`. Let `C` denote `common_type_t<T1, T2>`. If there is such a type `C`, the member typedef `type` shall denote the same type, if any, as `common_type_t<C, R...>`. Otherwise, there shall be no member `type`.

   > -?- Note B: A program may specialize the `common_type` trait for two cv-unqualified non-reference types if at least one of them is a user-defined type. [Note: Such specializations are needed when only explicit conversions are desired among the template arguments. — end note] Such a specialization need not have a member named `type`, but if it does, that member shall be a typedef-name for a cv-unqualified non-reference type that need not otherwise meet the specification set forth in Note A, above.

   > -4- [Example: Given these definitions: […]