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 (
const
andvolatile
) - 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,
const
cannot be applied- When
const T
fails to be a const-qualified type,T
is 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.