The _t
alias templates were introduced in C++14 and the _v
variable templates in C++17. There are many good reasons for why these exist.
The _t
and _v
templates are more convenient.
Firstly, trait_t<T>
is five characters shorter than trait<T>::type
. Furthermore, you would need to prefix the latter with typename
because the compiler cannot infer whether ::type
is a a type or a static member. See also Where and why do I have to put the "template" and "typename" keywords?.
This can make a big difference, comparing C++11/C++17 code:
// C++17
template <typename T>
std::enable_if_t<!std::is_void_v<T>> foo();
// C++11
template <typename T>
typename std::enable_if<!std::is_void<T>::value>::type foo();
The _t
and _v
templates offer more implementation freedom.
The fact that the traits are all classes is a significant limitation. It means that each use of e.g. std::is_same
will have to instantiate a new class template, and this is relatively costly. Modern compilers implement all type traits as intrinsics, similar to:
template <typename _A, typename _B>
struct is_same {
static constexpr bool value = __is_same(_A, _B);
};
template <typename _A, typename _B>
inline constexpr bool is_same_v = __is_same(_A, _B);
See __type_traits/is_same.h
in libc++.
The class is obviously redundant and it would be much more efficient to use the built-in function directly through a variable template.
Are trait classes pointless now that _t
and _v
exist?
The answer depends on the compiler.
A common argument in favor of the classes is that they allow short-circuiting.
For example, you can replace
(std::is_same_v<Ts, int> && ...)
// with
std::conjunction_v<std::is_same<Ts, int>...>
... and unlike the fold expression, not all std::is_same
will be instantiated.
However, at least for clang and GCC, the cost of instantiating std::is_same_v
is so trivially cheap (thanks to it being a built-in) that even though fold expressions don't short-circuit, it's still better for compilation speed to use them.
Click on image to go to benchmark results
However, older compilers might implement some traits using actual classes instead of built-ins, so it's theoretically possible that short-circuiting would be better.
Trait classes are sometimes more convenient for TMP.
Regardless of performance, the traits are sometimes useful for metaprogramming, such as:
template <typename T>
struct Select;
template <typename A, typename B>
struct Select<Pair<A, B>> : std::conditional<LeftIsBetter<A, B>, A, B> {};
// is more concise than
template <typename A, typename B>
struct Select<Pair<A, B>> {
using type = std::conditional_t<LeftIsBetter<A, B>, A, B>;
};
Inheriting from trait classes is convenient in some cases, although not strictly necessary.
See Also
You can find rationale and further explanation of _t
and _v
alias/variable templates in the following papers:
::type
and::value
are dependent types onT
. – Quittor