令人困惑的模板错误

Posted

技术标签:

【中文标题】令人困惑的模板错误【英文标题】:Confusing Template error 【发布时间】:2011-04-16 17:27:24 【问题描述】:

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

整个事情可以很容易地简化为一个最小的例子:

template<typename T, typename U, int N> struct X 
    void f(T* t)
    
        // expected-erroruse '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 关键字以使代码在语法上正确?

【问题讨论】:

您是否尝试将其插入箭头指向的位置? 类似于this和this 【参考方案1】:

ISO C++03 14.2/4:

当成员模板特化的名称出现在 .或 -> 在后缀表达式中,或在限定 ID 中的嵌套名称说明符之后,并且后缀表达式或限定 ID 显式依赖于模板参数 (14.6.2),成员模板名称必须以关键字模板为前缀。否则,该名称被假定为命名一个非模板。

t-&gt;f0&lt;U&gt;();f0&lt;U&gt;是出现在-&gt;之后的成员模板特化,它显式依赖于模板参数U,因此成员模板特化必须以template关键字为前缀。

所以将t-&gt;f0&lt;U&gt;() 更改为t-&gt;template f0&lt;U&gt;()

【讨论】:

有趣的是,我认为将表达式放在括号中:t-&gt;(f0&lt;U&gt;()) 会解决这个问题,因为我认为这会将 f0&lt;U&gt;() 放入独立表达式中......好吧,我想错了,看来。 .. 您能否评论一下为什么会这样?为什么 C++ 需要这种语法? 是的,这很奇怪。该语言可以“检测”模板关键字需要存在。如果它可以做到这一点,那么它应该只是在其中“插入”关键字。【参考方案2】:

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

#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&lt;int()&gt; 之前省略template 时会打印0,但在插入时会打印1。我把它作为一个练习来弄清楚代码的作用。

【讨论】:

现在这是一个可怕的例子! 我无法重现您在 Visual Studio 2013 中描述的行为。它始终调用 f&lt;U&gt; 并始终打印 1,这对我来说非常有意义。我仍然不明白为什么需要 template 关键字以及它有什么区别。 @Violet VSC++ 编译器不是兼容的 C++ 编译器。如果您想知道为什么 VSC++ 总是打印 1,则需要一个新问题。 这个答案解释了为什么需要template:***.com/questions/610245/…,而不是仅仅依赖于难以理解的标准术语。如果该答案中的任何内容仍然令人困惑,请报告。 @JohannesSchaub-litb:谢谢,一个很好的答案。原来,我以前读过它,因为它已经被我点赞了。显然,我的记忆力很好。【参考方案3】:

将它插入到插入符号所在的点之前:

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

编辑:如果你像编译器一样思考,这条规则的原因会变得更清楚。 编译器一般一次只向前看一两个标记,一般不会“向前看”到表达式的其余部分。[编辑:见评论]关键字的原因与为什么需要 typename 关键字来指示依赖类型名称:它告诉编译器“嘿,您将要看到的标识符是模板的名称,而不是静态数据成员的名称,后跟一个 less-比签名”。

【讨论】:

我本来从来没有能够猜到...但是谢谢你;-)。显然,C++ 总有一些东西要学! 即使无限前瞻,您仍然需要template。在某些情况下,无论有没有template,都会产生具有不同行为的有效程序。所以这不仅仅是一个语法问题(t-&gt;f0&lt;int()&gt;(0) 在语法上对于小于和模板参数列表版本都是有效的)。 @Johannes Schaub - litb:是的,所以与向前看相比,为表达分配一致的语义意义更多的是问题。【参考方案4】:

摘自C++ Templates

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

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

这个例子中奇怪的结构是 .template。如果没有额外使用模板,编译器不知道后面的小于标记 (

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

【讨论】:

以上是关于令人困惑的模板错误的主要内容,如果未能解决你的问题,请参考以下文章

可能简单但令人困惑的赋值错误

Fortran程序中令人困惑的调试错误

令人困惑的 NODE_MODULE 错误

TypeScript 令人困惑的错误'对象可能是'未定义'和'类型上不存在属性'长度''

尝试上传到 Cloudinary 时收到令人困惑的错误

令人困惑的“重复标识符”打字稿错误消息