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

### 2141. `common_type` trait produces reference types

Section: 21.3.8.7 [meta.trans.other] Status: C++14 Submitter: Doug Gregor Opened: 2012-03-11 Last modified: 2016-01-28

Priority: Not Prioritized

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

View all issues with C++14 status.

Discussion:

The type computation of the `common_type` type trait is defined as

>     template <class T, class U>
>      struct common_type<T, U> {
>        typedef decltype(true ? declval<T>() : declval<U>()) type;
>      };

This means that `common_type<int, int>::type` is `int&&`, because

- `declval<int>()` returns `int&&`
- The conditional operator returns an xvalue when its second and third operands have the same type and are both xvalues (7.6.16 [expr.cond] p4)
- `decltype` returns `T&&` when its expression is an xvalue (9.2.9.3 [dcl.type.simple] p4)

Users of `common_type` do not expect to get a reference type as the result; the expectation is that `common_type` will return a non-reference type to which all of the types can be converted.

Daniel: In addition to that it should be noted that without such a fix the definition of `std::unique_ptr`'s `operator<` in 20.3.1.6 [unique.ptr.special] (around p4) is also broken: In the most typical case (with default deleter), the determination of the common pointer type CT will instantiate `std::less<CT>` which can now be `std::less<T*&&>`, which will not be the specialization of pointer types that guarantess a total order.

Given the historic constext of `common_type` original specification, the proper resolution to me seems to be using `std::decay` instead of `std::remove_reference`:

>     template <class T, class U>
>     struct common_type<T, U> {
>       typedef typename decay<decltype(true ? declval<T>() : declval<U>())>::type type;
>     };

At that time rvalues had no identity in this construct and rvalues of non-class types have no cv-qualification. With this change we would ensure that

>     common_type<int, int>::type == common_type<const int, const int>::type == int

Note that this harmonizes with the corresponding heterogenous case, which has already the exact same effect:

>     common_type<int, long>::type == common_type<const int, const long>::type == long

[2012-10-11 Daniel comments]

While testing the effects of applying the proposed resolution I noticed that this will have the effect that the unary form of `common_type`, like

>     common_type<int>

is not symmetric to the n-ary form (n > 1). This is unfortunate, because this difference comes especially to effect when `common_type` is used with variadic templates. As an example consider the following `make_array` template:

>     #include <array>
>     #include <type_traits>
>     #include <utility>

>     template<class... Args>
>     std::array<typename std::common_type<Args...>::type, sizeof...(Args)>
>     make_array(Args&&... args)
>     {
>       typedef typename std::common_type<Args...>::type CT;
>       return std::array<CT, sizeof...(Args)>{static_cast<CT>(std::forward<Args>(args))...};
>     }

>     int main()
>     {
>       auto a1 = make_array(0); // OK: std::array<int, 1>
>       auto a2 = make_array(0, 1.2); // OK: std::array<double, 2>
>       auto a3 = make_array(5, true, 3.1415f, 'c'); // OK: std::array<float, 4>

>       int i = 0;
>       auto a1b = make_array(i); // Error, attempt to form std::array<int&, 1>

>       auto a2b = make_array(i, 1.2); // OK: std::array<double, 2>
>       auto a2c = make_array(i, 0); // OK: std::array<int, 2>
>     }

The error for `a1b` only happens in the unary case and it is easy that it remains unnoticed during tests. You cannot explain that reasonably to the user here.

Of-course it is possible to fix that in this example by applying `std::decay` to the result of the `std::common_type` deduction. But if this is necessary here, I wonder why it should also be applied to the binary case, where it gives the wrong illusion of a complete type decay? The other way around: Why is `std::decay` not also applied to the unary case as well?

This problem is not completely new and was already observed for the original `std::common_type` specification. At this time the `decltype` rules had a similar asymmetric effect when comparing

> `std::common_type<const int, const int>::type` (equal to '`int`' at this time)

with:

> `std::common_type<const int>::type` (equal to '`const int`')

and I wondered whether the unary form shouldn't also perform the same "decay" as the n-ary form.

This problem makes me think that the current resolution proposal might not be ideal and I expect differences in implementations (for those who consider to apply this proposed resolution already). I see at least three reasonable options:

1. Accept the current wording suggestion for LWG 2141 as it is and explain that to users.

2. Keep `std::common_type` as currently specified in the Standard and tell users to use `std::decay` where needed. Also fix other places in the library, e.g. the comparison functions of `std::unique_ptr` or a most of the time library functions.

3. Apply `std::decay` also in the unary specialization of `std::common_type` with the effect that `std::common_type<const int&>::type` returns `int`.

[2012-10-11 Marc Glisse comments]

If we are going with decay everywhere, I wonder whether we should also decay in the 2-argument version before and not only after. So if I specialize `common_type<mytype, double>`, `common_type<const mytype, volatile double&>` would automatically work.

[2012-10-11 Daniel provides wording for bullet 3 of his list:]

1. Change 21.3.8.7 [meta.trans.other] p3 as indicated:

   >     template <class T>
   >     struct common_type<T> {
   >       typedef typename decay<T>::type type;
   >     };

   >     template <class T, class U>
   >     struct common_type<T, U> {
   >       typedef typename decay<decltype(true ? declval<T>() : declval<U>())>::type type;
   >     };

[2013-03-15 Issues Teleconference]

Moved to Review.

Want to carefully consider the effect of `decay` vs. `remove_reference` with respect to constness before adopting, although this proposed resolution stands for review in Bristol.

[2013-04-18, Bristol meeting]

Previous wording:

> This wording is relative to N3376.

> 1. In 21.3.8.7 [meta.trans.other] p3, change the `common_type` definition to

>    >     template <class T, class U>
>    >     struct common_type<T, U> {
>    >       typedef typename decay<decltype(true ? declval<T>() : declval<U>())>::type type;
>    >     };

[2013-04-18, Bristol]

Move to Ready

[2013-09-29, Chicago]

Accepted for the working paper

Proposed resolution:

This wording is relative to N3485.

1. Change 21.3.8.7 [meta.trans.other] p3 as indicated:

   >     template <class T>
   >     struct common_type<T> {
   >       typedef typename decay<T>::type type;
   >     };

   >     template <class T, class U>
   >     struct common_type<T, U> {
   >       typedef typename decay<decltype(true ? declval<T>() : declval<U>())>::type type;
   >     };