typename的双重意义
Posted coolcpp
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了typename的双重意义相关的知识,希望对你有一定的参考价值。
《Effective C++》 Note
下面有两种template声明:
template<class T> class Widget();
template<typename T> class Widget();
当我们声明template类型参数,class和typename没有什么不同。但是使用typename可以暗示参数并非一定得是个class类型。
C++并不总是把class和typename视为等价,有时候你一定得使用typename。
如下这个例子:
#include <iostream>
#include <vector>
template<typename C>
void print2nd(const C& container)
{
if (container.size() >= 2)
{
C::const_iterator iter(container.begin()); //取得第一元素的迭代器
++iter; //迭代器移向第二元素
int value = *iter; //将第二元素复制到value
std::cout << value; //输出value
}
}
int main()
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
print2nd(v);
return 0;
}
这个模板函数,接受一个STL兼容容器为参数,容器内持有的对象可被赋值为int。这个函数仅仅打印传进来容器的第二元素值。在VS 2015上可成功编译并运行,别的环境不知。
iter的类型是C::const iterator,到底是什么取决于template参数C。template内出现的名称若相依于某个template参数,称为从属名称(dependent names),若从属名称在class内呈嵌套状,称其为嵌套从属名称(nested dependent name)。C::const_iterator便是个嵌套从属类型名称。
value是个local变量,其类型为int,int并不倚赖任何template参数的名称,这样的名称称为非从属名称(non-dependent names)。
嵌套从属类型有可能导致解析(parsing)困难。假设令print2nd这样:
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是否为一个类型。C++有个规则可以解析这一歧义状态:如果解析器在template中遭遇一个嵌套从属名称,它便假设这名称不是个类型,除非你告诉它。所以缺省情况下嵌套从属名称不是类型。
所以上面的例子不是有效的C++代码,虽然它在这个机器上编译运行都无问题。iter声明式只有在C::const_iterator是个类型时才合理,但C++并不知道它是个合理的类型。我们需要告诉它,只需在紧临它之前放置关键字typename:
template<typename C>
void print2nd(const C& container)
{
if(container.size() >= 2)
{
typename C::const_iterator iter(container.begin());
.....
}
}
规则为:任何时候当你想要在template中指涉一个嵌套从属类型名称,就必须在紧临它的前一位置放上关键字typename。
typename只被用来验明嵌套从属类型名称:其他名称不该有它的存在。
template<typename C> //允许使用"typename"或"class"
void f(const C& container //不允许使用"typename"
typename C::iterator iter); //一定要使用"typename"
上述规则的例外是,typename不可以出现在base classes 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"
{
//嵌套从属类型,既不在base class list中也不在mem.init.list中
//作为一个base class修饰符需加上typename
typename Base<T>::Nested temp;
}
}
假设我们在写一个function template,它接受一个迭代器,而我们打算为该迭代器的对象做一份local复件temp。可以这样写:
template<typename, IterT>
void workWithIterator(IterT iter)
{
typename std::iterator_traits<IterT>::value_type temp(*iter);
....
}
由于std::iterator_traits<IterT>::value_type是个嵌套从属类型名称,
value_type被嵌套于iterator_traits<IterT>之内而IterT是个template参数,所以我们必须将其前放置typename。
以上是关于typename的双重意义的主要内容,如果未能解决你的问题,请参考以下文章