Search code examples
c++compiler-errorsvariadic-templatestemplate-specialization

Clang fails to match variadic template template specialization


I have the following piece of code:

#include <iostream>
#include <variant>

template <typename U>
struct Match : std::false_type {};

template <template<typename> class C, typename... Args>
struct Match<C<Args...>> : std::true_type {};

int main() {
    using V = std::variant<int, double, float>;

    std::cout << Match<V>{} << '\n';

    return 0;
}

Clang seems to always print 0, while all other compilers I tried on Godbolt seem to print 1. However, if the specialization is changed to:

//             Change here vvv
template <template<typename...> class C, typename... Args>
struct Match<C<Args...>> : std::true_type {};

Then Clang also agrees and prints 1. Who is correct in the first case?


Solution

  • This has to do with P0522R0, which was adopted into C++17 but is disabled in Clang by default. You need to pass -frelaxed-template-template-args to make Clang accept it.

    Briefly, P0522R0 made it so that a variadic class template is a valid template argument in cases where the corresponding template parameter is non-variadic. For example:

    template <template <class> class NonVariadic>
    void foo();
    
    template <class...> class Variadic;
    
    int main() {
        foo<Variadic>();  // ill-formed before P0522R0
    }
    

    In the OP's code, in order to match the partial specialization, it must be that C (which is non-variadic) is deduced as std::variant (which is variadic).

    The reason why P0522R0 is turned off in Clang by default is that the paper did not update the specification of template partial ordering. For example:

    template <class> class X;
    
    template <template <class> class NonVariadic, class T>
    class X<NonVariadic<T>> {};
    
    template <template <class...> class Variadic, class T>
    class X<Variadic<T>> {};
    
    template <class...>
    class V;
    
    X<V<int>> x;
    

    The instantiation X<V<int>> is ambiguous. It can use the first partial specialization (with NonVariadic being deduced to V) or the second one (with Variadic being deduced to V). In GCC trunk, this compiles with -std=c++14 because prior to P0522R0, it was not permissible for V to be the argument for the parameter NonVariadic. In C++17 and later, it doesn't compile because of the ambiguity. In all language modes, Clang has P0522R0 off by default so that the code above can compile.

    One would expect that the first partial specialization would be considered more specialized than the second because the first partial specialization can't be used for something like X<V<int, int>>, while the second still can. But the current rules don't actually say that the first one is more specialized. This is CWG2398.

    There is an effort underway in Clang to implement P0522R0 and a possible resolution to CWG2398, making the above code unambiguous. If the effort is successful, they will presumably share their approach with CWG. Perhaps the GCC devs will also implement it then.