Yet another type-trait: decay

Author: Thorsten Ottosen
Contact: tottosen@dezide.com
organizations:Dezide Aps
Date: 2006-09-08
Number:WG21/N2069 J16/06-0139
Working Group:Library

Table of Contents

1   Introduction and Motivation

The latest draft of the C++ standard (n2009) includes a new section on type traits. The type traits provide a consistent and rich interface for compile-time type inspection and manipulation.

However, at least one very common trait is missing: decay. The purpose of decay is best seen in connection std::make_pair() which is defined as follows:

template <class T1, class T2> 
inline pair<T1,T2> make_pair(T1 x, T2 y)
{ 
    return pair<T1,T2>(x, y); 
}

The arguments are passed by value primarily to ensure string literals decay to const char* so as to make the following code work:

std::pair<std::string,int> p = std::make_pair("foo", 42);

If the arguments to make_pair() were passed by const reference, the above code would fail to compile because we suddenly create a temporary pair object of the type std::pair<const char[4],int> (and this instantiation fails because arrays are not constructible with the T() syntax, nor are they copy-constructible).

The problem with the current definition of std::make_pair() is that it leads to excessive copying of objects and hence great inefficiencies. With the advent of Rvalue References and perfect forwarding (see n2027) we can easily remove all these inefficiencies:

template <class T1, class T2> 
inline pair<T1,T2> make_pair(T1&& x, T2&& y)
{ 
    return pair<T1, T2>(std::forward<T1>(x), std::forward<T2>(y)); 
}

But alas, the above will still not compile and the reason is the same as before: we instantiate pair with an array type. However, with the proposed type-trait, the efficient version of std::make_pair() may be specified as follows:

template <class T1, class T2> 
inline pair< typename decay<T1>::type, typename decay<T2>::type > 
make_pair(T1&& x, T2&& y)
{ 
    return pair< typename decay<T1>::type, 
                 typename decay<T2>::type >(std::forward<T1>(x), 
                                            std::forward<T2>(y)); 
}

Simply put, decay<T>::type is the identity type-transformation except if T is an array type or a reference to a function type. In those cases the decay<T>::type yields a pointer or a pointer to a function, respectively.

I believe the reason n1856 does not provide an efficient version of std::make_pair() is because of the lack of decay.

The type-trait has been part of boost since 1.33.0, and is used in libraries such as Boost.Assign.

The implementation may be found here.

2   Wording

Extend the synopsis of 20.4.2 to include the following:

// [20.4.8] other transformations:
template <std::size_t Len, std::size_t Align> struct aligned_storage;
template <class T> struct decay;

Add the following row to Table 46 (Other transformations):

Template Condition Comments
template <class T> struct decay;   If is_array<T>::value is true, the member typedef type shall equal remove_extend<T>::type*. If is_function<T>::value is true, the member typedef type shall equal add_pointer<T>::type. Otherwise the member typedef type equals T.

Modify the synopsis of 20.2 to read:

template <class T1, class T2>
bool operator<=(const pair<T1,T2>&, const pair<T1,T2>&);
template<class T1, class T2> 
pair< typename decay<T1>::type, typename decay<T2>::type > make_pair(T1&&, T2&&);

Modify the specification of 20.2.2 to read:

template<class T1, class T2> 
pair< typename decay<T1>::type, typename decay<T2>::type > make_pair(T1&& x, T2&& y);

Returns: pair< typename decay<T1>::type, typename decay<T2>::type >( std::forward(x), std::forward(y) )

and remove the footnote 226.

3   Considerations for other parts of the library

The new tuple-library has a function called make_tuple() which is similar to make_pair(). However, make_tuple() already takes its arguments by const reference. In the near future I expect that all the TR1 components included the working draft needs to be made move-aware. When that time comes, it is suggested that make_tuple() should make use of decay in the same way as make_pair() does above.

4   Acknowledgement

Many thanks to John Maddock for his feedback.