What is the purpose of _t aliases and _v variable templates for type traits?

What is the purpose of _t aliases and _v variable templates for type traits?


7

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?

1

  • 1

    They were added as more-readable aliases to the nested types. I personally don't find the nested types particularly hard to read in the first place. Though there are particular linters that will enforce this. As far as your last question, no I don't think that's quite true as at the end of the day both ::type and ::value are dependent types on T.

    – Cory Kramer

    yesterday

4 Answers
4


12

The issue is that when you have std::result_of<TEMPLATE_STUFF>::type, type is a dependent name and as such needs to be qualifed with typename in order to inform the compiler that type is the name of a type and not a object. The *_t names are aliases to typename trait_name::type so you don’t have to type typename yourself.

The typename is not needed for the *_vs since those are values, but it keeps a consistent syntax and is still less typing.


9

TL;DR – _t aliases can shorten metaprogramming code significantly due to omitting ::type and typename. _v variable templates were added later for symmetry with _t aliases and because they’re just better in every way.

C++17 – _v variable templates

Here’s a quote from proposal paper that introduced _v variable templates into C++17:

Variable templates like is_same_v<T, U> are superior to nested
constants like is_same<T, U>::value for several reasons:

  1. Obviously, the former is 5 characters shorter.

  2. Less obviously, the former eliminates "disconnected verbosity". When working with type traits, it’s common to have to say things like
    enable_if_t<!is_same<X, decay_t>::value, Z>, where "is_same" and
    "::value" are separated by a potentially substantial amount of
    machinery.

  3. Having to say "::value" isn’t a feature, it’s a workaround for a language limitation that has been solved. (Note that this proposal
    doesn’t touch the struct templates, so metaprogrammers who want the
    is_same type instead of its nested constant are unaffected.)

Also, important points from the adoption paper:

Following the success of the _t alias templates for type traits, a matching set of _v variable templates have been proposed. The language support for variable templates arrived too late to confidently make this change for C++14, but experience since has shown that such variable templates are more succinct, can clean up the text in a similar way that the _t aliases have been widely adopted through the standard, and the author’s experience using them in his own implementation of the standard type traits library is that code is much simpler when written using such variable templates directly, rather than turning a value into a type, then performing template manipulations on the type, before turning the type back into a value.
The impact on the standard is that many places that reference some_trait<T>::value would instead use some_trait_v<T>. The saving is not quite as great as in the case of alias templates, as there is no irksome typename to remove. However, the consistecy of using _t and _v to refer to traits, and not using ::something to extract meaning is compelling.

C++14 – _t aliases

Similar reasoning was provided in paper that introduced _t aliases in C++14, with the extra benefit of adding typename, which NathanOliver remarked in his answer. Quote from the paper:

Unfortunately, the above-described flexibility comes with a cost for the most common use cases.
In a template context, C++ requires that each “metacall” to a metafunction bear syntactic overhead
in the form of an introductory typename keyword, as well as the suffixed ::type:

typename metafunction-name<metafunction-argument(s)>::type

Even relatively straightforward compositions can rather quickly become somewhat messy; deeper
nesting is downright unwieldy:

template< class T > using reference_t
   = typename conditional<is_reference<T>::value, T,
                          typename add_lvalue_reference<T>::type>::type;

Worse, accidentally omitting the keyword can lead to diagnostics that are arcane to programmers
who are inexpert in metaprogramming details.

In our experience, passing metafunctions (rather than metadata) constitutes a relatively
small fraction of metafunction compositions. We find ourselves passing metafunction results
far more frequently. We therefore propose to add a set of template aliases for the library’s
TransformationTraits in order to reduce the programmer burden of expressing this far more
common case. Note, in the following rewrite of the above example, the absence of any typename
keyword, as well as the absence of any ::type suffix, thus condensing the statement from 3 to 2
lines of code:

template< class T > using reference_t
    = conditional_t< is_reference<T>::value, T, add_lvalue_reference_t<T> >;


6

Is it beneficial in any context to expose implementation details like std::result_of::type or std::is_same::value?

They are not implementation details. These are the original interfaces that are intended to be used.

The _t variants have to be implemented as alias templates which only exist since C++11 and the _v variants have to be implemented as variable templates which only exist since C++14.

So originally it simply wasn’t possible to define the interface without the indirection through ::type or ::value members. The short-hand versions have been added later and don’t do anything but safe some typing and making code easier to read.

The _t variants could have been added directly with C++11, but the type traits had already been considered for inclusion into C++11 very early on. Presumably alias templates were added to the draft later or the ::type interface was already familiar because type trait implementations have existed before C++11 as well.

If you are writing C++17 or later code that doesn’t need to be compatible with earlier C++ versions, there is no reason to not use the _t and _v variants instead of the old ::type/::value interfaces. If you need to support older C++ versions, then you should use ::type/::value accordingly, until up to C++11 and C++14 respectively.

Newly-added type traits still follow the same scheme for consistency.

3

  • 1

    Re: "Newly-added type traits still follow the same scheme for consistency" – it may be good to mention that some type traits depend on the base (non-_v) trait to exist, like std::conjunction/std::disjunction

    – Ted Lyngmo

    23 hours ago


  • 1

    there is no reason to not use the _t and _v variants instead of the old ::type/::value interface Overall, this is the answer I am looking for. But then,, I wonder, what is the point of std::invoke_result available from C++17 if std::invoke_result_t should always be preferred? Could std::invoke_result be removed?

    – KamilCuk

    8 hours ago


  • @KamilCuk, I guess you got the answer to the follow up question towards the bottom of the other answer.

    – Enlico

    7 hours ago


2

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 were necessary.

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.

Are the 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.

What is the purpose of _t aliases and _v variable templates for type traits?


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.

The 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 the classes is convenient in a some cases, although not strictly necessary.

See Also

You can find rationale and further explanation of _t and _v aliase/variable templates in the following papers:

1

  • note that the _t naming convention is actually encoded by POSIX which reserves all names ending in _t for typenames (which in C++ and with namespaces shouldn't interfere of course).

    – rubenvb

    7 hours ago



Leave a Reply

Your email address will not be published. Required fields are marked *