顾名思义,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

04-26 09:47