51

自从 GCC 发现我已经有一段时间了,但它就发生在今天。但我从来不明白为什么 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();
}

注意:这不是我正在使用的实际功能,而只是演示问题的一些愚蠢的东西。

4

5 回答 5

57

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

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

您可以使用以下代码检查它:

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 的第一个模板编译步骤中,Test::value 被解释为一个值,但类型 X 的 Test<> 模板的实例化产生了一个类型。

于 2009-03-13T12:25:51.417 回答
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除了一个类型之外,这也不会是一个错误。所以我猜编译器可以发现它必须是一种类型,但对于可怜的编译器(作者)来说可能很难。

该标准要求在typename此处使用,根据标准第 14.6/3 节的 litb。

于 2009-03-13T11:31:26.853 回答
4

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

于 2009-03-13T11:32:24.527 回答
3

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

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

于 2009-03-13T12:11:10.563 回答
2

需要注意的是,值/类型类型的问题并不是根本问题。主要问题是解析。考虑

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++ 中的类型,就无法解析事物。

于 2010-11-30T17:25:57.107 回答