common_type
is nearly impossible to specialize
correctly and regresses key functionalitySection: 21.3.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.
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.
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 typeT2
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]
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 membertype
. All types in the
parameter packT
shall be complete or (possibly cv)void
.
A program may specialize this trait for two cv-unqualified non-reference types
if at least onetemplate 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]…
Edit 21.3.8.7 [meta.trans.other] p3 (and its subbullets) as shown below
For the
common_type
trait applied to a parameter packT
of types, the membertype
shall be either defined or not present as follows:
If
sizeof...(T)
is zero, there shall be no membertype
.If
sizeof...(T)
is one, letT0
denote the sole type in the packT
. The member typedeftype
shall denote the same type asdecay_t<T0>
.If
sizeof...(T)
is two, letT1
andT2
, respectively, denote the first and second types comprisingT
, and letD1
andD2
, respectively, denotedecay_t<T1>
anddecay_t<T2>
.
If
is_same_v<T1, D1>
andis_same_v<T2, D2>
, and if there is no specializationcommon_type<T1, T2>
, letC
denote the type, if any, of an unevaluated conditional expression (7.6.16 [expr.cond]) whose first operand is an arbitrary value of typebool
, whose second operand is an xvalue of typeD1
, and whose third operand is an xvalue of typeD2
. If there is such a typeC
, the member typedeftype
shall denoteC
. Otherwise, there shall be no membertype
.If
not is_same_v<T1, D1>
ornot is_same_v<T2, D2>
, the member typedeftype
shall denote the same type, if any, ascommon_type_t<D1, D2>
. Otherwise, there shall be no membertype
.If
sizeof...(T)
is greater thanonetwo, letT1
,T2
, andR
, respectively, denote the first, second, and (pack of) remaining types comprisingT
.[Note:Letsizeof...(R)
may be zero. — end note] LetC
denote the type, if any, of an unevaluated conditional expression (7.6.16 [expr.cond]) whose first operand is an arbitrary value of typebool
, whose second operand is an xvalue of typeT1
, and whose third operand is an xvalue of typeT2
.C
denotecommon_type_t<T1, T2>
. If there is such a typeC
, the member typedeftype
shall denote the same type, if any, ascommon_type_t<C, R...>
. Otherwise, there shall be no membertype
.
[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]
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 membertype
. All types in the
parameter packT
shall be complete or (possibly cv)void
.
A program may specialize this trait for two cv-unqualified non-reference types
if at least onetemplate 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]…
Edit 21.3.8.7 [meta.trans.other] p3 (and its subbullets) as shown below
For the
common_type
trait applied to a parameter packT
of types, the membertype
shall be either defined or not present as follows:
(3.1) — If
sizeof...(T)
is zero, there shall be no membertype
.(3.2) — If
sizeof...(T)
is one, letT0
denote the sole type in the packT
. The member typedeftype
shall denote the same type asdecay_t<T0>
.(3.3) — If
sizeof...(T)
is two, letT1
andT2
, respectively, denote the first and second types comprisingT
, and letD1
andD2
, respectively, denotedecay_t<T1>
anddecay_t<T2>
.
(3.3.1) — If
is_same_v<T1, D1>
andis_same_v<T2, D2>
, letC
denote the type of an unevaluated conditional expression (7.6.16 [expr.cond]) whose first operand is an arbitrary value of typebool
, whose second operand is an xvalue of typeD1
, and whose third operand is an xvalue of typeD2
. [Note: This will not apply if there is a specializationcommon_type<D1, D2>
. — end note](3.3.2) — Otherwise, let
C
denote the typecommon_type_t<D1, D2>
.In either case, if there is such a type
C
, the member typedeftype
shall denoteC
. Otherwise, there shall be no membertype
.(3.4) — If
sizeof...(T)
is greater thanonetwo, letT1
,T2
, andR
, respectively, denote the first, second, and (pack of) remaining types comprisingT
.[Note:Letsizeof...(R)
may be zero. — end note] LetC
denote the type, if any, of an unevaluated conditional expression (7.6.16 [expr.cond]) whose first operand is an arbitrary value of typebool
, whose second operand is an xvalue of typeT1
, and whose third operand is an xvalue of typeT2
.C
denotecommon_type_t<T1, T2>
. If there is such a typeC
, the member typedeftype
shall denote the same type, if any, ascommon_type_t<C, R...>
. Otherwise, there shall be no membertype
.
[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]
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 typedeftype
shall be defined or omitted as specified in Note A, below.
If it is omitted, there shall be no membertype
. All types in the
parameter packT
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]…
Edit 21.3.8.7 [meta.trans.other] p3 (and its subbullets) as shown below
-3- Note A: For the
common_type
trait applied to a parameter packT
of types, the membertype
shall be either defined or not present as follows:
(3.1) — If
sizeof...(T)
is zero, there shall be no membertype
.(3.2) — If
sizeof...(T)
is one, letT0
denote the sole type in the packT
. The member typedeftype
shall denote the same type asdecay_t<T0>
.(3.3) — If
sizeof...(T)
is two, letT1
andT2
, respectively, denote the first and second types comprisingT
, and letD1
andD2
, respectively, denotedecay_t<T1>
anddecay_t<T2>
.
(3.3.1) — If
is_same_v<T1, D1>
andis_same_v<T2, D2>
, letC
denote the type of an unevaluated conditional expression (7.6.16 [expr.cond]) whose first operand is an arbitrary value of typebool
, whose second operand is an xvalue of typeD1
, and whose third operand is an xvalue of typeD2
. [Note: This will not apply if there is a specializationcommon_type<D1, D2>
. — end note](3.3.2) — Otherwise, let
C
denote the typecommon_type_t<D1, D2>
.In either case, if there is such a type
C
, the member typedeftype
shall denoteC
. Otherwise, there shall be no membertype
.(3.4) — If
sizeof...(T)
is greater thanonetwo, letT1
,T2
, andR
, respectively, denote the first, second, and (pack of) remaining types comprisingT
.[Note:Letsizeof...(R)
may be zero. — end note] LetC
denote the type, if any, of an unevaluated conditional expression (7.6.16 [expr.cond]) whose first operand is an arbitrary value of typebool
, whose second operand is an xvalue of typeT1
, and whose third operand is an xvalue of typeT2
.C
denotecommon_type_t<T1, T2>
. If there is such a typeC
, the member typedeftype
shall denote the same type, if any, ascommon_type_t<C, R...>
. Otherwise, there shall be no membertype
.-?- Note B: A program may specialize the
-4- [Example: Given these definitions: […]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 namedtype
, 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.