具有模板参数推导和默认模板参数的模板变量

时间:2018-03-12 11:24:58

标签: c++ templates language-lawyer c++17 default-parameters

similar question惊讶(和共同)我自己尝试了标准中提到的问题的例子:

template <typename T, typename U = int> struct S;
template <typename T = int, typename U> struct S
{ void f() { std::cout << __PRETTY_FUNCTION__ << '\n'; } };

int main()
{
    S s; s.f();
    return 0;
}

上面的代码打印void S<int, int>::f() [T = int, U = int] compiled with gcc HEAD 8.0.1 201803但无法使用clang HEAD 7.0.0进行编译,除非在实例化期间使用尖括号:

S s; s.f(); // error: declaration of variable 's' with deduced type 'S' requires an initializer
S<> t; t.f(); // Correct

除了这个问题之外,我已经检查了其他模板风格这个特定的行为,并且以非常不规则的方式接受或拒绝了代码:

Template function
template <typename T, typename U = int> void function();
template <typename T = int, typename U> void function()
{ std::cout << __PRETTY_FUNCTION__ << '\n'; }

int main()
{
    /* Rejected by GCC: no matching function for call to 'function()'
       template argument deduction/substitution failed:
       couldn't deduce template parameter 'T'
       same error with function<>()

       CLang compiles without issues */
    function(); // CLang prints 'void function() [T = int, U = int]'
    return 0;
}
Template variable
template <typename T, typename U = int> int variable;
template <typename T = int, typename U> int variable = 0;

int main()
{
    /* GCC complains about wrong number of template arguments (0, should be at least 1)
     while CLang complains about redefinition of 'variable' */
    std::cout << variable<> << '\n';
    return 0;
}
Template alias
template <typename T, typename U = int> using alias = int;
template <typename T = int, typename U> using alias = int;

int main()
{
    /* GCC complains about redefinition of 'alias'
       while CLang compiles just fine. */
    alias<> v = 0;
    std::cout << v << '\n';
    return 0;
}

关于此功能的standards text并没有区分不同的模板类型,因此我认为它们的行为应该相同。

但是,模板变量的情况是两个编译器都拒绝的情况,所以我对模板变量选项有些怀疑。我觉得CLang正确地拒绝模板变量抱怨重新定义,而GCC错误地通过拒绝错误原因的代码,这是有道理的,但这种推理并不遵循标准在[temp]中所说的.PARAM] / 10。

那么对于模板变量的情况我应该期待什么?:

  • 由于重新定义而拒绝了代码(CLang是对的)。
  • 接受代码,合并两个模板定义(GCC和CLang都错了)。

2 个答案:

答案 0 :(得分:3)

对于类模板参数推导,这是一个铿锵的bug。来自[temp.param]/14

  

可以使用的默认 template-arguments 集合是通过合并模板的所有先前声明中的默认参数获得的,其方式与默认函数参数相同([dcl.fct.default] )。 [实施例:

template<class T1, class T2 = int> class A;
template<class T1 = int, class T2> class A;
     

相当于

template<class T1 = int, class T2 = int> class A;
     

- 结束示例]

当您编写S s时,两个模板参数都是默认的,因此默认构造函数的rewrite为:

template <typename T=int, typename U=int>
S<T, U> __f();

哪个是可行的,应该推断S<int, int>

对于函数,如果可以推导出所有模板参数,则无需指定<>来调用函数模板。这是[temp.arg.explicit]/3

  

如果可以推导出所有模板参数,则可以省略它们;在这种情况下,空模板参数列表<>本身也可以省略。

请注意,这适用于扣除。别名模板或变量模板没有扣除。因此,您不能省略<>。这是[temp.arg]/4

  

使用模板参数包或默认模板参数时, template-argument 列表可以为空。在这种情况下,空<>括号仍应用作 template-argument-list

答案 1 :(得分:1)

免责声明:以下内容在C ++ 14的上下文中有效。使用C ++ 17,两个编译器都是错误的。请参阅Barry的另一个答案。

详细研究我发现Clang在这里是正确的,而GCC很困惑。

  • 第一种情况,类模板(与功能模板不同)确实需要<>

  • 第二种情况,函数模板,由Clang处理,与第一种情况完全相同,没有使用<>来表示使用模板的语法要求。这在C ++中适用于所有上下文中的函数模板。

  • 第三种情况:作为变量,我看到Clang是正确的而GCC是混淆的。如果您使用extern重新声明变量,Clang会接受它。

    template <typename T, typename U = int> int variable = 0;
    template <typename T = int, typename U> extern int variable;
    
    int main()
    {
        // accepted by clang++-3.9 -std=c++14
        std::cout << variable<> << '\n';
        return 0;
    }
    

    所以它在标准和之前的情况下都表现得一致。没有extern这是重新定义,并且是禁止的。

  • 第四种情况,using模板。 Clang再次表现得很好。我使用typeid来确保别名确实是int:

    template <typename T, typename U = int> using alias = int;
    template <typename T = int, typename U> using alias = int;
    
    int main()
    {
        alias<> v = 0;
        std::cout << v << '\n';
        std::cout << typeid(v).name() << '\n';
        return 0;
    }
    

    然后

    $ ./a.out | c++filt -t
    

    输出

    0
    int
    

因此,如standard中所述,Clang确实对重新声明的模板类型没有任何区别。