Search code examples
c++c++17c++14language-designtype-traits

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


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?


Solution

  • 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.

    benchmark results showing that the use of fold expressions is 1.2 times faster than std::conjunction/std::disjunction
    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: