美文网首页C++
条款42: 了解typename的双重意义

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

作者: 未来已来_1cab | 来源:发表于2019-01-30 21:51 被阅读13次

    如下:

    template<class T>
    class Widget;     //使用”class”
    template<typename T>
    class Widget;   //使用”typename”
    

    虽然使用了不同的关键字,但从C++的角度来说,声明模板参数时,关键字class 和typename意义完全相同。 然而C++并不把class和typename视为等价。有时你一定得使用typename。

    考虑下面一个函数模板,接受一个STL兼容容器为参数,容器内持有对象可赋值为ints。假设这个函数仅仅是打印其第二元素值。(这个函数无意义,只是为了论述)

    template<typename C>
    void print2(const C& container)  //打印容器第二元素
    {       
        if(container.size()>2)         
        {                   
            C::const_iterator  it(container.begin());  //第一个元素迭代器                   
            ++it;   
            int value = *it;               
            std::cout<<value;
        }
    }
    

    上述代码强调两个局部变量,val 和it 。it类型为C::const_itertor,实际类型取决于模版参数C。模版内出现的名称如果相依于某个模版参数,称之为从属名(depedent names)。如果该名称在class内嵌套,则称嵌套从属名称(nested dependent name),如C::const_iterator(嵌套从属类型)。嵌套从属名称可能会导致解析困难,如下:

    template<typename C>
    void print2(const C& container)  //打印容器第二元素
    {         
        C::const_iterator * x;
        …
    }
    

    似乎是声明一个指针x,其类型为C::const_itertor(这是在我们已经知道它是个类型的情况下)。 但如果C::const_itertor不是个类型呢?如C刚好有个static成员变量而刚好命名为const_iterator,或x碰巧是个全局变量名?在这种情况下上面情况也许就不是声明一个局部变量,而是一个相乘的动作。 很明显,在知道C之前,无从知道C::const_iterator是否为类型。C++规则:如果解析在模版中遇到一个嵌套从属名称,它便假设这个名称不是类型。所以在缺省情况下,在缺省情况下嵌套从属名不是类型。再看:

    template<typename C>
    void print2(const C& container)  //打印容器第二元素
    {
        if(container.size()>2)         
        {                   
            C::const_iterator   it(container.begin());   //该名被假设为非类型….
        }
    }
    

    很显然上述代码不合法,it只有在C::const_iterator声明为类型时才合理,正确的做法是:

    template<typename C>
    void print2(const C& container)  //打印容器第二元素
    {         
        if(container.size()>2)         
        {                   
            typename  C::const_iterator it(container.begin());   //该名被假设为非类型….
        }
    }
    

    一般规则为:想在模板中涉及一个嵌套从属类型名,就必须在其前面加个类型关键字typename。


    例外情况:typename只用来验明嵌套从属类型名称,其他名不该有它存在。如下函数模版接受一个容器和一个“指向该容器”的迭代器

    template<typename C>      //可使用class或typename
    void f(const C& container     //不能使用typename         
        typename C::iteratorit);                   //一定使用typename
    

    上述C并不是嵌套从属类型名,(它并不嵌套于任何“取决于template参数”的东西内),所以声明container并不需要以typename为前导,而C::iterator必须以typename为前导。 “typename必须做为嵌套从属类型前缀”这一规则的例外是:typename不可以出现在基类列表内的嵌套从属类型前,也不可在成员初始列中作为基类修饰符。如:

    template<typename T>
    class Derived: public Base<T>::Nested  //基类列表中不允许typename
    {
    public:
        explicitDerived(int x)
        :Base<T>::Nested(x)  //成员初始值列,不允许typename
        {
            typenameBase<T>::Nested tmp;  //嵌套从属类型名称
            //不在基类列表也不在成员初始值列中,作为基类修饰符必须加上typename
            …
        }
    };
    

    再看一个例子:

    template<typename IterT>
    void workWithIterator(IterT iter) {
        typenamestd::iterator_traits<IterT>::value_type tmp(*iter);
    …..
    }
    

    typename std::iterator_traits<IterT>::value_type,相当于说“类型为IterT的对象所指之物的类型”。这个语句声明一个局部变量,使用IterT的对象所指的类型,将tmp初始化为iter所指物。比如IterT类型是vector<int>::itertor,则tmp类型就是int.是vector<string>::iterator,tmp类型就为string。由于std::iterator_traits<IterT>::value_type是个嵌套从属类型,故在其之前放置typename。 当然,这个定义太长,我们可以使用typedef,如下:

    template<typename IterT>
    void workWithIterator(IterT iter) {
        typedef typenamestd::iterator_traits<IterT>::value_type value_type;
        value_type tmp(*iter);
    …..
    }
    

    由于typename的相关规则在不同编译器上有不同实现,故在移植性方面也会有小问题。

    需要记住的:
    1、声明模版参数时,关键字class和typename可互换
    2、使用typename标志嵌套从属类型名。但不得在基类列表和成员初始值列内以它做为基类的修饰符。

    相关文章

      网友评论

        本文标题:条款42: 了解typename的双重意义

        本文链接:https://www.haomeiwen.com/subject/yjkhsqtx.html