It was valid in C++14. In C++17, the changes in P0522R0 made this invalid; however, there is an open core issue, CWG2398, regarding similar examples that were broken by the P0522R0 changes. A resolution to CWG2398 would likely make this example well-formed again. Continue reading for more detail.
Partial ordering rules in general
The basic idea behind partial ordering of class template specializations goes all the way back to C++98. To determine whether one class template partial specialization is more specialized than another (or more specialized than the primary template), we transform each partial specialization (or primary template) into a similar function template. See [temp.spec.partial.order]. From
template<template<typename ...> class>
struct foo {}; // 1
template<template<typename> class C>
struct foo<C> {}; // 2
we obtain the following function templates:
template<template<typename ...> class TT>
void fooF(foo<TT>); // 1f
template<template<typename> class TT>
void fooF(foo<TT>); // 2f
If (2f) is more specialized than (1f), then (2) is more specialized than (1).
(Note that the rule requiring partial specializations to be more specialized than the primary template was introduced in C++14. However, it was approved as a DR so it should be applied retroactively to previous versions as well.)
The general principle behind partial ordering of function templates also goes all the way back to C++98. The idea (as explained in [temp.func.order]) is that if we generate an "arbitrary" specialization of 2f, and 1f "accepts" this arbitrary specialization, then 1f is "at least as general" as 2f, or to put it another way, 2f is "at least as specialized" as 1f. We also need to check whether 1f is "at least as specialized" as 2f. In order to say that 2f is more specialized than 1f, it must be the case that 2f is at least as specialized as 1f, but 1f is not at least as specialized as 2f.
The way to generate such an "arbitrary" specialization of 2f, as described in [temp.func.order]/3, is to replace each template parameter that appears in 2f with a completely unique argument that's compatible with that template parameter; one that is unrelated to any other entity in the program. In 2f, since TT
is a class template taking one type argument, we replace it with the following unique class template:
template <class> struct UniqueNV;
The transformed 2f is then:
void fooF(foo<UniqueNV>); // 2t
We then have to check whether template arguments can be deduced for (1f) in order to make (1f) identical to (2t). In other words, is there TT
such that void (foo<TT>)
is the same as void(foo<UniqueNV>)
, where TT
is a template <typename...> class
? Call this (Q1).
When we compare in the opposite direction, i.e., ask whether 1f is at least as specialized as 2f, the same principle applies. 1f is transformed:
template <class...> struct UniqueV;
void fooF(foo<UniqueV>); // 1t
and we try deduction against (2f): is there TT
such that void (foo<TT>)
is identical to void (foo<UniqueV>)
, considering that in this case TT
is a template <typename> class
, not a template <typename...> class
? Call this (Q2).
In order for the OP's example to be valid, the answer to (Q1) must be "yes", and the answer to (Q2) must be "no".
The answers to the above questions changed in C++17 due to P0522R0. It was already previously the case that a template <typename...> class
parameter could accept a template <typename> class
argument, which meant the answer to (Q1) was always "yes"; TT
is simply deduced to be UniqueNV
. After P0522R0, it is also the case that a template <typename> class
parameter can accept a template <typename...> class
argument. This makes the answer to (Q2) also "yes", which means that (2) is no longer considered more specialized than (1).
The reason why GCC nevertheless accepts it is that GCC never implemented the resolution to CWG1495; it still uses the old C++11 rules, where the partial specialization simply can't be identical to the primary template. However, if we change the example so that GCC is forced to compare two different partial specializations, we see that in C++17 mode and above, GCC no longer considers the specialization that takes a unary class template to be more specialized than the one that takes a variadic class template:
template <int, template <typename...> class>
struct foo {};
template <template <typename...> class C>
struct foo<0, C> {};
template <template <typename> class C>
struct foo<0, C> {};
template <class C> struct S;
int main() {
foo<0, S> f;
}
Godbolt link
Meanwhile, Clang has disabled the P0522 behaviour even in C++17 mode; you must turn it on using the -frelaxed-template-template-args
option. Clang accepts the code when the P0522 behaviour is turned off, and rejects when it is turned on. Godbolt link
Common sense says that (2) should indeed be more specialized than (1), but the isn't currently a rule in the C++ standard that makes it so. The problem is that when P0522 changed the rules as to when a template argument is compatible with a template parameter, it did not also include an accompanying change to the partial ordering rules that would keep (2) more specialized than (1). In the issue description for CWG2398, you can see some other examples that are now broken. If CWG2398 is eventually fixed, then (2) will likely be considered more specialized than (1) again. (Well, at least as long as I'm in CWG, I'll try to make sure they don't forget to account for this particular case of it.)