本文主要是介绍条款42:了解typename的双重意义,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1.前言
提一个问题:以下template声明式中,class和typename有什么不同:
template<class T> class Widget//使用class
template<typename T> class Widget;//使用typename
答案是两者没有不同,当我们声明template类型参数,class和typename的意义完全相同。
然而C++并不总是把class和typename视为等价,有时候一定要使用typename。为了解其时机,我们必须把template内涉及的两种名称搞清楚。
2.实例分析
假设有个template function,接收一个STL兼容容器为参数,容器内持有的对象可被赋值为ints。假设这个函数仅仅只是其第二元素值。
template<typename C>
void print2nd(const C& container)//打印容器内的第二个元素
{if(container.size()>=2){C::const_iterator iter(container.begin());//取得第一个元素的迭代器++iter;//将迭代器移往第二个元素int value=*iter;//将该元素复制到某个Intstd::cout<<value;//打印那个int}
}
在代码中特意强调两个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<typename C>
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<typename C>
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<typename C>//合法的C++代码
void print2nd(const C& container)
{if(container.size() >2){typename C::const_iterator iter(container.begin());....}
}
一般性规则很简单:任何时候当你想在template中涉及一个嵌套从属类型名称,就必须在紧邻它的前一个位置加上关键字typename。
typename只被用来验明嵌套从属类型名称;其它名称不该有它的存在。比如下面的function template,接受一个容器和一个“指向该容器”的迭代器:
template<typename C>//允许使用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<typename T>
class Derived:public Base<T>::Nested{//base class list中不允许“typename”public:explicit Derived(int x):Base<T>::Nested(x)//成员初值列表中不允许typename{typename Base<T>::Nested temp;//嵌套从属类型名称....//即不在base class list中,也不在成员初值列表中,作为一个base class修饰符需加 上typename}....
};
这样的不一致性令人烦恼。
最后看一个typename例子,那是在程序中经常看到的代表性例子。假设我们正在编写一个function template,它接受一个迭代器,而我们打算为该迭代器涉及的对象做一份local副本temp。我们可以这样写:
template<typename IterT>
void workWithIterator(IterT iter)
{typename std::iterator_traits<IterT>::value_type temp(*iter);....
}
该语句声明一个local变量(temp),使用IterT对象所指物的相同类型,并将temp初始化为iter所指物。如果IterT是个vector<int>::iterator,temp的类型就是string。由于std::iterator_traits<IterT>::value_type是个嵌套从事类型名称,所以我们必须在它之前放置typename。
如果你认为std::iterator_traits<IterT>::value_type读起来不畅快,那么可以建立一个typedef。对于traits成员名称如value_type。普遍的习惯是设定typedef名称用以代表某个traits成员名称,于是常常可以看到类似这样的local typedef:
template<typename IterT>
void workWithIterator(IterT iter)
{typedef typename std::iterator_traits<IterT>::value_type value_type;value_type temp(*iter);....
}
3.总结
(1)声明template参数时,前缀关键字class和typename可互换;
(2)请使用关键字typename标识嵌套从事类型名称;但不得在base class lists(基类)或member initialization list(成员初值列表)内以它作为base class修饰符。
这篇关于条款42:了解typename的双重意义的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!