C++箴言:领略typename的两个寄义
副标题#e#
问题:在下面的 template declarations(模板声明)中 class 和 typename 有什么差异?
template<class T> class Widget; // uses "class"
template<typename T> class Widget; // uses "typename"
谜底:没什么差异。在声明一个 template type parameter(模板范例参数)的时候,class 和 typename 意味着完全沟通的对象。一些措施员更喜欢在所有的时间都用 class,因为它更容易输入。其他人(包罗我本人)更喜欢 typename,因为它体现着这个参数不须要是一个 class type(类范例)。少数开拓者在任何范例都被答允的时候利用 typename,而把 class 保存给仅接管 user-defined types(用户界说范例)的场所。可是从 C++ 的概念看,class 和 typename 在声明一个 template parameter(模板参数)时意味着完全沟通的对象。
然而,C++ 并不老是把 class 和 typename 视为等同的对象。有时你必需利用 typename。为了领略这一点,我们不得不接头你会在一个 template(模板)中涉及到的两种名字。
假设我们有一个函数的模板,它能取得一个 STL-compatible container(STL 兼容容器)中持有的能赋值给 ints 的工具。进一步假设这个函数只是简朴地打印它的第二个元素的值。它是一个用糊涂的要领实现的糊涂的函数,并且就像我下面写的,它甚至不能编译,可是请将这些事先放在一边——有一种要领能发明我的愚蠢:
template<typename C> // print 2nd element in
void print2nd(const C& container) // container;
{
// this is not valid C++!
if (container.size() >= 2) {
C::const_iterator iter(container.begin()); // get iterator to 1st element
++iter; // move iter to 2nd element
int value = *iter; // copy that element to an int
std::cout << value; // print the int
}
}
我突出了这个函数中的两个 local variables(局部变量),iter 和 value。iter 的范例是 C::const_iterator,一个依赖于 template parameter(模板参数)C 的范例。一个 template(模板)中的依赖于一个 template parameter(模板参数)的名字被称为 dependent names(依赖名字)。当一个 dependent names(依赖名字)嵌套在一个 class(类)的内部时,我称它为 nested dependent name(嵌套依赖名字)。C::const_iterator 是一个 nested dependent name(嵌套依赖名字)。实际上,它是一个 nested dependent type name(嵌套依赖范例名),也就是说,一个涉及到一个 type(范例)的 nested dependent name(嵌套依赖名字)。
#p#副标题#e#
print2nd 中的另一个 local variable(局部变量)value 具有 int 范例。int 是一个不依赖于任何 template parameter(模板参数)的名字。这样的名字以 non-dependent names(非依赖名字)闻名。(我想不通为什么他们不称它为 independent names(无依赖名字)。假如,像我一样,你发明术语 "non-dependent" 是一个令人厌恶的对象,你就和我发生了共识,可是 "non-dependent" 就是这类名字的术语,所以,像我一样,转转眼睛放弃你的自我主张。)
nested dependent name(嵌套依赖名字)会导致理会坚苦。譬喻,假设我们越发愚蠢地以这种要领开始 print2nd:
template<typename C>
void print2nd(const C& container)
{
C::const_iterator * x;
...
}
这看上去仿佛是我们将 x 声明为一个指向 C::const_iterator 的 local variable(局部变量)。可是它看上去如此仅仅是因为我们知道 C::const_iterator 是一个 type(范例)。可是假如 C::const_iterator 不是一个 type(范例)呢?假如 C 有一个 static data member(静态数据成员)可巧就叫做 const_iterator 呢?再假如 x 可巧是一个 global variable(全局变量)的名字呢?在这种环境下,上面的代码就不是声明一个 local variable(局部变量),而是成为 C::const_iterator 乘以 x!虽然,这听起来有些愚蠢,但它是大概的,而编写 C++ 理会器的人必需思量所有大概的输入,甚至是愚蠢的。
直到 C 成为已知之前,没有任何步伐知道 C::const_iterator 到底是不是一个 type(范例),而当 template(模板)print2nd 被理会的时候,C 还不是已知的。C++ 有一条法则办理这个歧义:假如理会器在一个 template(模板)中碰着一个 nested dependent name(嵌套依赖名字),它假定谁人名字不是一个 type(范例),除非你用其它方法汇报它。缺省环境下,nested dependent name(嵌套依赖名字)不是 types(范例)。(对付这条法则有一个破例,我待会儿汇报你。)
记着这个,再看看 print2nd 的开头:
template<typename C>
void print2nd(const C& container)
{
if (container.size() >= 2) {
C::const_iterator iter(container.begin()); // this name is assumed to
... // not be a type
#p#分页标题#e#
这为什么不是正当的 C++ 此刻应该很清楚了。iter 的 declaration(声明)仅仅在 C::const_iterator 是一个 type(范例)时才有意义,可是我们没有汇报 C++ 它是,而 C++ 就假定它不是。要想转变这个形势,我们必需汇报 C++ C::const_iterator 是一个 type(范例)。我们将 typename 放在紧挨着它的前面来做到这一点:
template<typename C> // this is valid C++
void print2nd(const C& container)
{
if (container.size() >= 2) {
typename C::const_iterator iter(container.begin());
...
}
}
通用的法则很简朴:在你涉及到一个在 template(模板)中的 nested dependent type name(嵌套依赖范例名)的任何时候,你必需把单词 typename 放在紧挨着它的前面。(重申一下,我待会儿要描写一个破例。)
typename 应该仅仅被用于标识 nested dependent type name(嵌套依赖范例名);其它名字不该该用它。譬喻,这是一个取得一个 container(容器)和这个 container(容器)中的一个 iterator(迭代器)的 function template(函数模板):
template<typename C> // typename allowed (as is "class")
void f(const C& container, // typename not allowed
typename C::iterator iter); // typename required
C 不是一个 nested dependent type name(嵌套依赖范例名)(它不是嵌套在依赖于一个 template parameter(模板参数)的什么对象内部的),所以在声明 container 时它不必被 typename 前置,可是 C::iterator 是一个 nested dependent type name(嵌套依赖范例名),所以它必须被 typename 前置。
"typename must precede nested dependent type names"(“typename 必需前置于嵌套依赖范例名”)法则的破例是 typename 不必前置于在一个 list of base classes(基类列表)中的可能在一个 member initialization list(成员初始化列表)中作为一个 base classes identifier(基类标识符)的 nested dependent type name(嵌套依赖范例名)。譬喻:
template<typename T>
class Derived: public Base<T>::Nested {
// base class list: typename not
public: // allowed
explicit Derived(int x)
: Base<T>::Nested(x) // base class identifier in mem
{
// init. list: typename not allowed
typename Base<T>::Nested temp; // use of nested dependent type
... // name not in a base class list or
} // as a base class identifier in a
... // mem. init. list: typename required
};
这样的抵牾很令人讨厌,可是一旦你在经验中得到一点履历,你险些不会在意它。
让我们来看最后一个 typename 的例子,因为它在你看到的真实代码中具有代表性。假设我们在写一个取得一个 iterator(迭代器)的 function template(函数模板),并且我们要做一个 iterator(迭代器)指向的 object(工具)的局部拷贝 temp,我们可以这样做:
template<typename IterT>
void workWithIterator(IterT iter)
{
typename std::iterator_traits<IterT>::value_type temp(*iter);
...
}
不要让 std::iterator_traits<IterT>::value_type 吓倒你。那仅仅是一个 standard traits class(尺度特性类)的利用,用 C++ 的说法就是 "the type of thing pointed to by objects of type IterT"(“被范例为 IterT 的工具所指向的对象的范例”)。这个语句声明白一个与 IterT objects 所指向的对象范例沟通的 local variable(局部变量)(temp),并且用 iter 所指向的 object(工具)对 temp 举办了初始化。假如 IterT 是 vector<int>::iterator,temp 就是 int 范例。假如 IterT 是 list<string>::iterator,temp 就是 string 范例。因为 std::iterator_traits<IterT>::value_type 是一个 nested dependent type name(嵌套依赖范例名)(value_type 嵌套在 iterator_traits<IterT> 内部,并且 IterT 是一个 template parameter(模板参数)),我们必需让它被 typename 前置。
假如你以为读 std::iterator_traits<IterT>::value_type 令人讨厌,就想象谁人与它沟通的对象来代表它。假如你像大大都措施员,对多次输入它感想惊骇,那么你就需要建设一个 typedef。对付像 value_type 这样的 traits member names(特性成员名),一个通用的老例是 typedef name 与 traits member name 沟通,所以这样的一个 local typedef 凡是界说成这样:
template<typename IterT>
void workWithIterator(IterT iter)
{
typedef typename std::iterator_traits<IterT>::value_type value_type;
value_type temp(*iter);
...
}
#p#分页标题#e#
许多措施员最初发明 "typedef typename" 并列不太调和,但它是涉及 nested dependent type names(嵌套依赖范例名)法则的一个公道的附带功效。你会相当快地习惯它。你究竟有着强大的念头。你输入 typename std::iterator_traits<IterT>::value_type 需要几多时间?
作为竣事语,我应该提及编译器与编译器之间对环绕 typename 的法则的执行环境的差异。一些编译器接管必须 typename 时它却缺失的代码;一些编译器接管不许 typename 时它却存在的代码;尚有少数的(凡是是老旧的)会拒绝 typename 呈此刻它必须呈现的处所。这就意味着 typename 和 nested dependent type names(嵌套依赖范例名)的交互浸染会导致一些轻微的可移植性问题。
Things to Remember
·在声明 template parameters(模板参数)时,class 和 typename 是可交流的。
·用 typename 去标识 nested dependent type names(嵌套依赖范例名),在 base class lists(基类列表)中或在一个 member initialization list(成员初始化列表)中作为一个 base class identifier(基类标识符)时除外。