Understanding Alias Templates

后端 未结 2 1614
礼貌的吻别
礼貌的吻别 2021-01-19 13:00

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

template 
using void_t = void;

I believe I have a generally

相关标签:
2条回答
  • 2021-01-19 13:08

    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.

    0 讨论(0)
  • 2021-01-19 13:14

    In Barry's example from your linked question:

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

    void_t is just used to translate the type deduced by decltype to void so that it matches the default argument to the primary template definition. The SFINAE is all taken care of by the decltype expression. You could just as easily do the following:

    //use , void() instead of wrapping in void_t
    //this uses the comma operator to check the type of the to_string call, then change the type to void
    decltype(std::to_string(std::declval<T>()), void())
    

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

    If void_t is available in your implementation you don't need to redefine it. When it's standardised it will be available just like any of the other alias templates in the standard.

    Think about it this way: if T is int, which has a valid std::to_string overload, deduction will look like this:

    has_to_string<int> -> has_to_string<int,void> because of the default argument. So lets look for specializations of has_to_string with those arguments.

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

    Okay, that is a partial specialization for some T and some dependent type. Let's work out that type:

    void_t<decltype(std::to_string(std::declval<T>()))>
    //std::to_string(int&&) is valid and returns a std::string
    void_t<std::string>
    //void_t changes types to void
    void
    

    Now our specialization looks like this:

    template<>
    struct has_to_string<int,void>
    : std::true_type { };
    

    This matches our instantiation of has_string<int,void>, so has_to_string<int> inherits from std::true_type.

    Now think about it when T is struct Foo{};. Again, let's work out that dependent type:

    void_t<decltype(std::to_string(std::declval<T>()))>
    //wait, std::to_string(Foo&&) doesn't exist
    //discard that specialization
    

    With that specialization discarded, we fall back to the primary template:

    template<typename T, typename = void>
    struct has_to_string
    : std::false_type { };
    

    So has_to_string<Foo> inherits from std::false_type.

    0 讨论(0)
提交回复
热议问题