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

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

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,

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 T1decay_t<T1>, and whose third operand is an xvalue of type T2decay_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 specializationof 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 23.15.7.6 [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 (8.5.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 onetwo, 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 (8.5.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 specializationof 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 23.15.7.6 [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 (8.5.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 onetwo, 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 (8.5.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 20.5.4.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 8 [expr]/5 means that the "type of an expression" is never a reference type, and 8.5.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), tThe
    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 23.15.7.6 [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 (8.5.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 onetwo, 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 (8.5.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: […]