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

时间:2009-03-13 11:22:25

标签: c++ g++ typedef typename

自从海湾合作委员会抓住我这一次以来已经有一段时间了,但它刚刚发生在今天。但是我从来没有理解为什么GCC在模板中需要typedef typename,而VS和我猜ICC没有。 typedef typename是一个“bug”还是一个严格的标准,还是留给编译器编写者的东西?

对于那些不知道我的意思的人来说,这是一个样本:

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

注意:这不是我正在使用的实际功能,但只是一些愚蠢的事情来证明这个问题。

5 个答案:

答案 0 :(得分:56)

标准要求使用typename。模板编译需要两步验证。在第一次传递期间,编译器必须验证模板语法,而不实际提供类型替换。在此步骤中,假定std :: map :: iterator是一个值。如果它确实表示类型,则typename关键字是必需的。

为什么这有必要?在替换实际的KEY和VALUE类型之前,编译器不能保证模板不是专用的,并且专门化不是将 iterator 关键字重新定义为其他类型。

您可以使用以下代码进行检查:

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被解释为值,但实例化了测试&lt;&gt;类型为X的模板会生成一个类型。

答案 1 :(得分:32)

好吧,GCC实际上要求 typedef - typename就足够了。这有效:

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

这是上下文敏感的解析问题的示例。有问题的行只是从这个函数的语法中看不出来 - 你需要知道std::map<KEY,VALUE>::const_iterator是否是一个类型。

现在,我似乎无法想到一个例子...... ::const_iterator可能是一个类型,也不会是一个错误。所以我想编译器可以发现是一个类型,但对于糟糕的编译器(编写者)来说可能很难。

根据标准第14.6 / 3条的规定,该标准要求在此使用typename

答案 2 :(得分:4)

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

答案 3 :(得分:3)

这是Microsoft C ++编译器中的一个错误 - 在您的示例中,std :: map :: iterator可能不是一个类型(您可以在KEY,VALUE上使用专门的std :: map,以便std :: map ::迭代器是一个变量,例如)。

GCC强制您编写正确的代码(即使您的意思很明显),而Microsoft编译器正确猜测您的意思(即使您编写的代码不正确)。

答案 4 :(得分:2)

应该注意,值/类型kinding问题不是根本问题。主要问题是解析。考虑

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

除非typename关键字是必需的,否则无法判断这是强制转换还是函数调用。在这种情况下,上面的代码包含一个函数调用。一般来说,如果不完全解析,就不能延迟选择,只需考虑片段

(a)(b)(c)

如果你不记得,cast的优先级高于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 ++中的类型,则无法解析。