Skip to content

Latest commit

 

History

History
73 lines (57 loc) · 2.89 KB

File metadata and controls

73 lines (57 loc) · 2.89 KB

条款42:了解typename的双重意义

在以下声明中,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表示嵌套从属类型名称;但不得在基类列表或成员初始化列表内以他作为基类修饰符。