There are a lot of *_v
and *_t
suffixes, like std::is_same_v
, std::invoke_result_t
, result_of_t
and milions of other such functions.
Why do they exist at all? Is it beneficial in any context to expose implementation details like std::result_of::type
or std::is_same::value
? Ignoring standard compliance, should the _v
_t
versions always be preferred? Could the ::type
::value
versions have never existed at all?
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.
_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();
_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.
_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.
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.
You can find rationale and further explanation of _t
and _v
alias/variable templates in the following papers: