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

1.前言

提一个问题:以下template声明式中,class和typename有什么不同:

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

答案是两者没有不同,当我们声明template类型参数,class和typename的意义完全相同

然而C++并不总是把class和typename视为等价,有时候一定要使用typename。为了解其时机,我们必须把template内涉及的两种名称搞清楚。

2.实例分析

假设有个template function,接收一个STL兼容容器为参数,容器内持有的对象可被赋值为ints。假设这个函数仅仅只是其第二元素值。

template
void print2nd(const C& container)//打印容器内的第二个元素
{

    if(container.size()>=2)
    {
        C::const_iterator iter(container.begin());//取得第一个元素的迭代器
        ++iter;//将迭代器移往第二个元素
        int value=*iter;//将该元素复制到某个Int
        std::cout<

在代码中特意强调两个local变量:iter和value,iter的类型是C::const_iterator,实际上是该参数必须取决于template参数C。template内出现的名称如果相依于某个template参数,称之为丛属名称(dependent names)。如果从属名称在class内呈现嵌套状,我们称它为嵌套从属名称(nest dependent name)。C::const_iterator就是这样一个名称。实际上它还是个嵌套从属类型名称(nested dependent type name),也就是个嵌套从属名称并且涉及某类型。

该函数内的另一个变量value,其类型为int,int是一个不依赖于任何template参数的名称。这样的名称是谓非从属名称(non-dependent names)。

嵌套从属名称有可能导致解析(paesing)困难,比如下面这个例子:

template
void print2nd(const C& container)
{

    C::const_iterator* x;
    ...
}

看起来好像我们声明x为一个local变量,它是个指针,指向一个C::const_iterator。但它之所以被那么认为,只因为我们已经知道C::const_iterator是个类型。如果C::const_iterator不是个类型呢?如果C有个static成员变量而碰巧被命名为const_iterator,或如果x碰巧是个global变量名称?那样的话上述代码就不再是声明一个local变量,而是一个相乘动作:C::const_iterator乘以x。

在我们知道C是什么之前,没有任何办法可以知道C::const_iterator是否为一个类型。而当编译器开始解析template print2nd时,尚未确知C是什么东西。C++有个规则可以解析(resolve)此一歧义状态:如果解析器在template中遭遇一个嵌套从属名称,它便假设这名称不是个类型,除非你告诉了它。所以缺省情况下嵌套从事名称不是类型。见下面例子:

template
void print2nd(const C& container)
{
    if(container.size()>=2)
    {
        C::const_iterator iter(container.begin());//该名称被假设为非类型
        ...
    }

}

现在应该很清楚为什么这不是有效的C++代码了。iter声明式只有在C::const_iterator是个类型时才合理,但我们并没有告诉C++所它是,于是C++假设它不是。若要矫正这个形势,我们必须告诉C++说C::const_iterator是个类型。只要紧邻它之前放置关键字typename即可:

template//合法的C++代码
void print2nd(const C& container)
{

    if(container.size() >2)
    {

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

一般性规则很简单:任何时候当你想在template中涉及一个嵌套从属类型名称,就必须在紧邻它的前一个位置加上关键字typename。

typename只被用来验明嵌套从属类型名称;其它名称不该有它的存在。比如下面的function template,接受一个容器和一个“指向该容器”的迭代器:

template//允许使用typename
void f(const C& container,typename C::iterator iter);//不允许使用typename,一定要使用typename

上述的C并不是嵌套从事类型名称,所以container时并不需要以typename为前导,但C::iterator是个嵌套从属类型名称,所以必须以typename为前导。

“typename”必须作为嵌套从事类型名称的前缀词,typename不可以出现在base classes list内的嵌套从属类型名称之前,也不可在member initialization(成员初值列)中作为base classes修饰符。比如:

template
class Derived:public Base::Nested{//base class list中不允许“typename”

    public:
        explicit Derived(int x):Base::Nested(x)//成员初值列表中不允许typename
        {
            typename Base::Nested temp;//嵌套从属类型名称
            ....//即不在base class list中,也不在成员初值列表中,作为一个base class修饰符需加        
                      上typename

        }
        ....
};

这样的不一致性令人烦恼。

最后看一个typename例子,那是在程序中经常看到的代表性例子。假设我们正在编写一个function template,它接受一个迭代器,而我们打算为该迭代器涉及的对象做一份local副本temp。我们可以这样写:

template
void workWithIterator(IterT iter)
{

    typename std::iterator_traits::value_type temp(*iter);
    ....
}

该语句声明一个local变量(temp),使用IterT对象所指物的相同类型,并将temp初始化为iter所指物。如果IterT是个vector::iterator,temp的类型就是string。由于std::iterator_traits::value_type是个嵌套从事类型名称,所以我们必须在它之前放置typename

如果你认为std::iterator_traits::value_type读起来不畅快,那么可以建立一个typedef。对于traits成员名称如value_type。普遍的习惯是设定typedef名称用以代表某个traits成员名称,于是常常可以看到类似这样的local  typedef:

template
void workWithIterator(IterT iter)
{

    typedef typename std::iterator_traits::value_type  value_type;
    value_type temp(*iter);
    ....
}

3.总结

(1)声明template参数时,前缀关键字class和typename可互换;

(2)请使用关键字typename标识嵌套从事类型名称;但不得在base class lists(基类)或member initialization list(成员初值列表)内以它作为base class修饰符。

你可能感兴趣的:(c++,开发语言)