What is the syntax for partially specializing a template based on the number of parameters that the template takes?

Consider the following code:

template<typename> struct One {}; template<typename, typename> struct Two {}; template<template<typename...> class TTP, typename...> struct SS; #ifdef TEST_TTP template<template<typename> class OneParam, typename... Ts> struct SS<OneParam, Ts...> {}; template<template<typename, typename> class TwoParam, typename... Ts> struct SS<TwoParam, Ts...> {}; #else // TEST_TTP template<template<typename> class OneParam, typename TParam> struct SS<OneParam, TParam> {}; template<template<typename, typename> class TwoParam, typename TParam1, typename TParam2> struct SS<TwoParam, TParam1, TParam2> {}; #endif // TEST_TTP int main() { SS<One, int> ssoi; SS<Two, int, int> sstii; } 

This code will compile correctly on Clang, GCC, and MSVC if TEST_TTP not defined. However, if defined ...

  • The code compiles correctly in GCC, indicating that it recognizes that OneParam and TwoParam are different from TTP in the main template.
  • Clang cannot recognize that OneParam specializes in TTP , causing it to emit two errors (the first of which is that partial specialization does not specialize in any template parameters, and the second is that OneParam conflicts with the previously-sized template template). Then it gives similar errors for TwoParam (the first is identical, and the second says that the template template parameter has too many parameters) and an error for each SS instance (since it considers the template to be undefined), for a total of 6 errors.
  • MSVC generates similar errors for Clang, but more briefly: it emits C3855 ( OneParam incompatible with the main template), and C2079 (the variable uses the undefined type) for each SS instance, for only 3 errors.

Showcased live on Coliru .


From my testing:

GCC allows a template with a template template parameter, which requires the package of variational parameters to be partially specialized, based solely on the number of parameters that the template template parameter accepts. Clang and MSVC do not.

 template<template<typename...> class T> struct S; template<template<typename> class T> struct S<T> {}; // Only works with GCC. template<template<typename, typename> class T> struct S<T> {}; // Only works with GCC. 

Clang and MSVC do a great job if the other options are also specialized.

 template<template<typename...> class T, typename... Ts> struct S; template<template<typename> class T, typename TParam> struct S<T, TParam> {}; template<template<typename, typename> class T, typename TParam1, typename TParam2> struct S<T, TParam1, TParam2> {}; 

Thus, it would be obvious that either the first is not legal C ++ or Clang and MSVC are not properly supported. So the question is:

Given this, what is the correct, legal syntax for partial specialization of a template that contains a template template parameter, based on the number of parameters that the template template accepts? If there is no legal syntax for this, does it support the GCC extension and / or error?

If a complete record of my tests along with the original example that raised this question is desired, see the change history.

+8
c ++ visual-c ++ g ++ clang ++ template-templates
source share
1 answer

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 .

+1
source share

All Articles