令人困惑的模板错误


91

我一直在玩clang,我偶然发现了“ test / SemaTemplate / dependent-template-recover.cpp”(在clang发行版中),它应该提供从模板错误中恢复的提示。

可以很容易地将整个过程简化为一个最小的示例:

template<typename T, typename U, int N> struct X {
    void f(T* t)
    {
        // expected-error{{use 'template' keyword to treat 'f0' as a dependent template name}}
        t->f0<U>();
    }
};

c发出的错误消息:

tpl.cpp:6:13: error: use 'template' keyword to treat 'f0' as a dependent template name
         t->f0<U>();
            ^
            template 
1 error generated.

...但是我很难理解应该在哪里插入template关键字以使代码在语法上正确?


11
您是否尝试将其插入箭头所指的位置?
Mike Seymour

3
类似这种
Prasoon Saurav

Answers:


104

ISO C ++ 03 14.2 / 4:

当成员模板专门化名称出现在之后。或->在postfix-expression中,或在qualified-id中嵌套名称说明符之后,并且postfix-expression或qualified-id明确取决于模板参数(14.6.2),成员模板名称必须为以关键字模板为前缀。否则,假定该名称命名为非模板。

Int->f0<U>(); f0<U>是成员模板特->U,该成员特化在模板参数之后出现,并且显式取决于template参数,因此,成员模板特化必须以template关键字为前缀。

因此更改t->f0<U>()t->template f0<U>()


有趣的是,我想将表达式放在括号中:t->(f0<U>())可以解决该问题,因为我认为可以将其放入f0<U>()独立表达式中……嗯,我认为错了,看来……

24
您能否评论为什么会这样?为什么C ++需要这种语法?
好奇的

2
是的,这很奇怪。该语言可以“检测到”需要显示模板关键字。如果能够做到这一点,那么它应该只是将关键字“插入”其中。
Enrico Borba

26

除了其他观点外,请注意,有时编译器无法下定决心,并且在实例化时两种解释都可以生成其他有效程序

#include <iostream>

template<typename T>
struct A {
  typedef int R();

  template<typename U>
  static U *f(int) { 
    return 0; 
  }

  static int f() { 
    return 0;
  }
};

template<typename T>
bool g() {
  A<T> a;
  return !(typename A<T>::R*)a.f<int()>(0);
}


int main() {
  std::cout << g<void>() << std::endl;
}

0省略template之前f<int()>1插入时打印。我将其作为练习来弄清楚代码的作用。


3
现在,这是一个恶魔般的例子!
Matthieu M.

1
我无法重现您在Visual Studio 2013中描述的行为。它始终调用f<U>并始终打印1,这对我来说很有意义。我仍然不明白为什么template需要关键字以及它有什么区别。
紫罗兰色长颈鹿

@Violet VSC ++编译器不是兼容的C ++编译器。如果你想知道为什么VSC ++始终打印1.需要一个新的问题
litb -约翰内斯绍布

1
该答案解释了为什么template要这样做stackoverflow.com/questions/610245/…而不仅仅依赖于难以理解的标准术语。请报告该答案是否仍然令人困惑。
Johannes Schaub-litb 2014年

@ JohannesSchaub-litb:谢谢,一个很好的答案。原来,我已经读过它,因为它已经被我否决了。显然,我的记忆是嗯。
紫罗兰色长颈鹿

12

将其插入插入符号的位置之前:

template<typename T, typename U, int N> struct X {
     void f(T* t)
     {
        t->template f0<U>();
     }
};

编辑:如果您像编译器那样思考,则此规则的原因将变得更清楚。编译器通常一次只能预看一个或两个令牌,而通常不“预见”该表达式的其余部分。[编辑:查看评论]关键字的原因与您需要typename关键字来指示相关类型名称的原因相同:它告诉编译器“嘿,您将要看到的标识符是模板的名称,而不是模板的名称。静态数据成员的名称,后跟一个小于号”。


1
永远无法猜到...但是,谢谢;-)。显然总会有一些关于C ++的知识!

3
即使前瞻无限,您仍然需要template。在某些情况下,使用和不使用template都会产生具有不同行为的有效程序。因此,这不仅是一个语法问题(t->f0<int()>(0)对于小于和模板参数列表版本在语法上都是有效的)。
Johannes Schaub-litb

@Johannes Schaub-litb:是的,所以与向前看相比,为表达式分配一致的语义意义更多。
Doug

11

C ++模板摘录

.template构造引入类型名后,发现了一个非常相似的问题。考虑以下使用标准位集类型的示例:

template<int N> 
void printBitset (std::bitset<N> const& bs) 
{ 
    std::cout << bs.template to_string<char,char_traits<char>, 
                                       allocator<char> >(); 
} 

在此示例中,奇怪的构造是.template。如果没有额外使用模板,编译器将不会知道后面的小于符号(<)并不是真正的“小于”,而是模板参数列表的开头。请注意,只有在句点之前的构造取决于模板参数时,这才是问题。在我们的示例中,参数bs取决于模板参数N。

总之,.template表示法(以及类似的表示法,例如-> template)仅应在模板内部使用,并且仅在它们遵循依赖于模板参数的内容时使用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.