Search code examples
c++templatesaliasenable-ifc++17

Understanding Alias Templates


I asked a question that has several references to the code:

template <typename...>
using void_t = void;

I believe I have a generally misunderstand alias templates:

Why wouldn't you just evaluate whatever template parameter you're passing into an alias template in an enable_if_t or conditional_t statement?

Is the code above just about doing an enable_if_t on multiple template parameters at once?

Secondly, I believe that I have a specific misunderstanding of the role of void_t. This comment states that the C++17 standard defines void_t. Here's what I don't get:

Isn't void_t just an arbitrary name? If I still have to define template <typename...> using void_t = void; wherever I plan to use void_t what's the point of standardizing an arbitrary name?


Solution

  • I don't think the shown example really shows what void_t is good for as it only shows one use case, but when you look at

    template<typename T>
    struct has_to_string<T, 
        void_t<decltype(std::to_string(std::declval<T>()))>
        > 
    : std::true_type { };
    

    it is not so much different from

    template<typename T>
    struct has_to_string<T, 
        decltype(std::to_string(std::declval<T>()), void())
        > 
    : std::true_type { };
    

    And for this statement:

    The former version is much easier to read and void_t doesn't require decltype to work.

    I think the advantage in readability is quite small and the second part makes no sense, when decltype doesn't work, SFINAE kicks in as expected.

    One example where void_t is more useful is the one from the proposal:

    // primary template handles types that have no nested ::type member
    template< class, class = void_t<> >
    struct has_type_member
    : std::false_type { };
    
    // specialization recognizes types that do have a nested ::type member
    template< class T >
    struct has_type_member<T, void_t<typename T::type>>
    : std::true_type { }
    

    As you can see, even the primary template uses void_t to increase the readability as it now matches the specialization. That is not strictly necessary, but I like it. The real power comes when you think about the alternatives. Without void_t, the specialization is now more complicated:

    template< class T >
    struct has_type_member<T, decltype(typename T::type, void())>
    : std::true_type { }
    

    wouldn't work as T::type names a type, not an expression. You therefore need

    template< class T >
    struct has_type_member<T, decltype(std::declval<typename T::type>(), void())>
    : std::true_type { }
    

    The whole expression becomes longer, more tricky and it might suffer from edge-cases you forgot to handle. This is where void_t really helps, the other uses are then just a small improvement and they increase consistency.