typename的双重意义
当我们在定义类模板(或函数模板)时,通常会用下面两种方式来表示,那么class和typename有什么不同呢?
template<class T> class widget; // class
template<typename T> class widget; // typename
答案:没有不同,当我们声明template类型参数,不论使用关键字class或typename,意义完全相同。
然而在C++中class和 typename并不总是等价的。有时候一定得使用typename:
假设我们有个函数模板,传入STL容器作为参数,容器内的对象可被赋值为int。这个函数的功能仅仅只是打印其第二个元素值。
template<typename C>
void print2nd (const C& container) //打印容器内的第二元素
{
if (container.size() >= 2)
{
C::const_iterator iter (container.begin()); //取得第一元素的迭代器
++iter; //将iter移往第二元素
int value = *iter; //将该元素复制到某个int.
std::cout << value; //打印那个int.
}
}
int main()
{
std::vector<int> v = {1, 2, 3, 4};
print2nd(v);
return 0;
}
看上去这份代码并没有什么问题,但编译会报错:
报错的原因是:我们认为iter的类型是C::const_iterator,实际是什么类型取决于template参数C。但这里编译器并不能确定是一个类型,还是类中的静态成员变量,于是出现了报错。
这样可能并不好理解,我们来举个简单的例子:
template<typename C>
void print2nd(const C& container)
{
C::const_iterator * x;
...
}
class Bar
{
const static int const_iterator = 10;
};
C::const_iterator * x;
这行代码看起来像是声明x为一个局部变量,它是个指针,指向一个C::const_iterator类型。但它之所以被这样认为,是因为我们默认C::const_iterator是个类型。
如果C::const_iterator不是某个类型呢?如果C有个static成员变量而碰巧被命名为const_iterator,或x碰巧是个全局变量名称呢?那样的话上述代码就不再是声明一个局部变量,而是一个相乘动作:C::const_iterator乘以x。
template内出现的名称如果相依于某个template参数,称之为从属名称(dependent names)。如果从属名称在 class内呈嵌套状,我们称它为嵌套从属名称(nested dependent name)。C::const_iterator就是这样一个名称。实际上它还是个嵌套从属类型名称(nesteddependent typename),也就是个嵌套从属名称并且指涉某类型。
现在应该很清楚为什么会报错了吧。iter声明只有在C::const_iterator是个类型时才合理,但我们并没有告诉C++说它是,于是C++假设它不是。若要改变这种情况,我们必须告诉C++说C::const_iterator是个类型。只要在它之前放置关键字typename即可:
template<typename C>
void print2nd (const C& container)//这是合法的C++代码
{
if (container.size() >=2)
{
typename C::const_iterator iter (container.begin());
...
}
...
}
一般性规则很简单:任何时候当你想要在template中指涉一个嵌套从属类型名称,就必须在紧临它的前一个位置放上关键字typename。
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
...
}
...
};
注意:
- 声明template参数时,前缀关键字class和 typename可互换。
- 使用关键字typename标识嵌套从属类型名称;
- 在 基类列表 或 初始化列表 内不使用typename。