顾名思义,typename有双重含意。只要你用过template,那么第一重含意一定知道,那就是声明模板的时候,我们既可以这样写:
template <class T>
也可以这样写
template <typename T>
这两种写法并没有任何区别,都是标记T可以是符合隐式接口的任何类型,包括系统预定义类型,也包括用户自定义类型。
typename的第二重含意其实不大能遇到,因为这个依赖于编译器,看下面的例子:
class SampleClass
{
public:
typedef int MyInt;
// static const int MyInt = 3;
}; int main()
{
SampleClass::MyInt *b = new int(); // 有的编译器会报错
}
MyInt来源于SampleClass内的一个重定义,MyInt等价于int,所以main函数里面实质上是int *b = new int (3),但有的编译器会报error,这是因为编译器在遇到这句代码上会产生歧义,因为它可以将MyInt作为SampleClass的一个静态对象来看(可以看程序被注释掉的代码的地方),这样就变成了一个静态对象乘以b了。这种“看法”有些不可思议,但在有些编译器上,却是优先将之视为静态变量的,而不是类型。为了解决这个二义性,在前面加上typename,这样就会强制让编译器将之视为一个类型,而不是静态变量了。像这种SampleClass::MyInt或是书上举的T::const_iterator,都是类中定义的名字,这种名字称之为dependent names(嵌套从属名称),所有dependent names都潜在具有二义性,为了消除二义性,就在前面加上typename,变成typename T::const_iterator,typename SampleClass:MyInt,这样就会解决一些看似正确却怎么也编不过的代码问题了。
使用typename有两个特例,一个是继承的时候,像下面这样:
class A: public B::NestedClass{}; // 正确
class A: public typename B::NextedClass(){}; // 错误
在继承XXX类时,即使这个类名是dependent names,也不要使用typename,因为编译器这时候显示不会将之翻译成静态成员(被继承类必定是个类型)。
另一个特例是构造函数时,对于构造成员列表,像下面这样:
A(): B::NestedClass(){} // 正确
A(): typename B::NestedClass(){} // 错误
这时候编译器也不会将之视为静态对象的,因为静态对象是不支持成员初始化列表这样的初始化形式的,所以这时的typename,反而会被编译器认为一个是BUG。
最后总结一下:
1. 声明template参数时,前缀关键字class与typename可以互换
2. 请使用关键字typename标识嵌套从属类型名称;但不得在base class lists或者member initialization list内使用typename