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 properly on Clang, GCC, and MSVC, if TEST_TTP
isn't defined. However, if it is defined...
OneParam
and TwoParam
are distinct from TTP
in the primary template.OneParam
specialises TTP
, causing it to emit two errors (the first being that the partial specialisation doesn't specialise any template parameters, and the second being that OneParam
conflicts with the previously-declared template template parameter). It then emits similar errors for TwoParam
(the first is identical, while the second says that the template template parameter has too many parameters), and an error for each instantiation of SS
(because it considers the template to be undefined), for a total of 6 errors.OneParam
is incompatible with the primary template), and a C2079 (variable uses undefined type) for each instantiation of SS
, for a total of 3 errors.Demonstrated live on Coliru.
From my testing:
GCC allows a template with a template template parameter which takes a variadic parameter pack to be partially specialised based solely on the number of parameters that template template parameter takes. 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 are fine with this if other parameters are also specialised, however.
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> {};
It would thus appear that either the former isn't legal C++, or it isn't properly supported by Clang and MSVC. So, the question is this:
Considering this, what is the proper, legal syntax for partially specialising a template, which contains a template template parameter, based on the number of parameters that template template parameter takes? If there is no legal syntax for this, is supporting it a GCC extension and/or bug?
If a full record of the tests I performed, along with the original example which prompted this question, are desired, please see the edit history.
For example, given a specialization Stack<int>, “int” is a template argument. Instantiation: This is when the compiler generates a regular class, method, or function by substituting each of the template's parameters with a concrete type.
8. Why we use :: template-template parameter? Explanation: It is used to adapt a policy into binary ones.
The act of creating a new definition of a function, class, or member of a class from a template declaration and one or more template arguments is called template instantiation. The definition created from a template instantiation is called a specialization.
Templates can have more than one parameter type. Some older compilers allow one only to specialize either all or none of the template's parameters. Compilers that support partial specialization allow the programmer to specialize some parameters while leaving the others generic.
tl;dr: your code is valid, but no compiler handles it correctly; even those that accept it (gcc and ICC (Intel C++ Compiler)) do so inconsistently or for the wrong reason.
The correct reasoning is as follows:
Conclusion: you are using the proper, legal syntax; but you are likely to need workarounds for non-compliant compilers. Even when your code compiles I would recommend using static_assert
to verify that the expected function overload or class template partial specialization has been selected.
Per [temp.class.order], we partially order class template partial specializations by rewriting to overloaded function 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 rewrite, claiming the call to f
as ambiguous, as does MSVC; gcc inconsistently rejects this rewrite even though it accepted the original class template partial specialization. ICC accepts this rewrite and initializes ssoi
to 1
, corresponding to the 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 the call to f
at the initialization of ssoi
can call either #0
or #1
, so to determine which is more specialized we must synthesize template arguments and attempt to perform type deduction:
// #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 per [temp.func.order]/5 we do not synthesize arguments corresponding to Ts...
.
Deduction in #1 -> #0
succeeds (TTP := OneParam1; Ts... := {}
), as expected (#1
is the rewrite corresponding to a partial specialization of the class template of which #0
is the rewrite).
Deduction in #0 -> #1
succeeds (OneParam := TTP0; Ts... := {}
) under gcc and MSVC. clang (inconsistently) and ICC reject f1
, stating that TTP0
cannot be deduced to OneParam
(clang: "candidate template ignored: substitution failure : template template argument has different template parameters than its corresponding template template parameter").
So we must first determine whether the deduction #0 -> #1
is indeed feasible. We can look at 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 (inconsistently), clang and ICC. However, gcc is correct to accept this per [temp.arg.template]/3.
Next we must determine whether #1
is more specialized than #0
, or whether they are ambiguous in terms of ordering. Per [temp.deduct.partial]/10 we consider the types in #0
and #1
turn by turn; per [temp.arg.template]/4 we can rewrite OneParam
and TTP
to function templates, simultaneously :
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
We now partial order the rewritten f
and g
overloads, passing through [temp.deduct.partial] and [temp.deduct.type] to determine that per the tie-breaker for variadics #1
is more specialized than #0
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With