Effective C++条款42:模板与泛型编程——了解typename的双重意义

一、意义①

  • 意义①:typename可以在template中声明类型参数
  • 在template中声明类型参数时,typename和class是等价的,两者都可以
  • 例如:
//两者是等价的

template<class T> class Widget;

template<typename T> class Widget;

二、意义②

  • 意义②:可以用来声明某种类型

演示说明

  • 现在我们有一个模板,其接受一个STL容器类型,然后打印容器中的第二个元素的值,但是这个模板可能会产生错误。代码如下:
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来说,其是容器container的迭代器类型const_iterator,其依赖于template参数C的类型,因此我们称const_iterator为从属名称(意义为:模板内的一个名称依赖于template的某个参数,那么其就是从属名称)
      • 当从属名称属于class内时,我们称其为嵌套从属名称。因此上面的const_iterator就是一种嵌套从属名称
    • 非从属名称:上面的value变量,其类型就是int,不依赖于其他任何东西,因此我们称int为非从属名称
  • 上面的代码之所以可能会产生错误,与从属名称有关系。我们现在考虑这样的一个情况:
    • 我们修改print2nd模板,假设在其中定义一个迭代器指针,代码如下
    • 但是编译器可能会这样理解:const_iterator不是一个嵌套从属名称,而是一个类的static成员变量,x为一个局部变量。因此下面的代码可能会被编译器认为是两个变量在相乘
template<typename C>

void print2nd(const C& container)

{

    //...

    //const_iterator可能被编译器理解为C的static成员变量,x为一个变量,下面是两个变量的相乘

    C::const_iterator* x;
    
    //...

}
  • 正确的做法是为嵌套从属名称加上一个关键字typename,这样可以显式地告诉编译器某种东西是一个类型,而不是其他东西。代码如下:
template<typename C>

void print2nd(const C& container)

{

    if (container.size() >= 2) {

        //使用typename,显式告诉编译器,const_iterator是一个类型

        typename C::const_iterator iter(container.begin());

        ++iter;

        int value = *iter;

        std::cout << value;
        
    }

}
  • 有了上面的规则之后,我们可以编写下面的模板函数,其接受一个容器类,和该容器的迭代器为参数。代码如下:
template<typename C>

void print2nd(const C& container,typename C::const_iterator iter)

{

    //...

}

 

typename的一个例外

  • 当typename用来声明类型时,其不可以出现在两个地方:
    • ①基类的派生列表中
    • ②构造函数的成员初始化列中中
  • 我们假设Nested是基类中的一种类型。例如:
template<typename T>

class Derived :public Base<T>::Nested //此处不可以使用typename

{

public:

    explicit Derived(int)

    :Base<T>::Nested(x)//此处不可以使用typename

    {

        typename Base<T>::Nested temp; //此处可以使用typename

    }

};

 

typename在traits机制中的运用

  • 假设我们现在有这样一个函数模板,其接受一个迭代器类型,我们打算在函数中为迭代器所指的对象做一份副本。代码如下:
template<typename IterT>

void workWithIterator(IterT iter)

{

    typename std::iterator_traits<IterT>::value_type temp(*iter);

    //...

}
  • 此处我们使用到了iterator_traits<>模板类,其实一种traits类(参阅条款47)。我们传递给其一个迭代器类型为其进行实例化,那么我们就可以通过其value_type萃取出迭代器所指的容器的类型。例如:
    • 如果IterT是list<string>::iterator,那么value_type就代表string,temp的类型就是string
    • 如果IterT是vector<int>::iterator,那么value_type就代表int,temp的类型就是int
  • 因为value_type也是一种内嵌类型,因此我们需要使用typename声明其是一种类型
  • 如果上面的代码比较复杂,那么我们还可以搭配typedef来使用。例如:
template<typename IterT>

void workWithIterator(IterT iter)

{

    typedef typename std::iterator_traits<IterT>::value_type value_type; //为类型声明别名

    value_type temp(*iter); //使用类型定义变量

    //...

}

 

  • 关于typename的移植性问题:某些编译器接受typename,而可能有少数编译器不接受typename。因此其存在有移植性

三、总结

  • 声明template参数时,前缀关键字class和typename可互换
  • 请使用关键字typename标识嵌套从属类型名称;但不得在base class lists(基类列)或member initialization list(成员初值列)内以它作为base class修饰符
上一篇:Rosalind 42-Reversal Distance


下一篇:用户授权