Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the syntax for partially specialising a template based on the number of parameters a template template parameter 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 properly on Clang, GCC, and MSVC, if TEST_TTP isn't defined. However, if it is defined...

  • The code compiles properly on GCC, indicating that it recognises that OneParam and TwoParam are distinct from TTP in the primary template.
  • Clang fails to recognise that 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.
  • MSVC emits similar errors to Clang, but more concisely: It emits C3855 (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.

like image 310
Justin Time - Reinstate Monica Avatar asked Nov 16 '16 02:11

Justin Time - Reinstate Monica


People also ask

Which is the correct example of template parameters?

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.

Why do we use :: template template parameter?

8. Why we use :: template-template parameter? Explanation: It is used to adapt a policy into binary ones.

What is meant by template specialization in C++?

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.

What is the difference between partial specialization and template 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.


1 Answers

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:

  1. A variadic template template parameter can be deduced to a single-argument template template parameter, and vice versa. gcc is the only compiler to get this correct.
  2. A single-argument template is more specialized than a variadic template. All modern compilers get this right.
  3. Accordingly, a function template taking a single-argument template template parameter is more specialized than a function template taking a variadic template template parameter. The only compiler that appears to get this correct is ICC, but only because it gets (1) wrong.
  4. Accordingly, a class template taking a single-argument template template parameter is more specialized than a class template taking a variadic template template parameter. ICC and gcc appear to get this correct, but in the former case because it gets (1) wrong, and gcc inconsistently with (3).

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.


Full analysis

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.

like image 131
ecatmur Avatar answered Sep 22 '22 08:09

ecatmur