(这里也有我c++ 11的答案)
为了解析c++程序,编译器需要知道某些名称是否是类型。下面的例子说明:
t * f;
这应该如何解析?对于许多语言来说,编译器不需要知道名称的含义来进行解析,并且基本上知道一行代码的操作。然而,在c++中,根据t的含义不同,上面的解释可能会有很大的不同。如果它是一个类型,那么它将是一个指针f的声明。但是如果它不是一个类型,它将是一个乘法。所以c++标准在第(3/7)段说:
有些名称表示类型或模板。通常,无论何时遇到一个名称,在继续解析包含该名称的程序之前,都有必要确定该名称是否表示这些实体之一。确定这一点的过程称为名称查找。
如果t指的是一个模板类型参数,编译器将如何找出一个名称t::x指的是什么?X可以是一个静态int数据成员,它可以被相乘,也可以同样是一个嵌套类或类型定义,可以产生一个声明。如果一个名称具有这个属性——在知道实际的模板参数之前无法查找它——那么它被称为依赖名称(它“依赖”于模板形参)。
你可能会建议等待用户实例化模板:
让我们等待用户实例化模板,然后再找出t::x * f;的真正含义。
这是可行的,实际上标准允许作为一种可能的实现方法。这些编译器基本上将模板的文本复制到内部缓冲区中,只有当需要实例化时,它们才解析模板,并可能检测定义中的错误。但是,其他实现不会因为模板作者的错误而打扰模板的用户(可怜的同事!),而是选择在早期检查模板,并在实例化发生之前尽快在定义中给出错误。
所以必须有一种方法告诉编译器某些名称是类型,而某些名称不是。
“typename”关键字
答案是:我们决定编译器应该如何解析它。如果t::x是一个依赖名称,那么我们需要在它前面加上typename来告诉编译器以某种方式解析它。标准是14.6/2:
在模板声明或定义中使用且依赖于模板形参的名称为
假设不指定类型,除非适用名称查找找到类型名称或名称是限定的
通过关键字typename。
有许多名称不需要使用typename,因为编译器可以通过模板定义中适用的名称查找,找出如何解析结构本身——例如,当T是类型模板形参时,使用T *f;。但对于t::x * f;作为声明,它必须写成typename t::x *f;。如果省略关键字并且名称被认为是非类型,但当实例化发现它表示类型时,编译器会发出通常的错误消息。有时,结果误差在定义时给出:
// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;
语法只允许typename放在限定名之前——因此,如果限定名引用类型,那么它们总是被认为是已知的。
类似的问题也存在于表示模板的名称中,正如介绍文本所暗示的那样。
template关键字
还记得上面最初的报价吗?以及标准如何要求对模板进行特殊处理?让我们看看下面这个看似无辜的例子:
boost::function< int() > f;
对于人类读者来说,这可能是显而易见的。但编译器却不是这样。想象一下boost::function和f的任意定义:
namespace boost { int function = 0; }
int main() {
int f = 0;
boost::function< int() > f;
}
这实际上是一个有效的表达式!它使用小于操作符将boost::function与0 (int())进行比较,然后使用大于操作符将结果bool与f进行比较。然而,您可能已经知道,boost::function在现实生活中是一个模板,因此编译器知道(14.2/3):
在名称查找(3.4)发现一个名称是template-name之后,如果该名称后面跟着一个<,则<为
总是作为模板参数列表的开头,从不作为名称后面跟着小于号
操作符。
现在我们又回到了与typename相同的问题。如果在解析代码时还不知道名称是否是模板,该怎么办?我们需要在模板名称之前插入模板,就像14.2/4所指定的那样。这看起来像:
t::template f<int>(); // call a function template
模板名称不仅可以出现在::后面,还可以出现在->或。在类成员访问中。你还需要在这里插入关键字:
this->template f<int>(); // call a function template
依赖关系
对于那些书架上有厚厚的标准书的人,他们想知道我到底在说什么,我将谈谈标准中是如何指定的。
在模板声明中,一些构造有不同的含义,这取决于你使用什么模板实参来实例化模板:表达式可能有不同的类型或值,变量可能有不同的类型,函数调用可能最终调用不同的函数。这种构造通常被认为依赖于模板参数。
该标准精确地定义了一个结构是否依赖的规则。它将它们分为逻辑上不同的组:一个捕获类型,另一个捕获表达式。表达式可能取决于它们的值和/或类型。因此,我们有,加上典型的例子:
依赖类型(例如:类型模板参数T)
值依赖表达式(例如:非类型模板形参N)
依赖类型的表达式(例如:转换为类型模板参数(T)0)
大多数规则都是直观的,并且是递归构建的:例如,如果N是一个依赖值的表达式,或者T是一个依赖类型,那么构造为T[N]的类型就是一个依赖类型。有关依赖类型的详细信息请参见(14.6.2/1)节,依赖类型的表达式请参见(14.6.2.2)节,依赖值的表达式请参见(14.6.2.3)节。
相关的名字
标准对从属名称的定义有些不清楚。简单地读一下(您知道,最小惊喜原则),它定义的所有依赖名称都是下面函数名的特殊情况。但是由于T::x显然也需要在实例化上下文中查找,所以它也需要是一个依赖名称(幸运的是,从c++ 14中期开始,委员会已经开始研究如何修复这个令人困惑的定义)。
为了避免这个问题,我对标准文本进行了简单的解释。在所有表示依赖类型或表达式的构造中,它们的子集表示名称。因此,这些名称是“依赖名称”。一个名字可以有不同的形式——标准是这样说的:
名称是标识符(2.11)、operator-function-id(13.5)、conversion-function-id(12.3.2)或template-id(14.2)的使用,表示实体或标签(6.6.4,6.1)。
标识符只是字符/数字的普通序列,而接下来的两个是操作符+和操作符类型形式。最后一个形式是template-name <argument list>。所有这些都是名称,按照标准中的常规用法,名称还可以包含限定符,表示应该在哪个名称空间或类中查找名称。
依赖于值的表达式1 + N不是名称,但N是。作为名称的所有依赖结构的子集称为依赖名称。然而,在模板的不同实例化中,函数名可能有不同的含义,但不幸的是,这个通用规则不会捕捉到函数名。
依赖函数名
这不是本文主要关注的问题,但仍然值得一提:函数名是单独处理的异常。标识符函数名不是由其本身决定的,而是由调用中使用的类型依赖参数表达式决定的。在例子f((T)0)中,f是一个依赖名称。在标准中,这是在(14.6.2/1)中规定的。
其他注释和示例
在足够多的情况下,我们同时需要typename和template。您的代码应该如下所示
template <typename T, typename Tail>
struct UnionNode : public Tail {
// ...
template<typename U> struct inUnion {
typedef typename Tail::template inUnion<U> dummy;
};
// ...
};
关键字模板并不总是必须出现在名称的最后一部分。它可以出现在用作作用域的类名之前的中间,如下面的示例所示
typename t::template iterator<int>::value_type v;
在某些情况下,关键字是禁止的,具体如下所示
在依赖基类的名称上不允许写入typename。假设给出的名称是类类型名。基类列表和构造函数初始化列表中的名称都是这样:
template <typename T>
struct derive_from_Has_type: /* typename */ SomeBase<T>::type
{};
在using-declarations中,不可能在last::之后使用模板,c++委员会说不做解决方案。
template <typename T>
struct derive_from_Has_type: SomeBase<T> {
使用SomeBase<T>::模板类型/ /错误
使用typename SomeBase<T>::type;// typename *is* allowed
};