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

9

通过一个类似的问题(链接),我感到惊讶(和困惑)。我自己尝试了这个问题中提到的标准示例:

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]。使用gcc HEAD 8.0.1 201803编译可以通过,但使用clang HEAD 7.0.0编译会失败,除非在实例化时使用尖括号。请参考gcc编译结果clang编译结果
S s; s.f(); // error: declaration of variable 's' with deduced type 'S' requires an initializer
S<> t; t.f(); // Correct

不考虑这个问题,我已经检查了其他模板样式来验证它们的行为方式。代码被接受或拒绝的方式非常不规则。

模板函数
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 <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 <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;
}

有关该功能的标准文本没有区分不同的模板类型,因此我认为它们应该表现出相同的行为。

但是,两个编译器都拒绝了模板变量的情况,因此我对模板变量选项有些怀疑。对我来说,CLang拒绝模板变量并抱怨重定义是正确的,而GCC因为错误的原因而拒绝了代码,但这种推理并不符合标准中[ temp.param] / 10所说的内容。

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

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

你尝试过将变量重新声明为“extern”吗?否则它确实是一个重新定义。 - Peter K
3
如果这不算是欺负编译器,那我也不知道什么算了 ;) - Passer By
@PeterK 我没有这样做。我认为在最坏的情况下,它会像别名模板一样运行(两种情况都有初始化程序,并且至少被CLang接受)。 - PaperBirdMaster
@PeterK 非常感谢!我已经在测试它了。 - PaperBirdMaster
我意识到这个问题没有明确说明……是C++17还是C++14? 这对于第一种情况很重要。 - Barry
显示剩余2条评论
2个回答

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

The set of default template-arguments available for use is obtained by merging the default arguments from all prior declarations of the template in the same way default function arguments are ([dcl.fct.default]). [ Example:

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

is equivalent to

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

— end example ]

当您写下 S s 时,两个模板参数都被默认,因此默认构造函数的重写为 rewrite
template <typename T=int, typename U=int>
S<T, U> __f();

哪一个是可行的,应该推导出S<int,int>


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

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

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

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


在模板变量的情况下,哪个编译器是正确的? - Yola
@Yola 两种方式都可以,不编译也可以 - 这取决于你的需求。不过Clang的诊断功能确实更好一些。 - Barry

1

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

仔细研究后,我发现Clang是正确的,而GCC则感到困惑。

  • First case, class template (unlike function template) indeed requires <>.

  • Second case, function template, is treated by Clang exactly like the first case, without the syntactic requirement to use <> to indicate that template is used. This applies in C++ to function templates in all contexts.

  • Third case: as of variables, I see that Clang is correct while GCC is confused. If you redeclare the variable with extern Clang accepts it.

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

    so it again behaves consistently both with standard and the previous cases. Without extern this is redefinition and it is forbidden.

  • Fourth case, using template. Clang again behaves consistently. I used typeid to ensure that alias is indeed 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;
    }
    

    then

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

    outputs

    0
    int
    

正如标准所述,Clang确实不会区分重新声明的模板的类型。


2
首先,类模板(与函数模板不同)确实需要<>,但在C++17中不再是真的。 - Jarod42
抱歉,我手头只有Clang 3.9,它只支持C++-14;我会检查更新版本。 - Peter K

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接