UPDATE: My concern discussed here has been answered on Stackoverflow.
What’s std::decay?
decay is a metafunction added to the <type_traits> header in C++11. A metafunction is a function (in the mathematical sense) that takes types as arguments rather than values. A simpler metafunction to describe is add_const which takes any arbitrary type and adds const to it. For example:
static_assert(std::is_same_v<std::add_const_t<int>, const int>);
static_assert(std::is_same_v<std::add_const_t<int *>, int * const>);
decay applies type transformations as if it were being passed as a function argument by value. For example:
static_assert(std::is_same_v<std::decay_t<void ()>, void (*)()>);
static_assert(std::is_same_v<std::decay_t<int [3]>, int *>);
static_assert(std::is_same_v<std::decay_t<const int>, int>);
Current Implementation
Here’s a possible implementation as given in by
cppreference.com.
This is also roughly the implementation used in both libstdc++ and libc++.
template< class T >
struct decay {
typedef typename std::remove_reference<T>::type U;
typedef typename std::conditional<
std::is_array<U>::value,
typename std::remove_extent<U>::type*,
typename std::conditional<
std::is_function<U>::value,
typename std::add_pointer<U>::type,
typename std::remove_cv<U>::type
>::type
>::type type;
};
Alternative Implementation
The idea here is to leverage the compiler which already defines the type
transformations necessary for pass-by-value semantics, rather than manually
writing them out for decay.
Here it is:
template <typename T>
struct decay {
template <typename U> static U impl(U);
using type = decltype(impl(std::declval<T>()));
};
The function impl takes an argument by value, so any argument of type T that
gets passed to it will go through the type transformations necessary for
pass-by-value sematics. Notice that this is exactly what we want for decay!
I pass an rvalue reference of T to impl, and the deduced type U is the
result we want for decay.
So What?
Not only is the implementation simpler this way, but it also follows the DRY (Don’t Repeat Yourself) principle. Suppose the definition of pass-by-value transformations were to change in the standard. In the current state, at least 4 modifications are required.
- Rules for template type deduction
- Definition of
decay - Implementation of template type deduction in the compiler
- Implementation of
decayin the standard library
If we were to piggy-back on the rules of template type deduction to define
decay instead, (1) and (3) still needs to be done but we get (2) and (4)
for free.
I also think it allows for stronger claims about what decay does. Here’s a
note about decay from N3797:
[ Note: This behavior is similar to the lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) conversions applied when an lvalue expression is used as an rvalue, but also strips cv-qualifiers from class types in order to more closely model by-value argument passing. — end note ]
What does “more closely model by-value argument passing” mean here? It sounds to me like it doesn’t exactly model by-value argument passing. I think leveraging the existing rules of template type deduction would allow us to claim that it exactly models by-value argument passing.
Am I off base here? Let me know what you think!
