43.了解typename

template声明式

 
在template声明式中使用class与typename有什么不同吗?

1
2
template<class T> class Widget;
template<typename T> class Widget;

答案是没有不同。有的人喜欢用class,因为少打了字,有的人则习惯于使用typename,因为这暗示了用户参数并非一定要是一个class类型。
但这种相同仅仅只是在template声明式内,c++并不总是把class与template视为等价,有时候我们非得使用typename不可。


template内refer to的名称

 
假设有一个template function,接受一个STL容器为参数,容器内持有的对象可被赋值为int。进一步假设这个函数只是打印其第二元素值。我们暂且不去管这个函数有多么地无聊甚至不能编译,其实现如下:

1
2
3
4
5
6
7
8
9
template <typename C>
void print2nd (const C& container){
if(container.size()>=2){
C::const_iterator iter(container.begin());
++iter;
int value = *iter;
cout << value;
}
}

显而易见,这段代码中有两个local变量iter和value。

嵌套从属名称

iter的类型是C::const_iterator,实际上它是什么取决于template参数C.template内出现的名字如果依赖于某个template参数,则称为从属名称。如果从属名称在class内程嵌套状(::)则称之为嵌套从属名称。
value是一个int,不依赖于任何template参数,于是它叫做非从属名称。


嵌套从属名称的风险

嵌套从属名称可能会导致解析(parsing)困难。举例而言,我们如果写了一个比刚才那个更加愚蠢的程序:

1
2
3
4
template <typename C>
void print2nd(const C& container){
C::const_iterator * x;
}

可能有人对这种写法并无异议:我们声明了一个local变量x,它是一个指针,指向C::const_iterator。但如果C中有一个static成员恰好被命名为const_iterator,又或者x恰好是一个global变量。那这个代码就是执行了一次相乘操作。这简直丧心病狂。

解决方案

C++对于这种歧义状态有一种解析规则:如果编译器在template中遭遇了一个嵌套从属名称,它便假设该名称并非类型,除非你主动告诉它这是类型。(这个情况有一个小小的例外)。
想要程序正确运行,上文中的实例应该改为:

1
typename C::const_iterator iter;

任何时候你想在template内指涉一个嵌套从属类型名称,就必须在它之前放上一个typename.(再说一次,有一个小例外)另外,typename只被用来验明嵌套从属类型名称,其它名称没必要用它。

例外情况

typename不可以出现在base classes list内的嵌套从属类型名称之前,也不可以在member initialization list中作为base class修饰符。举例而言:

1
2
3
4
5
6
7
8
9
template<typename T>
class Derived:public Base<T>::Nested{// base classes list中禁止出现typename
public:
explicit Derived(int x)
:Base<T>::Nested(x){//member initialization list中禁止出现typename
typename Base<T>::Nested temp;//需要typename
}
}
}

typename与typedef

由于某些声明式实在太长,所以讲typedef与typename相结合可能是一种不错的主意:

1
2
3
4
template<typename IterT>
void workWithIterator(IterT iter){
typedef typename std::iterator_traits<IterT>::value_type value_type;
}


总结

  1. 声明template参数时,class与typename意义相同。
  2. 必须用typename标识嵌套从属类型名称,但在base class lists && member initialization list中禁止使用。