为什么我需要在g ++中使用typedef typename而不是VS?

自从海湾合作委员会抓到了这件事以来已经有一段时间了,但今天才发生。 但我从来没有理解为什么GCC需要在模板内使用typedef typename,而VS和我想ICC不这样做。 typedef typename是一个“错误”还是一个过于严格的标准,还是一些编译器的作者?

对于那些不知道我的意思的人来说是一个例子:

template<typename KEY, typename VALUE> bool find(const std::map<KEY,VALUE>& container, const KEY& key) { std::map<KEY,VALUE>::const_iterator iter = container.find(key); return iter!=container.end(); } 

上面的代码编译在VS(可能在ICC中),但在GCC失败,因为它需要这样的:

 template<typename KEY, typename VALUE> bool find(const std::map<KEY,VALUE>& container, const KEY& key) { typedef typename std::map<KEY,VALUE>::const_iterator iterator; //typedef typename iterator iter = container.find(key); return iter!=container.end(); } 

注:这不是我正在使用的实际function,但只是愚蠢的东西,说明问题。

typename是标准所要求的。 模板编译需要两步validation。 在第一遍时,编译器必须validation模板语法,而不实际提供typesreplace。 在这一步中,std :: map :: iterator被假定为一个值。 如果它确实表示一个types,则需要typename关键字。

为什么这是必要的? 在代替实际的KEY和VALUEtypes之前,编译器不能保证模板没有被专门化,专业化也没有将迭代器关键字重新定义为别的东西。

你可以用下面的代码检查它:

 class X {}; template <typename T> struct Test { typedef T value; }; template <> struct Test<X> { static int value; }; int Test<X>::value = 0; template <typename T> void f( T const & ) { Test<T>::value; // during first pass, Test<T>::value is interpreted as a value } int main() { f( 5 ); // compilation error X x; f( x ); // compiles fine f: Test<T>::value is an integer } 

最后一次调用失败,错误指示在f()的第一个模板编译步骤中,Test :: value被解释为一个值,但具有typesX的Test <>模板的实例化产生一个types。

那么,GCC实际上并不需要 typedeftypename就足够了。 这工作:

 #include <iostream> #include <map> template<typename KEY, typename VALUE> bool find(const std::map<KEY,VALUE>& container, const KEY& key) { typename std::map<KEY,VALUE>::const_iterator iter = container.find(key); return iter!=container.end(); } int main() { std::map<int, int> m; m[5] = 10; std::cout << find(m, 5) << std::endl; std::cout << find(m, 6) << std::endl; return 0; } 

这是一个上下文相关的parsing问题的例子。 这个问题的意思是从这个函数的语法中看不出来 – 你需要知道std::map<KEY,VALUE>::const_iterator是否是一个types。

现在,我似乎无法想象什么… ::const_iterator可能是一个例子,除了一个types,这也不会是一个错误。 所以我猜编译器可以发现它必须是一个types,但是可怜的编译器(编写者)可能很难。

标准要求在这里使用typename ,根据标准第14.6 / 3节的规定。

它看起来像VS / ICC提供了typename关键字,只要它认为是必需的。 注意这是一个坏事(TM) – 让编译器决定想要什么。 通过灌输在需要时跳过types名称的坏习惯,这进一步使问题复杂化,并且是可移植性的噩梦。 这绝对不是标准的行为。 尝试严格的标准模式或科莫。

这是Microsoft C ++编译器中的一个bug – 在你的例子中,std :: map :: iterator可能不是一个types(你可以在KEY,VALUE上使用专门的std :: map,这样std :: map :: iterator是一个variables例如)。

GCC迫使你编写正确的代码(即使你的意思是明显的),而微软编译器正确地猜测你的意思(即使你写的代码是不正确的)。

应该指出的是,价值/types激进问题不是根本问题。 主要的问题是parsing 。 考虑

 template<class T> void f() { (T::x)(1); } 

除非typename关键字是强制性的,否则无法判断这是否是强制转换或函数调用。 在这种情况下,上面的代码包含一个函数调用。 一般来说,select不能放弃,不要放弃parsing,只要考虑片段

 (a)(b)(c) 

如果你不记得的话,在C中抛出比函数调用更高的优先级,Bjarne希望函数式抛出的原因之一。 因此无法判断上述的含义

 (a)(b) (c) // a is a typename 

要么

 (a) (b)(c) // a is not a typename , b is 

要么

 (a)(b) (c) // neither a nor b is a typename 

我插入空间来表示分组。

还要注意,“templatename”关键字与“typename”的原因是相同的,如果不知道它们在C / C ++中的types,就不能parsing它们。