43
votes

The rules for picking which class template specialization is preferred involve rewriting the specializations into function templates and determining which function template is more specialized via the ordering rules for function templates [temp.class.order]. Consider this example, then:

#include <iostream>

template <class T> struct voider { using type = void; };
template <class T> using void_t = typename voider<T>::type;

template <class T, class U> struct A { };

template <class T> int foo(A<T, void_t<T>> ) { return 1; }
template <class T> int foo(A<T*, void> )     { return 2; }

int main() {
    std::cout << foo(A<int*, void>{});
}

Both gcc and clang print 2 here. This makes sense with some previous examples - deducing against a non-deduced context (void against void_t<T>) is just ignored, so deducing <T, void_t<T>> against <X*, void> succeeds but deducing <T*, void> against <Y, void_t<Y>> fails in both arguments. Fine.

Now consider this generalization:

#include <iostream>

template <class T> struct voider { using type = void; };
template <class T> using void_t = typename voider<T>::type;

template <int I> struct int_ { static constexpr int value = I; };

template <class T, class U> struct A      : int_<0> { };
template <class T> struct A<T, void_t<T>> : int_<1> { };
template <class T> struct A<T*, void>     : int_<2> { };

int main() {
    std::cout << A<int*, void>::value << '\n';
}

Both clang and gcc report this specialization as ambiguous, between 1 and 2. But why? The synthesized function templates aren't ambiguous. What's the difference between these two cases?

1
Paging @bogdan.Barry
Interesting, looks like a bug and VC++ also reports ambiguity. According to 14.5.5.2 those classes should be transformed into template <class T> void f(A<T, void_t<T>>) and template <class T> void f(A<T*, void>) and second should be picked just like in your first examplempiatek
I'm here :-). The first thing that comes to mind is that it's the same problem as the one I wrote about in this answer, but I'll give it some more thought in a bit.bogdan
@Bogdan The "unordered" issue with void_t is CWG 2173.T.C.
@MassimilianoJanes In your examples, you're pinning the synthesized types to concrete, well known types, so you're not modelling the general algorithm. If you want to add a relevant voider specialization to the last example in your answer, add template<> struct voider<T0_> { using type = U0_; };. This will change the result in your example, but not in the algorithm that the compiler actually uses for partial ordering. To test the compilers' algorithm, you could use an example like this one; I've included some explanations in code comments.bogdan

1 Answers

6
votes

Clang is being GCC-compatible (and compatible with existing code that depends on both of these behaviors).

Consider [temp.deduct.type]p1:

[...] an attempt is made to find template argument values (a type for a type parameter, a value for a non-type parameter, or a template for a template parameter) that will make P, after substitution of the deduced values (call it the deduced A), compatible with A.

The crux of the issue is what "compatible" means here.

When partially ordering function templates, Clang merely deduces in both directions; if deduction succeeds in one direction but not the other, it assumes that means the result will be "compatible", and uses that as the ordering result.

When partially ordering class template partial specializations, however, Clang interprets "compatible" as meaning "the same". Therefore it only considers one partial specialization to be more specialized than another if substituting the deduced arguments from one of them into the other would reproduce the original partial specialization.

Changing either of these two to match the other breaks substantial amounts of real code. :(