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

Posted

技术标签:

【中文标题】为啥我需要在 g++ 中使用 typedef typename 而不是 VS?【英文标题】:Why do I need to use typedef typename in g++ but not VS?为什么我需要在 g++ 中使用 typedef typename 而不是 VS? 【发布时间】:2010-10-13 03:16:16 【问题描述】:

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

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

【问题讨论】:

g++之所以需要它,是因为g++更符合标准。 VS 在模板化解析的这一部分有点松懈(这导致了更复杂的模板中的其他问题)。 是的,但是为什么标准的 friggin 会这样做呢?我处理过相同的代码! 【参考方案1】:

好吧,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&lt;KEY,VALUE&gt;::const_iterator 是否是一个类型。

现在,我似乎想不出一个例子来说明 ...::const_iterator 除了类型之外可能是什么,这也不是错误。所以我猜编译器可以发现它必须是一个类型,但是对于可怜的编译器(作家)来说可能很难。

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

【讨论】:

在这里,我也认为您的意思是 KEY,VALUE 在以“typename”开头的行中。通过这种更改,它可以为我编译。 :) 我看到了你对这个问题的评论并刚刚修复了它:) 如需获得良好的常见问题解答,请考虑womble.decadentplace.org.uk/c++/… ..::iterator 可以引用静态成员。 没有类型名编译器无法解析的片段的简单示例:(X::t)(y),这是强制转换还是函数调用?注意优先级也不同!将 typedef 添加到 C 的人应该被枪杀:)【参考方案2】:

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

【讨论】:

如果编译器不顾一切地这样做,那将是一件坏事。事实上,它只对损坏的代码执行此操作。标准中实际上并没有禁止编译损坏的代码。不过仍然应该是一个警告(诊断)。【参考方案3】:

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

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

【讨论】:

实际上,MSVC 似乎会在决定之前检查 std::map::iterator 是否为类型。我没有标准的副本,但这似乎是不符合标准的行为,但这仅意味着它将(尝试)纠正和编译一些不正确的程序,而不是将错误引入正确的程序。 是的,这是一个错误,因为编译器不会对非法代码发出诊断。 不存在非法代码。仅当程序格式错误时才需要进行诊断。【参考方案4】:

标准要求类型名。模板编译需要两步验证。在第一次通过期间,编译器必须验证模板语法而不实际提供类型替换。在这一步中,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 被解释为一个值,但类型 X 的 Test 模板的实例化产生了一个类型。

【讨论】:

我认为您在两次调用 f 时混淆了 cmets,f( X() ); 成功,而 f( 5 ); 是编译错误。无论如何,MSVC 处理这个问题 - 它似乎延迟了 Test&lt;T&gt;::value 是值还是类型的决定,直到模板被实例化。但是,它不会对类模板的成员执行此操作。 @Sumudu:你说得对,我还将f( X() ) 调用更正为上面更明确的代码。如果 MSVC 延迟检查直到类型被实例化,则 MSVC 不符合标准。【参考方案5】:

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

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

我在其中插入空格以指示分组。

请注意,“模板名称”关键字是必需的,原因与“类型名称”相同,您无法在不知道 C/C++ 中的类型的情况下解析事物。

【讨论】:

MSVC 使用了一个非常简单的解决方案来解决这个问题:它不会解析模板函数中的代码,直到使用特定的 T 实例化模板。对于开发人员来说,这比需要大量的解决方案更令人愉快。额外的 "this->"、"typename" 和 "template" 关键字,以及许多额外的 typedef 来重新定义在基类中已经定义的名称。 (是的,是的,我知道,MSVC 不是标准的——但它更容易使用。) 然而,这种行为暴露了两个不同实例的语义与现有的糟糕 C++ 规则已经允许的更大不同的可能性。 是的,但是在花了几十个小时将我的模板代码转换为正确的标准 C++ 之后,添加了大量的句法噪音,同时对 GCC 无用的错误消息感到困惑(我最喜欢的一些:“'操作员的声明=' as non-function”、“template-parameter-lists”、“expected primary-expression before '>' token”)……我开始讨厌 C++ 的官方规则了。 @Qwertie,同意。虽然 MSVC 在许多情况下允许省略类型名可能并不严格遵守,但在我使用它的 16 年中,我从未遇到过这种行为导致生成的二进制文件出现意外行为的情况。我刚刚将一些代码移植到 GCC,并且喜欢它给出的所有错误,这些错误并没有提供任何线索表明缺少类型名是实际问题。想到另一个以“t”开头的词。

以上是关于为啥我需要在 g++ 中使用 typedef typename 而不是 VS?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Qt Creator 找到 typedef,但没有找到构建过程?

为啥 std::vector::data() 中没有使用指针 typedef?

为啥 C 程序员使用 typedef 来重命名基本类型?

将头文件中的 typedef 枚举包含在另一个头文件中

19.typedef

为啥我们要在 C 中如此频繁地对结构进行 typedef 呢?