根据模板模板参数采用的参数数量部分特化模板的语法是什么?

What is the syntax for partially specialising a template based on the number of parameters a template template parameter takes?

考虑以下代码:

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;
}

如果未定义 TEST_TTP,此代码将在 Clang、GCC 和 MSVC 上正确编译。但是,如果它 定义的...

现场演示on Coliru.


根据我的测试:

GCC 允许带有模板模板参数的模板采用可变参数包,仅根据模板模板参数采用的参数数量进行部分特化。 Clang 和 MSVC 没有。

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 和 MSVC 也可以。

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> {};

因此,前者似乎不是合法的 C++,或者它没有得到 Clang 和 MSVC 的正确支持。所以,问题是:

考虑到这一点,根据模板模板参数采用的参数数量,部分特化包含模板模板参数的模板的正确合法语法是什么?如果没有合法的语法,支持它是 GCC 扩展 and/or 错误吗?

如果需要我执行的测试的完整记录以及提示此问题的原始示例,请查看编辑历史记录。

tl;dr:你的代码是有效的,但没有编译器能正确处理它;即使是那些接受它的人(gcc 和 ICC(英特尔 C++ 编译器))也不一致或出于错误的原因这样做。

正确的推理如下:

  1. 可变模板模板参数可以be deduced to a single-argument template template parameter, and vice versa。 gcc 是唯一正确的编译器。
  2. 单参数模板是 more specialized than a variadic template。所有现代编译器都能做到这一点。
  3. Accordingly,采用单参数模板模板参数的函数模板比​​采用可变参数模板模板参数的函数模板更专业。似乎唯一正确的编译器是 ICC,但这只是因为它得到 (1) 错误。
  4. Accordingly,采用单参数模板模板参数的 class 模板比采用可变模板模板参数的 class 模板更专业。 ICC 和 gcc 似乎是正确的,但在前一种情况下,因为它得到 (1) 错误,并且 gcc 与 (3) 不一致。

结论:您使用的是正确的、合法的语法;但是您可能需要针对不兼容的编译器的解决方法。即使您的代码可以编译,我也会 建议使用 static_assert 来验证是否已选择预期的函数重载或 class 模板偏特化。


全面分析

根据 [temp.class.order],我们通过重写重载函数模板来部分排序 class 模板偏特化:

// 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>{});

正如预期的那样,clang 拒绝了此重写,声称对 f 的调用不明确,MSVC 也是如此; gcc 不一致地拒绝此重写,即使它接受了原始的 class 模板部分专业化。 ICC 接受此重写并将 ssoi 初始化为 1,对应于 OneParam 专业化 #1.

现在我们可以根据rules for partial ordering of function templates([temp.func.order])判断哪个编译器是正确的。我们可以看到在 ssoi 初始化时对 f 的调用可以调用 #0#1,因此要确定哪个更专业,我们必须合成模板参数并尝试执行类型推导:

// #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>{});

请注意,根据 [temp.func.order]/5 我们不会合成对应于 Ts....

的参数

#1 -> #0 中的推导成功(TTP := OneParam1; Ts... := {}),正如预期的那样(#1 是对应于 class 模板的部分特化的重写,其中 #0 是重写)。

#0 -> #1 中的推导在 gcc 和 MSVC 下成功(OneParam := TTP0; Ts... := {})。 clang(不一致)和 ICC 拒绝 f1,指出 TTP0 不能推导为 OneParam(clang:"candidate template ignored: substitution failure : template template argument has different template parameters than its corresponding template template parameter")。

所以首先要判断推演#0 -> #1是否确实可行。我们可以看一个更简单的等效案例:

template<template<class> class P> class X { };
template<class...> class C { };
X<C> xc;

这被 gcc 接受,被 MSVC(不一致)、clang 和 ICC 拒绝。但是,gcc 接受这个是正确的 [temp.arg.template]/3.

接下来我们必须确定 #1 是否比 #0 更专业,或者它们在排序方面是否有歧义。根据 [temp.deduct.partial]/10 我们依次考虑 #0#1 中的类型;根据 [temp.arg.template]/4 我们可以同时将 OneParamTTP 重写为函数模板:

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

我们现在 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#0 更专业。