请考虑以下代码:
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上正确编译。但是,如果 定义了......
OneParam
和TwoParam
与主模板中的TTP
不同。OneParam
专门设置TTP
,导致它发出两个错误(第一个是部分特化没有专门化任何模板参数,第二个是{ {1}}与先前声明的模板模板参数冲突)。然后它会为OneParam
发出类似的错误(第一个是相同的,而第二个表示模板模板参数的参数太多),并且TwoParam
的每个实例化都有错误(因为它考虑了模板) (未定义),共计6个错误。SS
与主模板不兼容),并且C2079(变量使用未定义类型)用于OneParam
的每个实例化,共计3个错误。现场演示on Coliru。
从我的测试开始:
GCC允许带有模板模板参数的模板,该参数仅根据模板模板参数所采用的参数数量,使可变参数包具有部分专用性。 Clang和MSVC没有。
SS
如果其他参数也是专门的,那么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.
因此,看起来前者既不是合法的C ++,也不是Clang和MSVC不能正确支持的。所以,问题是:
考虑到这一点,根据模板模板参数所采用的参数数量,对包含模板模板参数的模板进行部分特化的正确合法语法是什么?如果没有合法的语法,是否支持GCC扩展和/或错误?
如果需要我执行的测试的完整记录以及提示此问题的原始示例,请参阅编辑历史记录。
答案 0 :(得分:2)
tl; dr:您的代码有效,但没有编译器正确处理它;甚至那些接受它的人(gcc和ICC(英特尔C ++编译器))也不一致或出于错误的原因。
正确的推理如下:
结论:您正在使用正确的法律语法;但是您可能需要非兼容编译器的变通方法。即使您的代码编译,我也会建议使用static_assert
来验证是否已选择预期的函数重载或类模板部分特化。
Per [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>{});
正如预期的那样,clang拒绝重写,声称对f
的调用不明确,MSVC也是如此; gcc不一致地拒绝这个重写,即使它接受了原始的类模板部分特化。 ICC接受此重写并将ssoi
初始化为1
,对应OneParam
专精#1
。
现在我们可以按照rules for partial ordering of function templates( [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>{});
请注意,根据 [temp.func.order] / 5,我们不会合成与Ts...
对应的参数。
#1 -> #0
中的扣除成功(TTP := OneParam1; Ts... := {}
),如预期的那样(#1
是对应于类模板的部分特化的重写,其中#0
是重写的)
#0 -> #1
中的扣除在gcc和MSVC下成功(OneParam := TTP0; Ts... := {}
)。 clang(不一致)和ICC拒绝f1
,声明TTP0
无法推断为OneParam
(clang:“候选模板被忽略:替换失败:模板模板参数具有与其对应的模板参数不同模板模板参数“)。
因此,我们必须首先确定扣除{{1}}是否确实可行。我们可以看一个更简单,等效的案例:
#0 -> #1
这被gcc接受,并被MSVC(不一致),clang和ICC拒绝。但是,gcc对于[temp.arg.template]/3
>接受这个是正确的接下来,我们必须确定template<template<class> class P> class X { };
template<class...> class C { };
X<C> xc;
是否比#1
更专业,或者它们在排序方面是否含糊不清。每 [temp.deduct.partial] / 10,我们会依次考虑#0
和#0
中的类型;每 [temp.arg.template] / 4我们可以同时将#1
和OneParam
重写为功能模板:
TTP
我们现在partial order重写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
和f
重载,通过 [temp.deduct.partial] 和 [temp.deduct。键入] 以确定每tie-breaker for variadics g
比#1
更专业。