基于模板模板参数所采用的参数数量,部分特化模板的语法是什么?

Jus*_*ica 7 c++ g++ template-templates visual-c++ clang++

请考虑以下代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 的代码可以正确编译上GCC,这表明它认识到OneParamTwoParam来自不同TTP的主模板.
  • 锵没有认识到OneParam专业TTP,使其发射两个错误(第一个是那部分特不擅长任何模板参数,第二个是说OneParam与先前声明的模板,模板参数冲突).然后它会发出类似的错误TwoParam(第一个是相同的,而第二个表示模板模板参数有太多的参数),并且每个实例化的错误SS(因为它认为模板未定义),共计6个错误.
  • MSVC向Clang发出类似的错误,但更简洁:它发出C3855(OneParam与主模板不兼容),并且C2079(变量使用未定义类型)用于每个实例化SS,总共3个错误.

在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.
Run Code Online (Sandbox Code Playgroud)

如果其他参数也是专用的,那么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> {};
Run Code Online (Sandbox Code Playgroud)

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

考虑到这一点,基于模板模板参数采用的参数数量,部分特化模板的合法语法是什么,其中包含模板模板参数?如果没有合法的语法,是否支持GCC扩展和/或错误?

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

eca*_*mur 2

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

正确的推理如下:

  1. 可变参数模板参数可以推导为单参数模板参数,反之亦然。gcc 是唯一能够正确实现这一点的编译器。
  2. 单参数模板比可变参数模板更专业。所有现代编译器都正确地做到了这一点。
  3. 因此,采用单参数模板模板参数的函数模板比​​采用可变参数模板模板参数的函数模板更专业。似乎唯一正确的编译器是 ICC,但这只是因为它得到 (1) 错误。
  4. 因此,采用单参数模板模板参数的类模板比采用可变参数模板模板参数的类模板更专业。ICC 和 gcc 似乎对此是正确的,但在前一种情况下,因为它得到 (1) 错误,而 gcc 与 (3) 不一致。

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


全面分析

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

// 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>{});
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,clang 拒绝了此重写,声称对 的调用f不明确,MSVC 也是如此;尽管 gcc 接受了原始类模板部分特化,但它始终拒绝这种重写。ICC 接受此重写并初始化ssoi1,对应于OneParam专门化#1

现在我们可以通过遵循函数模板的部分排序规则[temp.func.order])来确定哪个编译器是正确的。f我们可以看到,在初始化时对 的调用ssoi可以调用#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>{});
Run Code Online (Sandbox Code Playgroud)

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

推导#1 -> #0成功 ( TTP := OneParam1; Ts... := {}),如预期(是与重写的#1类模板的部分特化相对应的重写)。#0

在 gcc 和 MSVC 下推导#0 -> #1成功 ( OneParam := TTP0; Ts... := {})。clang (不一致)和 ICC拒绝f1,指出TTP0无法推断出OneParam(clang:“候选模板被忽略:替换失败:模板模板参数与其相应的模板模板参数具有不同的模板参数”)。

所以我们首先要判断扣除是否#0 -> #1确实可行。我们可以看一个更简单、等效的情况:

template<template<class> class P> class X { };
template<class...> class C { };
X<C> xc;
Run Code Online (Sandbox Code Playgroud)

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

接下来我们必须确定 是否#1比 更专业#0,或者它们在排序方面是否不明确。每[temp.deduct.partial]#0 /10 我们依次考虑和中的类型#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
Run Code Online (Sandbox Code Playgroud)

我们现在对重写和重载进行部分排序,通过[temp.deduct.partial][temp.deduct.type]来确定每个变量的决胜局比 更专业。fg #1#0