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

如下:

template
class Widget;     //使用”class”
template
class Widget;   //使用”typename”

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

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

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

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

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

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

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

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


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

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

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

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

再看一个例子:

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

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

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

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

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

你可能感兴趣的:(条款42: 了解typename的双重意义)