条款42、typename的双重意义

来源:互联网 发布:java io深入 编辑:程序博客网 时间:2024/06/08 11:52
如下:
template<classT>class Widget;     //使用”class”template<typenameT>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 val=*it;                   std::cout<<val;         }}

上述代码强调两个局部变量,val 和it 。it类型为c::const_itertor,实际类型取决于模版参数C。模版内出现的名称如果相依于某个模版参数,称之为从属名(depedent names)。如果该名称在class内嵌套,则称嵌套从属名称(nestde dependent name),如C::const_iterator(嵌套从属类型).
另一个local变量是val,为int类型。不依赖于任何模版参数名。称之为非从属类型(non-dependent names).
但嵌套从属名称可能会导致解析困难,如下:
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或typenamevoid 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);…..}

typenamestd::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标志嵌套从属类型名。但不得在基类列表和成员初始值列内以它做为基类的修饰符。
0 0
原创粉丝点击