混淆的模板错误

112

我已经使用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>();
    }
};

clang所产生的错误信息:

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

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


12
你试过把它插在箭头指向的地方吗? - Mike Seymour
3
类似于 这个这个 - Prasoon Saurav
4个回答

139

ISO C++03 14.2/4:

当成员模板特化的名称出现在后缀表达式中的“.”或“->”之后,或在限定符标识符中的嵌套名称说明符之后,并且后缀表达式或限定符标识符明确依赖于模板参数(14.6.2)时,必须在成员模板名称前加上关键字template。否则,该名称被认为指代一个非模板。

t->f0<U>(); 中,f0<U> 是出现在 -> 后并明确依赖于模板参数 U 的成员模板特化,因此成员模板特化必须以关键字 template 为前缀。

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


1
有趣的是,我认为将表达式放在括号中:t->(f0<U>())会解决这个问题,因为我认为这会将f0<U>()放入独立的表达式中... 好吧,看来我错了... - user350814
43
能否请您评论一下为什么会出现这种情况?为什么C++需要这种语法? - Curious
12
是的,这很奇怪。语言可以“检测”到模板关键字需要存在。既然它能做到,那么它应该自己“插入”关键字。 - Enrico Borba
我觉得这是一个无用的语法特性.... - Virux

31

除了其他人提到的观点外,还要注意有时编译器可能无法确定,当实例化时,两种解释都可以产生替代有效的程序。

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

在省略f<int()>前的template时,它打印出0,但在插入它时打印1。留给读者练习来弄清代码的作用。


3
那可真是个魔鬼般的例子! - Matthieu M.
1
我无法在Visual Studio 2013中重现您所描述的行为。它总是调用f<U>并始终打印1,这对我来说非常合理。 我仍然不明白为什么需要template关键字以及它所产生的差异。 - Violet Giraffe
@ Violet,VSC++编译器不是符合C ++标准的编译器。如果您想知道为什么VSC ++始终打印1,则需要提出新问题。 - Johannes Schaub - litb
3
此答案解释了为什么需要使用 template:https://dev59.com/dHRB5IYBdhLWcg3weXOX,而不仅仅依赖于难以理解的标准术语。如果该答案中有任何仍然令人困惑的地方,请报告。 - Johannes Schaub - litb
@JohannesSchaub-litb:谢谢,非常好的答案。事实证明,我以前已经看过它,因为它已经被我点赞了。显然,我的记忆力有点差。 - Violet Giraffe

15

摘自C++ Templates

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

template<int N> 
void printBitset (std::bitset<N> const& bs) 
{ 
    std::cout << bs.template to_string<char,char_traits<char>, 
                                       allocator<char> >(); 
} 
这个示例中的奇怪构造是 .template。如果没有额外使用 template,编译器不知道紧随其后的小于号(<)并不是真正的“小于”,而是一个模板参数列表的开头。请注意,只有在前面的结构取决于模板参数时,这才是一个问题。在我们的示例中,参数 bs 取决于模板参数 N。
总之,.template表示法(以及类似的表示法,如->template)应仅在模板内部使用,而且仅在它们跟随某些依赖于模板参数的内容时才应使用。

14

将其插入到光标所在点之前:

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

编辑:如果您像编译器一样思考,这条规则的原因会更清晰。[编辑:请参见评论]关键字的原因与您需要使用typename关键字指示依赖类型名称的原因相同:它告诉编译器“嘿,您即将看到的标识符是模板的名称,而不是静态数据成员的名称后跟小于号”。


1
我绝对猜不到那个......但还是谢谢你;-)。显然关于C++总有新东西可以学习! - user350814
3
即使具有无限的前瞻能力,你仍然需要使用“template”。在某些情况下,使用和不使用“template”都可以产生有效的程序,但二者的行为不同。因此,这不仅是一个语法问题(对于小于号和模板参数列表版本,“t->f0<int()>(0)”在语法上都是有效的)。 - Johannes Schaub - litb
@Johannes Schaub - litb:是的,这更多是将一致的语义含义分配给表达式的问题,而不是向前查看的问题。 - Doug

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