根据模板模板参数的参数数量对模板进行部分特化的语法是什么?

时间:2016-11-16 02:14:05

标签: c++ visual-c++ g++ clang++ template-templates

请考虑以下代码:

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上正确编译。但是,如果 定义了......

  • 代码在GCC上正确编译,表明它识别出OneParamTwoParam与主模板中的TTP不同。
  • Clang无法识别OneParam专门设置TTP,导致它发出两个错误(第一个是部分特化没有专门化任何模板参数,第二个是{ {1}}与先前声明的模板模板参数冲突)。然后它会为OneParam发出类似的错误(第一个是相同的,而第二个表示模板模板参数的参数太多),并且TwoParam的每个实例化都有错误(因为它考虑了模板) (未定义),共计6个错误。
  • MSVC向Clang发出类似错误,但更简洁:它发出C3855(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扩展和/或错误?

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

1 个答案:

答案 0 :(得分:2)

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,采用单参数模板模板参数的类模板比采用可变参数模板模板参数的类模板更专业。 ICC和gcc看起来是正确的,但在前一种情况下,因为它得到(1)错误,而gcc与(3)不一致。
  5. 结论:您正在使用正确的法律语法;但是您可能需要非兼容编译器的变通方法。即使您的代码编译,我也会建议使用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我们可以同时将#1OneParam重写为功能模板:

    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更专业。