typename的一些用法和注意问题
Posted litifeng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了typename的一些用法和注意问题相关的知识,希望对你有一定的参考价值。
一些关键概念
在我们揭开真实原因的面纱之前,先保持一点神秘感,因为为了更好的理解C++标准,有几个重要的概念需要先行介绍一下。
限定名和非限定名
限定名(qualified name),故名思义,是限定了命名空间的名称。看下面这段代码,cout和endl就是限定名:
#include <iostream> int main() { std::cout << "Hello world!" << std::endl; }
cout和endl前面都有std::,它限定了std这个命名空间,因此称其为限定名。
如果在上面这段代码中,前面用using std::cout;或者using namespace std;,然后使用时只用cout和endl,它们的前面不再有空间限定std::,所以此时的cout和endl就叫做非限定名(unqualified name)。
依赖名和非依赖名
依赖名(dependent name)是指依赖于模板参数的名称,而非依赖名(non-dependent name)则相反,指不依赖于模板参数的名称。看下面这段代码:
template <class T> struct MyClass { int i; vector<int> vi; vector<int>::iterator vitr; T t; vector<T> vt; vector<T>::iterator viter; };
因为是内置类型,所以类中前三个定义的类型在声明这个模板类时就已知。然而对于接下来的三行定义,只有在模板实例化时才能知道它们的类型,因为它们都依赖于模板参数T。因此,T, vector<T>和vector<T>::iterator称为依赖名。前三个定义叫做非依赖名。
更为复杂一点,如果用了typedef T U; U u;,虽然T没再出现,但是U仍然是依赖名。由此可见,不管是直接还是间接,只要依赖于模板参数,该名称就是依赖名。
typename的标记作用:
结束以上两个个概念的讨论,让我们接着揭开typename的神秘面纱。
一个例子
在Stroustrup起草了最初的模板规范之后,人们更加无忧无虑的使用了class很长一段时间。可是,随着标准化C++工作的到来,人们发现了模板这样一种定义:
template <typename T> void foo() { T::iterator iter; // ... }
在上例代码中T本身已经是模板的类型参数,它只有等到模板实例化时才会知道是哪种类型,更不用说由T定义的内部的iterator
。
编译器不知道第三行代码到底是,定义一个变量还是定义一个新类型,这样同一行代码能以两种完全不同的方式解释,而且在模板实例化之前,完全没有办法来区分它们,这绝对是滋生各种bug的温床。这时C++标准委员会再也忍不住了,与其到实例化时才能知道到底选择哪种方式来解释以上代码,委员会决定引入一个新的关键字,这就是typename
。
c++标准定义到:
对于用于模板定义的依赖于模板参数的名称,只有在实例化的参数中存在这个类型名,或者这个名称前使用了typename
关键字来修饰,编译器才会将该名称当成是类型。除了以上这两种情况,绝不会被当成是类型。
因此,如果你想直接告诉编译器T::iterator
是类型而不是变量,只需用typename
修饰:
template <typename T> void foo() { typename T::iterator iter; // ... }
通过加上typename关键字,这样编译器就可以确定T::iterator
是一个类型,而不再需要等到实例化时期才能确定,因此消除了歧义。
以上是关于typename的一些用法和注意问题的主要内容,如果未能解决你的问题,请参考以下文章
为啥 GraphQL 片段在查询中需要 __typename?