While trying to implement a few things relying on variadic templates, I stumbled accross something I cannot explain. I boiled down the problem to the following code snippet:
template <typename ... Args>
struct A {};
template <template <typename...> class Z, typename T>
struct test;
template <template <typename...> class Z, typename T>
struct test<Z, Z<T>> {
static void foo() {
std::cout << "I'm more specialized than the variadic spec, hehe!" << std::endl;
}
};
template <template <typename...> class Z, typename T, typename ... Args>
struct test<Z, Z<T, Args...>> {
static void foo() {
std::cout << "I'm variadic!" << std::endl;
}
};
int main() {
test<A, A<int>>::foo();
}
Under gcc, it produces an error because it considers both specializations to be equally specialized when trying to instantiate test<A, A<int>>
:
main.cpp: In function 'int main()':
main.cpp:25:24: error: ambiguous template instantiation for 'struct test<A, A<int> >'
test<A, A<int>>::foo();
^~
main.cpp:11:12: note: candidates are: template<template<class ...> class Z, class T> struct test<Z, Z<T> > [with Z = A; T = int]
struct test<Z, Z<T>> {
^~~~~~~~~~~~~
main.cpp:18:12: note: template<template<class ...> class Z, class T, class ... Args> struct test<Z, Z<T, Args ...> > [with Z = A; T = int; Args = {}]
struct test<Z, Z<T, Args...>> {
However, clang deems the first specialization "more specialized" (through partial ordering: see next section) as it compiles fine and prints:
I'm more specialized than the variadic spec, hehe!
A live demo can be found on Coliru. I also tried using gcc's HEAD version and got the same errors.
My question here is: since these two well-known compilers behave differently, which one is right and is this piece of code correct C++?
Standard interpretation (C++14 current draft)
From the sections §14.5.5.1 and $14.5.5.2 of the C++14 standard draft, partial ordering is triggered to determine which specialization should be chosen:
(1.2) — If more than one matching specialization is found, the partial order rules (14.5.5.2) are used to determine whether one of the specializations is more specialized than the others. If none of the specializations is more specialized than all of the other matching specializations, then the use of the class template is ambiguous and the program is ill-formed.
Now according to §14.5.5.2, the class template specializations are transformed into function templates through this procedure:
For two class template partial specializations, the first is more specialized than the second if, given the following rewrite to two function templates, the first function template is more specialized than the second according to the ordering rules for function templates (14.5.6.2):
(1.1) — the first function template has the same template parameters as the first partial specialization and has a single function parameter whose type is a class template specialization with the template arguments of the first partial specialization, and
(1.2) — the second function template has the same template parameters as the second partial specialization and has a single function parameter whose type is a class template specialization with the template arguments of the second partial specialization.
Therefore, I tried to reproduce the issue with the function template overloads that the transformation described above should generate:
template <typename T>
void foo(T const&) {
std::cout << "Generic template\n";
}
template <template <typename ...> class Z, typename T>
void foo(Z<T> const&) {
std::cout << "Z<T>: most specialized overload for foo\n";
}
template <template <typename ...> class Z, typename T, typename ... Args>
void foo(Z<T, Args...> const&) {
std::cout << "Z<T, Args...>: variadic overload\n";
}
Now trying to use it like this:
template <typename ... Args>
struct A {};
int main() {
A<int> a;
foo(a);
}
yields a compilation error [ambiguous call] in both clang and gcc: live demo. I expected clang would at least have a behavior consistent with the class template case.
Then, this is my interpretation of the standard (which I seem to share with @Danh), so at this point we need a language-lawyer to confirm this.
Note: I browsed a bit LLVM's bug tracker and could not find a ticket for the behavior observed on function templates overloads in this question.