Author: | Thorsten Ottosen |
---|---|
Contact: | tottosen@dezide.com |
organizations: | Dezide Aps |
Date: | 2006-09-08 |
Number: | WG21/N2069 J16/06-0139 |
Working Group: | Library |
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.
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.
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.
Many thanks to John Maddock for his feedback.