Title
common_type trait produces reference types
Status
c++14
Section
[meta.trans.other]
Submitter
Doug Gregor

Created on 2012-03-11.00:00:00 last changed 123 months ago

Messages

Date: 2013-09-29.10:09:46

Proposed resolution:

This wording is relative to N3485.

  1. Change [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;
    };
    
Date: 2013-09-15.00:00:00

[ 2013-09-29, Chicago ]

Accepted for the working paper

Date: 2013-04-15.00:00:00

[ 2013-04-18, Bristol ]

Move to Ready

Date: 2013-04-15.00:00:00

[ 2013-04-18, Bristol meeting ]

Previous wording:

This wording is relative to N3376.

  1. In [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;
    };
    
Date: 2013-03-15.00:00:00

[ 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.

Date: 2012-10-11.00:00:00

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

  1. Change [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;
    };
    
Date: 2012-10-11.00:00:00

[ 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.

Date: 2012-10-11.00:00:00

[ 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.

Date: 2012-03-15.22:41:19

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 ([expr.cond] p4)
  • decltype returns T&& when its expression is an xvalue ([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 [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
History
Date User Action Args
2014-02-20 13:20:35adminsetstatus: wp -> c++14
2013-09-29 10:09:46adminsetmessages: + msg6651
2013-09-29 10:09:46adminsetstatus: voting -> wp
2013-09-23 13:24:31adminsetstatus: ready -> voting
2013-04-18 22:58:13adminsetmessages: + msg6454
2013-04-18 22:58:13adminsetmessages: + msg6453
2013-04-18 22:58:13adminsetstatus: review -> ready
2013-03-18 14:33:00adminsetmessages: + msg6404
2013-03-18 13:02:36adminsetstatus: new -> review
2012-10-11 18:12:43adminsetmessages: + msg6167
2012-10-11 17:49:32adminsetmessages: + msg6166
2012-10-11 17:49:32adminsetmessages: + msg6165
2012-03-15 22:04:49adminsetmessages: + msg6061
2012-03-11 00:00:00admincreate