为啥我需要在 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<KEY,VALUE>::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<T>::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,但没有找到构建过程?