1.模板类型参数申明
使用模板时,在申明模板类型参数时,我们经常有如下两种申明方式:
代码语言:javascript复制//方式一
template <class T> CTest;
//方式二
template <typename T> CTest;
这两种写法并没有任何区别,都是标记T是模板类型参数,可以是任何类型,包括用户自定义类型或是语言的基本类型。虽然而这在用于模板类型参数申明时的作用完全相同,但是仍建议使用typename,因为typename的字面意义即表示类型名称,更加符合其语义。而class则多用于类的申明,而非模板类型参数。当然,如果原有项目中均使用class,那么请与原有项目风格保持一致。
2.嵌套从属类型名称(nested dependent type name)须使用typename
在template声明式中,用于申明模板类型参数时,class与typename作用完全一致。当然,因为前者字符数少,可能会有人倾向于使用class。但有些时候,typename却是不可被替换成class的。
假设,有个template function,接受了一个容器C为参数,这个容器内部定义了一个类型a,如果了解STL,想必会知道容器内部会定义5种迭代器型别(iterator_category, value_type, difference_type, pointer, reference),这里a可以是其中任何一个,也可是用户自定义类型,但假设不是基本类型。现在看这个template function的定义:
代码语言:javascript复制template<typename C> //建议使用typename
void func(const C& container)
{
//...
C::a* x;
//...
}
考虑上面模板定义式中间那行代码,对于开发者而言,可以很明显的推断出代码的含义,x是一个a类型的指针。但是对于编译器而言,在没有明确C的定义之前,是无法确定a是一个嵌套于C中的类型,其实a可能是C内一个静态成员变量,假设x刚好是一个全局变量,那么这行代码也可以由编译器解析为两数相乘。
编译器面对这样的代码如何处置?编译器会这样处理:如果在template中遇到一个嵌套从属类型名称,即依赖于模板类型参数的类型,放在上面例子中对应C::a,C::a依赖于模板类型参数C,它便假设这个名称不是个类型,除非显示告诉编译器。所以缺省情况下嵌套从属类型名称不是类型。如何显示告知呢,可以使用typename,这是它的第二重意义。在此对之前假设a不是基本类型,因为基本类型并不依赖其它类型。
正确的函数模板定义如下:
代码语言:javascript复制template<typename C>
void func(const C& container)
{
//...
typename C::a * x; //在行首加上typename即可
//...
}
到这里,想必对typename的第二重含义已经基本了解,这也是typename与class的不同之处,模板中当出现嵌套从属类型名称时须使用typename帮助编译识别。
3.规则之外
模板中当出现嵌套从属类型名称时须使用typename帮助编译识别,这一规则也存在例外。typename不可以出现在base classes list(所继承的基类成员列表)内的嵌套从属类型名称之前,也不可以在member initialization list(成员初始化列表)中作为base class修饰符。例如:
代码语言:javascript复制template<typename T>
class Derived: public Base<T>::Nested //基类成员列表中不允许使用typename
{
public:
explicit Drived(int x) : Base<T>::Nexted(x) //成员初始化列表中不允许使用typename
{
typename Base<T>:: Nexted temp; //这里可以
}
4.小结
(1)申明模板参数时,class和typename可以互换,建议使用typename,因为从字面更加符合语义; (2)嵌套从属类型名称(nested dependent type name)须使用typename来标识,但不能在所继承的基类成员列表和成员初始化列表中使用。
参考文献
[1]Effective C :改善程序与设计的55个具体做法(第3版 中文版)[M].条款四十二:了解typename的双重意义