tl; dr: your code is valid, but the compiler does not handle it correctly; even those that accept it (gcc and ICC (Intel C ++ compiler)) do it inconsistently or for the wrong reason.
The correct reasoning is as follows:
Conclusion: You are using the correct, legal syntax; but you will most likely need workarounds for inappropriate compilers. Even when your code compiles, I would recommend using static_assert to make sure the expected overload function or partial template specialization is selected.
Full analysis
Per [temp.class.order] , we partially order partial specializations of class templates, rewriting them to overloaded functional templates:
// rewrite corresponding to primary template<template<typename...> class TTP, typename... Ts> int f(SS<TTP, Ts...>) { return 0; } // #0 // rewrite corresponding to specialization template<template<typename> class OneParam, typename... Ts> int f(SS<OneParam, Ts...>) { return 1; } // #1 int ssoi = f(SS<One, int>{});
As expected, clang rejects this correspondence, claiming that the call f ambiguous, like MSVC; gcc inconsistently rejects this correspondence, even if it accepts a partial specialization of the source class template. ICC accepts this correspondence and initializes ssoi to 1 , which corresponds to OneParam specialization #1 .
Now we can determine which compiler is correct by following the rules for partial ordering of function templates ( [temp.func. Order] ). We can see that a call to f during ssoi initialization can call either #0 or #1 , therefore, to define a more specialized one, we should synthesize the template arguments and try to output type:
// #1 -> #0 template<template<typename...> class TTP, typename... Ts> int f0(SS<TTP, Ts...>); template<typename> class OneParam1; int ssoi0 = f0(SS<OneParam1>{}); // #0 -> #1 template<template<typename> class OneParam, typename... Ts> int f1(SS<OneParam, Ts...>); template<typename...> class TTP0; int ssoi1 = f1(SS<TTP0>{});
Note that for [temp.func.order] / 5 we do not synthesize the arguments corresponding to Ts...
The residue in #1 -> #0 succeeds ( TTP := OneParam1; Ts... := {} ), as expected ( #1 is a rewrite corresponding to the partial specialization of the class template, of which #0 is a rewrite).
The residue at #0 -> #1 succeeds ( OneParam := TTP0; Ts... := {} ) under gcc and MSVC. clang (inconsistently) and ICC reject f1 , stating that TTP0 cannot be displayed on OneParam (clang: "candidate template ignored: replacement failed: template template argument has different template parameters than its corresponding template template parameter").
So, we must first determine if a deduction #0 -> #1 is really possible. We can consider a simpler, equivalent case:
template<template<class> class P> class X { }; template<class...> class C { }; X<C> xc;
This is accepted by gcc and rejected by MSVC (inconsistent), clang and ICC. However, gcc correctly takes this for [temp.arg.template] / 3 .
Next, we must determine whether #1 more specialized than #0 , or whether they are ambiguous in terms of ordering. Per [temp.deduct.partial] / 10 consider the types in #0 and #1 in turn; per [temp.arg.template] / 4 we can rewrite OneParam and TTP to use templates at the same time:
template<typename...> class X1; template<typename> class X2; template<typename PP> int f(X1<PP>); // #2 template<typename... PP> int f(X1<PP...>); // #3 template<typename PP> int g(X2<PP>); // #4 template<typename... PP> int g(X2<PP...>); // #5
Now a partial order of the rewritten overloads f and g passing through [temp.deduct.partial] and [temp.deduct.type] to determine that tie-breaks for variators #1 more specialized than #0 .