在以下声明中,class和typename没有什么不同:
template <class T> class Widget;
template <typename T> class Widget;
从C++的角度看,声明template参数时,不论使用关键字class和typename,意义完全相同。
然而C++并不总把class和typename视为等价,有时你必须使用typename。
假设我们有个模板函数,接收一个STL容器为参数,容器内持有的对象可被赋值为int。这个函数仅仅打印其第二元素。这是一个无聊的函数,它不能通过编译:
template <typename C>
void print2nd(const C& container) {
if (container.size() >= 2) {
C::const_iterator iter(container.begin());
++iter;
int value = *iter;
std::cout << value;
}
}
iter的类型是 C::const_iterator
,实际是什么取决于template参数C。template内出现的名称如果依赖于某个模板参数,称之为从属名称(dependent name)。如果从属名称在class内被嵌套,我们称它为嵌套从属名称(nested dependent name)。C::const_iterator
就是这样一个名称。实际上它还是个从属类型名称(nested dependent type name),也就是个嵌套从属名称并且是某种类型。
嵌套从属名称有可能导致解析困难。举个例子,假设我们令print2nd更愚蠢些,这样开头:
template <typename C>
void print2nd(const C& container) {
C::const_iterator* x;
...
}
我们可以认为x是个指向 C::const_iterator
的指针,也可以认为 const_iterator
是C的static成员变量,表达式执行乘法运算。
在我们知道C是什么之前,没有任何办法可以知道 C::const_iterator
是否为一个类型。C++有个规则可以解析此歧义状态:如果解析器在template中遇到嵌套从属名称,它便假设这名称不是给类型,除非你告诉它是。
为了消除歧义,我们必须告诉C++说 C::const_iterator
是个类型。只要在它之前放置关键字 typename
即可:
template <typename C>
void print2nd(const C& container) {
if (container.size() >= 2) {
typename C::const_iterator iter(container.begin());
...
}
}
typename只被用来表示嵌套从属类型名称,其他名称不该有它存在。这一规则的例外是,typename不可出现在基类列表内的嵌套从属类型名称之前,也不可以在成员初始化列表中作为基类修饰符,例如:
template <typename T>
class Derived : public Base<T>::Nested { // base class list
public:
explicit Derived(int x) : Base<T>::Nested(x) { // member init list
typename Base<T>::Nested temp;
...
}
...
};
请记住
- 声明template参数时,前缀关键字class和typename意义相同
- 请使用关键字typename表示嵌套从属类型名称;但不得在基类列表或成员初始化列表内以他作为基类修饰符。