了解typename的双重意义

使用模板时,可以用typename,也可以用class

 template<class T> class Widget;
    template<typename T> class Widget;
 
 
  • 1
  • 2

两者没有什么不同。作为template的类型参数,意义完全相同。在使用习惯上来说,很多人喜欢使用typename,因为这暗示参数并非一定要是个class类型。

C++有时不会把class和typename等价。有时候一定要用typename;为了了解原理,先来谈谈在template内指涉(refer to)的两种名称。

用一个例子来说明,现在有个template function,接收STL兼容容器为参数,容器内对象可被赋值为int,这个函数打印第二个元素的值

 template<typename C>
    void print2nd(const C& container)
    {
        if(container.size()>=2)
        {
            C::const_iterator iter(container.begin());
            ++iter;
            int valaue=*iter;
            std::cout<<value;
        }
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

现在解释一下两个local变量iterate和value。iterate类型为C::const_iterator,实际是什么取悦于template参数C。template内出现的名称如果依赖于某个template参数,称这个名称为从属名称(dependent names)。如果从属名称在class内呈嵌套状,称之为嵌套从属名称(nested dependent name)。C::const_iterator就是这样的嵌套从属名称(nested dependent name),且指涉某类型。value是int类型,不依赖template参数,称之为非从属名称(non-dependent names)。

嵌套从属名称有可能导致解析(parsing)困难,例如在print2dn这样做

template<typename C>
void print2nd(const C& container)
{
    C::const_iterator* x;
    ……
}

表面上看是声明了一个local变量x,x类型是只需C::const_iterator的指针,这是因为我们直到C::const_iterator是个类型。如果不是这样呢?例如,C内有个static成员变量碰巧命名为const_iterator,或x是个global变量;这样的话就是一个相乘动作:const::const_iterator乘以x。编写C++解析器的人必须操心这样的输入。

在直到C是什么之前,不能确定C::cont_iterator是否为一个类型。当编译器解析template print2nd时,不知道C是什么东西。C++有个规则可以解析这个歧义状态:如果解析器在template中遭遇到一个嵌套从属名称,它便假设这个明白不是个类型,除非你告诉它是。所以缺省情况下,嵌套从属名称不是类型。这个规则还有个例外,稍后再提。

再来看看print2nd,C::const_iterator iter只有在C::const_iterator是个类型时才合理,但是C++缺省认为它不是。要矫正这个形式,我们必须告诉C++C::const_iterator是个类型,在其前面放置关键字typename即可

 template<typename C>
    void print2nd(const C& container)
    {
        if(container.size()>=2)
        {
            typename C::const_iterator iter(container.begin());
            ++iter;
            int valaue=*iter;
            std::cout<<value;
        }
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

“typename必须作为嵌套从属类型名称的前缀词”这一规则的例外是:typename不可以出现在base class list内的嵌套从属类型名称之前,也不可以在member initialization list(成员初值列)中作为base class修饰符。

 template<typename T>
    class Derived: public Base<T>::Nested{ //base class list中不允许typename
    public:
        explicit Derived(int x)
        :Base<T>::Nested(x)  //mem init.list中不允许typename
        {
            typename Base<T>::Nested temp; //嵌套从属名称类型
            ……  //既不在base class list中,也不再mem init list中,作为base class修饰符要加上typename
        }
        ……
    };
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

再来看一个typename例子。一个function template接受一个迭代器,我们打算为该迭代器指涉的对象做一份local复件(副本)temp

    template<typename IterT>
    void workWithIterator(IterT iter)
    {
        typename::std::iterator_traits<IterT>::value_type temp(*iter);
        ……
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

typename::std::iterator_traits<IterT>::value_type是标准traits class(**条款**47)的一种运用。std::iterator_traits<IterT>::value_type是个嵌套从属类名称,所以在其前面放置typename。如果使用std::iterator_traits<IterT>::value_type感觉太长不习惯,可以考虑建立一个typedef,对于traits成员名称,普遍的习惯是设定typedef名称代表某个traits成员名称。

 template<typename IterT>
    void workWithIterator(IterT iter)
    {
        typedef typename::std::iterator_traits<IterT>::value_type value_type;
        value_type temp(*iter);
        ……
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

最后提一下,typename相关规则在不同编译器上有不同的实践。这意味着typename和嵌套从属名称之间的互动,也许会在移植性方面带来头疼的问题。

总结

  • 声明template参数时,前缀关键字class和typename可以互换。
  • 使用关键字typename标识嵌套从属类型名称;但不得在base class list(基类列)或member initialization list(成员初值列)内以它作为base class修饰符。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值