Since C++11, std::decay is introduced along with
<type_traits>. It is used to decay a type,
or to convert a type into its corresponding by-value
type. It will remove any top-level cv-qualifiers(const,
volatile) and reference qualifiers for the specified type.
For example, int& is turned into int and
an array type becomes a pointer to its element types. Knowing its usage,
we could try to implement our own version of
std::decay.
For std::decay<T>, the transformation of type
T contains following parts:
- Removing references
- Removing cv-qualifiers (
constandvolatile) - For an array type, yielding a pointer to its element type
- For a function type, yiedling its function pointer type
Removing References
Firstly, we implement RemoveReferenceT trait to remove
references:
1 | template <typename T> |
Results:
RemoveReference<int> // int |
The corresponding type trait in C++ STL is std::remove_reference
Removing cv-qualifiers
Then, RemoveConstT and RemoveVolatileT are
to remove const and volatile qualifiers,
respectively:
1 | template <typename T> |
1 | template <typename T> |
RemoveConstT and RemoveVolatileT can be
composed into RemoveCVT:
1 | // metafunction forwarding: inherit the Type member from RemoveConstT |
Results:
RemoveCV<int> // int |
The corresponding type traits in C++ STL: std::remove_cv, std::remove_const, std::remove_volatile
Note that
const volatile int*is not changed because the pointer itself is neither const or volatile. (See const and volatile pointers)
With RemoveReference and RemoveCVT traits
above, we can get a decay trait for nonarray and nonfunction cases:
1 | // remove reference firstly and then cv-qualifier |
We name our version DecayT in order not to confuse with
original std::decay.
Array-to-pointer Decay
Now we take array types into account. Below are partial specialisations to convert an array type into a pointer to its element type:
1 | // unbounded array |
Similarly, C++ STL provides std::is_array
to check whether T is an array type.
Function-to-pointer Decay
We want to recognise a function regardless of its return type and parameter types, and then get its function pointer. Because there are different number of parameters, we need to employ variadic templates:
1 | template <typename Ret, typename...Args> |
C++ STL also provides std::is_function to check the function type.
It is worth mentioning that many compilers nowadays use fundamental properties to check a function type for better performance instead1:
!std::is_const<const T>::value && !std::is_reference<T>::value
- Functions are not objects; thus,
constcannot be applied- When
const Tfails to be a const-qualified type,Tis either a function type or a reference type- We can rule out reference types to get only with function types for
T
Now, with alias template for convenience, we could get our own
version of decay trait, Decay:
1 | template <typename T> |
Results:
Decay<int&> // int |
In Comparison with
std::decay
In fact, C++ standard defines std::decay as:
| Template | Comments |
|---|---|
template <class T> struct decay; |
Let U be remove_reference_t<T>. If
is_array_v<U> is true, the member typedef type shall
equal remove_extent_t<U>*. If
is_function_v<U> is true, the member typedef type
shall equal add_pointer_t<U>. Otherwise the member
typedef type equals remove_cv_t<U>. [ Note: This
behavior is similar to the lvalue-to-rvalue, array-to-pointer, and
function-to-pointer 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 ] |
Most compilers directly follow the comments to implement the decay trait. Our own version in this article is basically a step-by-step implementation mentioned in the note for pedagogical purposes.