在模板中,我必须将typename和template放在从属名称上的位置和原因是什么?
到底什么是从属名称
我有以下代码:
模板<;typename T,typename Tail>;//Tail也将是一个UnionNode。
结构UnionNode:公共尾部{
// ...
模板<;typename U>;结构单元{
//问:在这里哪里添加typename/模板?
typedef Tail::inUnion<;U>;dummy;
};
模板<;gt;结构单元<;T>{
};
};
模板<;typename T>;//对于最后一个节点Tn。
结构UnionNode<;T、 无效>;{
// ...
模板<;typename U>;结构单元{
char fail[-2+(sizeof(U)%2)];//无法为任何U实例化
};
模板<;gt;结构单元<;T>{
};
};
我遇到的问题是typedef Tail::inUnion<;U>;虚拟行。我相当肯定,inUnion是一个依赖的名称,VC++完全正确地扼杀了它。
我还知道我应该能够在某个地方添加模板,告诉编译器inUnion是一个模板id。但是具体在哪里呢?然后,它是否应该假设inUnion是一个类模板,即inUnion<;U>命名类型而不是函数
(关于我的C++11答案,请参见此处)
为了分析C++程序,编译器需要知道某些名称是否为类型。以下示例说明:
t*f;
这应该如何解析?对于许多语言,编译器不需要知道名称的含义就可以解析代码,并且基本上知道一行代码的作用。然而,C++中,根据代码 的含义,可以产生很大不同的解释。如果它是一个类型,那么它将是指针f的声明。然而,如果它不是一个类型,它将是一个乘法。因此,C++标准在第(3/7)段:
有些名称表示类型或模板。通常,每当遇到名称时,在继续解析包含该名称的程序之前,必须确定该名称是否表示这些实体之一。确定这一点的过程称为名称查找
如果t引用模板类型参数,编译器将如何找出名称t::x引用的是什么x可以是可以相乘的静态int数据成员,也可以是可以生成声明的嵌套类或typedef如果名称具有此属性(在知道实际模板参数之前无法查找),则称之为依赖名称(取决于模板参数)。
您可能建议等待用户实例化模板:
让我们等到用户实例化模板,然后再找出
t::x*f的真正含义
这将起作用,并且实际上是标准允许的一种可能的实现方法。这些编译器基本上将模板的文本复制到内部缓冲区中,并且仅当需要实例化时,才解析模板并可能检测定义中的错误。但是,其他实现没有因为模板作者的错误而困扰模板的用户(可怜的同事!),而是选择尽早检查模板,并在实例化之前尽快给出定义中的错误
因此,必须有一种方法告诉编译器某些名称是类型,而某些名称不是类型
“typename”关键字
答案是:我们决定编译器应该如何解析它。如果t::x是一个依赖名称,那么我们需要用typename作为前缀,告诉编译器以某种方式解析它。该标准规定为(14.6/2):
模板声明或定义中使用的、依赖于模板参数的名称是
假定不命名类型,除非适用的名称查找找到类型名称或名称是限定的
通过关键字typename
有许多名称不需要typename,因为编译器可以通过模板定义中适用的名称查找,找出如何解析构造本身-例如使用T*f,当T是类型模板参数时。但是对于t::x*f要成为一个声明,它必须写成typename t::x*f。如果省略关键字并且名称被视为非类型,但当实例化发现它表示类型时,编译器会发出通常的错误消息。有时,在定义时给出误差:
//t::x被视为非类型,但作为一个表达式,以下内容忽略了
//运算符或分隔两个名称的分号。
t::xf;
语法只允许在限定名称之前使用typename——因此,如果非限定名称引用类型,则通常认为它们引用类型是理所当然的
正如介绍性文本所暗示的,对于表示模板的名称也存在类似的问题
“模板”关键字
还记得上面的初始引用以及标准如何要求对模板进行特殊处理吗?让我们举一个看起来很无辜的例子:
boost::function<;int()>;F
这对人类读者来说可能是显而易见的。对于编译器来说并非如此。想象一下以下任意定义的boost::function和f:
名称空间boost{int function=0;}
int main(){
int f=0;
boost::函数<;int()>;f;
}
这实际上是一个有效的表达式!它使用小于运算符将boost::function与零(int())进行比较,然后使用大于运算符将得到的bool与f进行比较。然而,正如您可能知道的那样,boost::function在现实生活中是一个模板,因此编译器知道(14.2/3):
在名称查找(3.4)发现某个名称是模板名称后,如果该名称后跟一个<;,这个<;是
始终作为模板参数列表的开头,决不作为名称后跟小于
接线员
现在我们回到了与typename相同的问题。如果在解析代码时我们还不知道名称是否是模板呢?我们需要按照14.2/4的规定,在模板名称前插入template。这看起来像:
t::模板f<;int>;(); // 调用函数模板
模板名称不仅可以出现在:之后,还可以出现在->或。您还需要在此处插入关键字:
此->;模板f<;int>;(); // 调用函数模板
依赖关系
对于那些书架上有厚厚的标准书的人,如果他们想知道我到底在说什么,我将介绍一下标准中是如何规定的
在模板声明中,根据用于实例化模板的模板参数,某些构造具有不同的含义:表达式可能具有不同的类型或值,变量可能具有不同的类型,或者函数调用可能最终调用不同的函数。这种构造通常被称为依赖于模板参数
该标准通过构造是否依赖来精确定义规则。它将它们分成逻辑上不同的组:一个捕获类型,另一个捕获表达式。表达式可能取决于其值和/或类型。因此,我们附上了一些典型的例子:
- 依赖类型(例如:类型模板参数
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)、操作员功能id(13.5)、转换功能id(12.3.2)或表示实体或标签(6.6.4、6.1)的模板id(14.2)的使用
标识符只是一个简单的字符/数字序列,而接下来的两个是操作符+和操作符类型表单。最后一个表单是模板名称<;参数列表>。所有这些都是名称,按照标准中的常规用法,名称还可以包括quali