C++ 了解typename的双重意义

20180329 C++ 了解typename的双重意义


在一个template的声明式中,eg:
template<class T> class Widget;  //使用"class"
template<typename T> class Widget;   //使用"typename"


class和typename没有不同,即当我们声明template类型参数,class和typename的意义完全相同。有些人喜欢用class,因为可以少打几个字;有些人喜欢用typename,因为它暗示参数并非一定是class类型;有些人在接受任何类型时用typename,而在接受用户自定义类型时保留旧式的class。


在以下两种情况下,typename要特殊对待:




情况一:
假设有一个模板函数(template function)接受一个STL兼容容器为参数,容器内持有的对象可被赋值为ints,进一步假设这个函数仅仅打印其第二个函数(这个函数可能不会通过编译,先不管这个了),下面是一种实现方式:
template<typename C>
void print2nd(const C& container)//打印容器内的第二个元素
{                             //注意:这不是有效的C++代码
  if(container.size() >= 2)
  {
    C::const_iterator iter(container.begin());//取得第一个元素的迭代器
    ++iter;                           //将iter移往第二个元素
    int value = *iter;               //将该元素复制到某个int
    std::cout<<value;//打印那个int
  }
}




现在我们观察两个局部(local)变量iter和value:
iter的类型是C::const_iterator,它是什么,取决于模板(template)参数C。在模板中出现的名称若依赖于某个模板(template)参数,我们称之为从属名称(dependent names)。若从属名称在类内呈嵌套状,我们称它为嵌套从属名称(nested dependent name),C::const_iterator就是这样一个名称。实际上它还是个嵌套从属类型名称(nested dependent type name),也就是个嵌套从属名称并且指涉某个类型。
而print2nd里的另一个局部变量value,其类型为int,int是一个并不依赖任何模板参数的名称,我们管这样的名称叫非从属名称(non-dependent names)。
嵌套从属名称有可能会导致解析(parsing)困难。比如我们让print2nd的函数体执行以下操作:
template<typename C>
void print2nd(const C& container)
{
  C::const_iterator* x;
  ...
}


看起来似乎我们要声明一个局部变量x,x是一个指针,指向一个C::const_iterator,但我们之所以这么任务,是因为声明已经知道了C::const_iterator是个类型,若C::const_iterator不是个类型呢?若C有个static成员变量而碰巧被命名为const_iterator呢?抑或如果x碰巧是个全局变量名称呢?那样的话上述代码就不再是声明为一个局部变量了,而是一个相乘操作:C::const_iterator 乘以 x。这听起来很疯狂,但这也是有可能的啊!




在我们知道C是什么之前,没有任何办法可以知道C::const_iterator是否为一个类型,而当编译器开始解析template print2nd时,尚未知道C是什么。C++有一个规则可以解析(resolve)这一歧义状态:若解析器在模板中遭遇一个嵌套从属名称,它便假设这个名称不是个类型,除非你告诉它是。所以缺省情况下嵌套从属名称不是类型,此规则有个例外,稍后再说。
在上例代码中,我们必须告诉C::const_iterator是个类型。只要紧邻它之前防止关键字typename即可:
template<typename C>
void print2nd(const C& container)
{
  if(container.size() >= 2)
  {
    typename C::const_iterator iter(container.begin());
    ...
  }
}


一般性规则很简单:任何时候要在模板中指涉一个嵌套从属类型名称时,就必须在紧邻它的前一个位置加上关键字typename(例外的情况,一会再说)。




typename只被用来验证嵌套从属类型名称;其他名称不该有他的存在。如下面这个函数模板,接受一个容器和一个“指向该容器”的迭代器:
template<typename C>     //允许使用“typename”(或“class”)
void f(const C& container,    //不允许使用“typename”
       typename C::iterator iter);   //一定要使用“typename”


上述的C并不是嵌套从事类型名称(它并非嵌套于任何“取决于模板参数”的东西里),所以声明container时并不需要以typename为前导,但C::iterator是个嵌套从属类型名称,所以必须以typename为前导。




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


template<typename T>
class Derived:public Base<T>::Nested   //基类列表中不允许 typename
{
public:
  explicit Derived(int x)
  :Base<T>::Nested(x)      //成员初值列中 不允许 typename
  {
    typename Base<T>::Nested temp;
    /*
    对于嵌套从属类型名称,当既不基类列表中也不在成员初值列中
    作为一个基类修饰符需要加上 typename。   
    */
    ...
  }
  ...
};




再看另外一个例子:
//撰写一个函数模板,它接受一个迭代器,为该迭代器指涉的对象做一份局部复件temp
template<typename IterT>
void workWithIterator(IterT iter)
{
  typename std::iterator_traits<IterT>::value_type temp(*iter);
  ...
}




std::iterator_traits<IterT>::value_type的意思是:类型为IterT的对象所指之物的类型,这个语句声明一个局部变量temp,使用IterT对象所指物的相同类型,将temp初始化为iter所指物。若IterT是vector<int>::iterator,temp的类型就是int。
由于std::iterator_traits<IterT>::value_type是个嵌套从属类型名称(value_type被嵌套于iterator_traits<IterT>里而IterT是个模板参数),所以必须在他之前放置typename。


如果你认为std::iterator_traits<IterT>::value_type读起来不顺畅,也可以这样写:


void workWithIterator(IterT iter)
{
  typedef typename std::iterator_traits<IterT>::value_type;
 value_type temp(*iter);
  ...
}


这就看你的使用习惯了。


注意:
1)声明模板参数时,前缀关键字class和typename可以互换;
2)请使用关键字typename标识嵌套从属类型名称;但不可以在基类列(base class lists)或成员初值列(member initialization list)里以它作为基类修饰符。








































































































  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值