在学习STL的过程当中,经常会遇到一些让人难以理解的C++代码,比如:
typedef typename std::vector<T>::size_type size_type;
看起来它应该是定义一个类型别名,但是typedef使用方式应该是typedef + 原类型名 + 新类型名(typedef具体使用方法可移步 typedef和#define的用法与区别 ),为何此处多了个typename?typename又是什么东西?
typedef char* PCHAR;
只记得泛型编程(GP)当中,若是对模板类/函数进行定义时,可以使用template或者template,且这两者是等价的。
那么如上语句中的typename关键字是起了什么作用呢?
1. std::vector::size_type 是什么?
查看侯捷的《STL源码剖析》一书,其为:
template <class T,class Alloc=alloc>
class vector{
public:
//...
typedef size_t size_type;
//...
};
原来vector::size_type是vector中的嵌套类型,其实际等价于STL中大面积使用的可跨平台的size_t类型。
- 使用 typename 关键字的意义
将语句中的typename关键字抛开不看,则语句为:
typedef std::vector::size_type size_type;
1
可以感觉到是对std::vector::size_type这个类型进行重命名。那么为什么要加上typename这个关键字呢?
原来,其中T是模板中的类型参数,它只有等到模板实例化时才会知道是哪种类型,更不用说T内部的size_type。所以对于限定依赖名(解释见补充)std::vector::size_type而言,无法判断其是静态成员变量/函数还是嵌套类型,这样会造成语句的二义性,这是不能够容忍的。
若是在std::vector::size_type这个类型前加上typename这一关键字,指明这是一个嵌套类型,而不是T的静态成员或静态成员函数,消除了二义性。
- 总结
根据以上的分析,可以知道语句的含义和作用是:
typedef创建了存在类型的别名,而typename告诉编译器std::vector::size_type是一个类型而不是一个成员。
拓展:为何会有typename关键字
若是不加 typename 时,在如下模板定义会造成语句的二义性:
template <class T>
void foo() {
T::iterator * iter;
// ...
}
struct ContainsAnotherType {
static int iterator;
// ...
};
然后如此实例化foo的类型参数:
foo<ContainsAnotherType>();
那么,T::iterator * iter;被编译器实例化为ContainsAnotherType::iterator * iter;。在这里,前面是一静态成员变量而不是类型,那么这便成了一个乘法表达式,不过iter在这里没有定义,编译器会报错:
error C2065: ‘iter’ : undeclared identifier
但如果iter是一个全局变量,那么这行代码将完全正确,它是表示计算两数相乘的表达式,返回值被抛弃。
如上造成了同一行代码能以两种完全不同的方式解释,而且在模板实例化之前,完全没有办法来区分它们。
为解决该问题,C++标准委员会引入了typename关键字,使得模板类/函数在实例化前就区分其内部的依赖名(例如:vector::iterator viter;, 也称dependent names)为静态成员变量/函数还是嵌套类型。
补充:类外部访问类内成员或名称、(非)限定名、(非)依赖名
一、 类外部访问类内成员或名称
在类作用域概念中,在类外部访问类中的名称时,可以使用类作用域操作符::,形如MyClass::name的调用通常存在三种:①静态数据成员、②静态成员函数和③嵌套类型:
struct MyClass {
static int A;
static int B();
typedef int C;
}
MyClass::A, MyClass::B, MyClass::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>
class MyClass {
int i;
vector<int> vi;
vector<int>::iterator vitr;
T t;
vector<T> vt;
vector<T>::iterator viter;
};
因为是内置类型,所以类中前三个定义的类型在声明这个模板类时就已知。然而对于接下来的三行定义,只有在模板实例化时才能知道它们的类型,因为它们都依赖于模板参数T。因此,T, vector和vector::iterator称为依赖名。前三个定义叫做非依赖名。
更为复杂一点,如果用了typedef T U; U u;,虽然T没再出现,但是U仍然是依赖名。由此可见,不管是直接还是间接,只要依赖于模板参数,该名称就是依赖名。
参考博客:
typedef typename 的作用