Multiple SFINAE class template specialisations using void_t

后端 未结 2 1406
-上瘾入骨i
-上瘾入骨i 2020-12-10 10:45

Are multiple class template specialisations valid, when each is distinct only between patterns involving template parameters in non-deduced contexts?

A common exampl

相关标签:
2条回答
  • 2020-12-10 11:30

    There is a rule that partial specializations have to be more specialized than the primary template - both of your specializations follow that rule. But there isn't a rule that states that partial specializations can never be ambiguous. It's more that - if instantiation leads to ambiguous specialization, the program is ill-formed. But that ambiguous instantiation has to happen first!

    It appears that clang is suffering from CWG 1558 here and is overly eager about substituting in void for std::void_t.

    This is CWG 1980 almost exactly:

    In an example like

    template<typename T, typename U> using X = T;
    template<typename T> X<void, typename T::type> f();
    template<typename T> X<void, typename T::other> f();
    

    it appears that the second declaration of f is a redeclaration of the first but distinguishable by SFINAE, i.e., equivalent but not functionally equivalent.

    If you use the non-alias implementation of void_t:

    template <class... Ts> struct make_void { using type = void; };
    template <class... Ts> using void_t = typename make_void<Ts...>::type;
    

    then clang allows the two different specializations. Sure, instantiating has_members on a type that has both type1 and type2 typedefs errors, but that's expected.

    0 讨论(0)
  • 2020-12-10 11:40

    I don't believe it's correct, or at least, not if we instantiate has_members with a type that has both type1 and type2 nested, the result would be two specializations that are

    has_members<T, void> 
    

    which would not be valid. Until the code is instantiated I think it's ok, but clang is rejecting it early. On g++, your fails with this use-case, once instantiated:

    struct X
    {
        using type1 = int;
        using type2 = double;
    };
    
    int main() {
        has_members<X>::value;
    }
    

    The error message is doesn't seem to describe the actual problem, but it at least is emitted:

    <source>:20:21: error: incomplete type 'has_members<X>' used in nested name specifier
         has_members<X>::value;
                         ^~~~~
    

    If you instantiate it with a type that has only type1 or type2 but not both, then g++ compiles it cleanly. So it's objecting to the fact that the members are both present, causing conflicting instantiations of the template.

    To get the disjunction, I think you'd want code like this:

    template <class, class = std::void_t<>>
    struct has_members : std::bool_constant<false> {};
    
    template <class T>
    struct has_members<T, std::enable_if_t<
            std::disjunction<has_member_type1<T>, has_member_type2<T>>::value>> : 
        std::bool_constant<true> {};
    

    This assumes you have traits to determine has_member_type1 and has_member_type2 already written.

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