Search code examples
c++templateslanguage-lawyerpartial-specializationc++17

`template <auto>` and partial class template specialization ordering


Consider:

#include <type_traits>

template <typename>
struct Tag {};

template <typename T>
auto tag = Tag<T>{};


template <typename...>
struct SelectorImpl;

// 1
template <auto... xs>
struct SelectorImpl<std::integral_constant<decltype(xs), xs>...>
{};

// 2
template <typename T, Tag<T>* tag, auto... xs>
struct SelectorImpl<std::integral_constant<decltype(tag), tag>,
                    std::integral_constant<decltype(xs), xs>...>
{};


template <auto... params>
struct Selector
: SelectorImpl<std::integral_constant<decltype(params), params>...>
{};


int main() {
    Selector<&tag<int>, 1, 2>{};
}

Both gcc and clang fail to compile this, reporting that specializations of SelectorImpl are ambiguous. I believe that specialization #2 is more specialized. Am I wrong? Is it the same issue as here? Is it a bug?


Solution

  • First of all, I wanted to be sure that <char X> has precedence over <auto X>. So I wrote this simple test: https://godbolt.org/g/Hy6NVB

    template<auto...>
    struct TestSingleImpl;
    
    // 1
    template<auto x>
    struct TestSingleImpl<x> { static constexpr bool value = false; };
    
    // 2
    template<char x>
    struct TestSingleImpl<x> { static constexpr bool value = true; };
    
    static_assert(TestSingleImpl<1234>::value == false, "1");
    static_assert(TestSingleImpl<'a'>::value == true, "2");
    

    Result: yep, it has precedence (no ambiguity)

    Then I applied the same example with variadic args: https://godbolt.org/g/7mWaeH

    template <typename...>
    struct Test;
    
    // 1
    template<auto v, auto... x>
    struct Test<std::integral_constant<decltype(v), v>,
                std::integral_constant<decltype(x), x>...>
    {
        static constexpr bool value = true;
    };
    
    // 2
    template<char v, auto... x>
    struct Test<std::integral_constant<char, v>,
                std::integral_constant<decltype(x), x>...>
    {
        static constexpr bool value = false;
    };
    
    template <auto... params>
    struct Selector : Test<std::integral_constant<decltype(params), params>...> {};
    
    static_assert(Selector<1234, 1, 2>::value == true, "1"); // 1 - ok
    static_assert(Selector<'a', 1, 2>::value == false, "2"); // 2 - AMBIGUITY
    

    Looks like template<char v, auto... x> doesn't have precedence over template<auto... x>.

    this looks like a HUGE bug in both CLANG and GCC

    If we remove the std::integral_constant, it works properly: https://godbolt.org/g/grRC8h

    This confirms the bug :)

    template <auto...>
    struct Test;
    
    // 1
    template<auto v, auto... x>
    struct Test<v, x...>
    {
        static constexpr bool value = true;
    };
    
    // 2
    template<char v, auto... x>
    struct Test<v, x...>
    {
        static constexpr bool value = false;
    };
    
    static_assert(Test<1234, 1, 2>::value == true, "1"); // 1 - ok
    static_assert(Test<'a', 1, 2>::value == false, "2"); // 2 - ok