我在哪里以及为什么必须放置“模板”和“类型名称”关键字?
在模板中,为什么我必须在相关名称上放置typename
和template
? 无论如何,依赖名称究竟是什么? 我有以下代码:
template <typename T, typename Tail> // Tail will be a UnionNode too.
struct UnionNode : public Tail {
// ...
template<typename U> struct inUnion {
// Q: where to add typename/template here?
typedef Tail::inUnion<U> dummy;
};
template< > struct inUnion<T> {
};
};
template <typename T> // For the last node Tn.
struct UnionNode<T, void> {
// ...
template<typename U> struct inUnion {
char fail[ -2 + (sizeof(U)%2) ]; // Cannot be instantiated for any U
};
template< > struct inUnion<T> {
};
};
我typedef Tail::inUnion<U> dummy
的问题是在typedef Tail::inUnion<U> dummy
行中。 我相当肯定,在inUnion
是一个独立的名称,而VC ++是完全正确的inUnion
它。 我也知道我应该可以在某处添加template
来告诉编译器inUnion是模板标识。 但究竟在哪里? 那么它是否应该假设inUnion是一个类模板,即inUnion<U>
是一个类型而不是函数?
为了解析C ++程序,编译器需要知道某些名称是否是类型。 以下示例演示:
t * f;
这应该如何解析? 对于很多语言来说,编译器不需要知道名称的含义就可以解析并基本知道一行代码的作用。 在C ++中,根据t
含义,上面的解释可能会有很大的不同。 如果它是一个类型,那么它将是一个指针f
的声明。 但是,如果它不是一种类型,它将是一个倍增。 所以C ++标准在第(3/7)段中提到:
有些名称表示类型或模板。 一般来说,无论何时遇到名称,都必须在继续解析包含它的程序之前确定该名称是否表示这些实体之一。 确定这个的过程称为名称查找。
如果t
指向模板类型参数,编译器将如何找出t::x
引用的名称? x
可以是一个静态的int数据成员,可以相乘,也可以是一个嵌套类或typedef,可以产生一个声明。 如果一个名称具有这个属性 - 只有在知道实际的模板参数之后才能查找它 - 那么它就称为依赖名称(它依赖于模板参数)。
您可能会推荐等到用户实例化模板:
让我们等待,直到用户实例化模板,然后再找出t::x * f;
的真实含义t::x * f;
。
这将作为一种可能的实施方法起作用并且实际上被标准所允许。 这些编译器基本上将模板的文本复制到内部缓冲区中,只有在需要实例化时,才会解析模板并可能检测到定义中的错误。 但是,如果模板的作者犯了错误,而不是模板的用户(可怜的同事!),其他实现选择尽早检查模板,并在实例化发生之前尽快给出定义中的错误。
所以必须有办法告诉编译器某些名称是类型的,而某些名称不是。
“typename”关键字
答案是:我们决定编译器如何解析它。 如果t::x
是一个从属名称,那么我们需要在typename
加上前缀来告诉编译器以某种方式解析它。 该标准在(14.6 / 2)说:
除非适用的名称查找找到类型名称或名称由关键字typename限定,否则假定在模板声明或定义中使用的名称取决于模板参数,而不是为类型命名。
有许多typename
不是必需的名称,因为编译器可以在模板定义中使用适用的名称查找,找出如何解析构造本身 - 例如使用T *f;
,当T
是一个类型模板参数。 但是对于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
仅前限定的名字-它是为此视为理所当然的认为不合格的名字总是已知是指类型,如果他们这样做。
正如介绍性文字暗示的那样,表示模板的名称也存在类似的问题。
“模板”关键字
请记住上面的初始报价以及标准如何对模板进行特殊处理? 我们来看看以下看似天真的例子:
boost::function< int() > f;
对于读者来说,这可能看起来很明显。 编译器不是这样。 想象一下boost::function
和f
的以下任意定义:
namespace boost { int function = 0; }
int main() {
int f = 0;
boost::function< int() > f;
}
这实际上是一个有效的表达! 它使用小于运算符来比较boost::function
和zero( int()
),然后使用大于运算符来比较结果bool
和f
。 然而,正如你可能知道的那样,现实生活中的boost::function
是一个模板,所以编译器知道(14.2 / 3):
名称查找(3.4)发现名称是模板名称后,如果该名称后面跟着<,则<始终将<始终作为模板参数列表的开始,并且永远不会作为名称,比运营商。
现在我们回到与typename
相同的问题。 如果我们在解析代码时不知道该名称是否是模板,该怎么办? 我们将需要在模板名称之前立即插入template
,如14.2/4
。 这看起来像:
t::template f<int>(); // call a function template
模板名称不仅可以在::
之后发生,也可以在->
或之后发生.
在一个班级成员访问。 您还需要在其中插入关键字:
this->template f<int>(); // call a function template
依赖
对于那些在他们的书架上有厚厚的Standardese书籍并且想知道我在说什么的人,我会谈谈标准中是如何规定的。
在模板声明中,根据用于实例化模板的模板参数,某些结构具有不同的含义:表达式可能具有不同的类型或值,变量可能具有不同的类型,或者函数调用可能最终调用不同的函数。 通常认为这样的构造依赖于模板参数。
该标准通过构造是否依赖来精确定义规则。 它将它们分成逻辑上不同的组:一个捕获类型,另一个捕获表达式。 表达式可能取决于它们的值和/或它们的类型。 所以我们附上了典型的例子:
T
) N
) (T)0
) 大多数规则是直观的,并且是递归地构建的:例如,如果N
是依赖于值的表达式或T
是依赖类型,则构造为T[N]
的类型是依赖类型。 这样做的细节可以在部分被读取(14.6.2/1
),用于依赖类型, (14.6.2.2)
进行类型相关的表达式和(14.6.2.3)
为值依赖性的表达式。
相关名称
该标准有点不清楚什么是依赖名称。 在一个简单的阅读(你知道,最不惊奇的原则),它定义为一个独立的名字是下面的函数名称的特殊情况。 但是,由于显然T::x
也需要在实例化上下文中查找,它也需要是一个独立的名称(幸运的是,在C ++中期之后,委员会已经开始研究如何解决这个混淆的定义) 。
为了避免这个问题,我对标准文本做了一个简单的解释。 在表示相关类型或表达式的所有结构中,它们的一个子集表示名称。 这些名字因此是“独立名称”。 名称可以采取不同的形式 - 标准说:
名称是标识符(2.11),运算符函数id(13.5),转换函数id(12.3.2)或模板id(14.2)的使用,表示实体或标签(6.6.4, 6.1)
标识符只是一个简单的字符/数字序列,而接下来的两个是operator +
和operator type
表单。 最后一种形式是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;
};
// ...
};
关键字template
并不总是必须出现在名称的最后部分。 它可以出现在用作范围的类名之前的中间,如下例所示
typename t::template iterator<int>::value_type v;
在某些情况下,关键字被禁止,如下所述
在依赖基类的名称上,不允许写入typename
。 假定给出的名称是类类型名称。 对于基类列表和构造函数初始值列表中的名称都是如此:
template <typename T>
struct derive_from_Has_type : /* typename */ SomeBase<T>::type
{ };
在使用声明中,不可能在last ::
之后使用template
,并且C ++委员会称不使用解决方案。
template <typename T>
struct derive_from_Has_type : SomeBase<T> {
using SomeBase<T>::template type; // error
using typename SomeBase<T>::type; // typename *is* allowed
};
C ++ 11
问题
尽管关于何时需要typename
和template
C ++ 03中的规则在很大程度上是合理的,但其制定有一个恼人的缺点
template<typename T>
struct A {
typedef int result_type;
void f() {
// error, "this" is dependent, "template" keyword needed
this->g<float>();
// OK
g<float>();
// error, "A<T>" is dependent, "typename" keyword needed
A<T>::result_type n1;
// OK
result_type n2;
}
template<typename U>
void g();
};
可以看出,即使编译器可以完全知道A::result_type
只能是int
(因此也是一个类型),并且this->g
只能是稍后声明的成员模板g
,我们仍然需要消歧关键字(即使A
明确地专用于某个地方,也不会影响该模板中的代码,所以它的含义不会受A
后来的专门化影响!)。
当前实例
为了改善这种情况,在C ++ 11中,语言跟踪何时类型引用封闭模板。 要知道,类型必须是通过使用某种形式的名称形成的,这是它自己的名称(在上面的A
, A<T>
, ::A<T>
)。 这种名称引用的类型被称为当前实例化。 如果名称形成的类型是成员/嵌套类(然后, A::NestedClass
和A
都是当前实例化),则可能有多个类型都是当前实例。
基于这个概念,该语言表示CurrentInstantiation::Foo
, Foo
和CurrentInstantiationTyped->Foo
(如A *a = this; a->Foo
)是当前实例化的成员, 如果它们被发现是类是当前的实例化或其非基础类之一(通过立即执行名称查找)。
如果限定符是当前实例化的成员,现在不再需要关键字typename
和template
。 这里要记住的关键点是A<T>
仍然是一个依赖于类型的名称(毕竟T
也是类型依赖的)。 但是A<T>::result_type
被认为是一种类型 - 编译器会“神奇地”查看这种依赖类型来解决这个问题。
struct B {
typedef int result_type;
};
template<typename T>
struct C { }; // could be specialized!
template<typename T>
struct D : B, C<T> {
void f() {
// OK, member of current instantiation!
// A::result_type is not dependent: int
D::result_type r1;
// error, not a member of the current instantiation
D::questionable_type r2;
// OK for now - relying on C<T> to provide it
// But not a member of the current instantiation
typename D::questionable_type r3;
}
};
这令人印象深刻,但我们可以做得更好吗? 该语言甚至更进一步,并且要求在实例化D::f
时实现再次查找D::result_type
(即使它在定义时间已经发现它的含义)。 当现在查找结果不同或导致模糊时,该程序不合格并且必须给出诊断。 想象一下,如果我们像这样定义C
会发生什么
template<>
struct C<int> {
typedef bool result_type;
typedef int questionable_type;
};
在实例化D<int>::f
时,编译器需要捕获错误。 因此,您可以充分利用这两个世界:“延迟”查找可以保护您,如果您可能会遇到依赖基类的问题,还可以通过“即时”查找将您从typename
和template
中解脱出来。
未知专业
在D
的代码中,名称typename D::questionable_type
不是当前实例化的成员。 相反,该语言将其标记为未知专业化的成员。 特别是,当你在做DependentTypeName::Foo
或DependentTypedName->Foo
时,情况总是如此,并且依赖类型不是当前实例化(在这种情况下,编译器可以放弃并说“我们稍后会看什么Foo
)或者它是当前的实例化,并且在它或它的非依赖基类中未找到该名称,并且还有依赖基类。
想象一下,如果我们在上面定义的A
类模板中有一个成员函数h
会发生什么
void h() {
typename A<T>::questionable_type x;
}
在C ++ 03中,语言允许捕获这个错误,因为永远不可能有实例化A<T>::h
(无论你给T
参数)的有效方法。 在C ++ 11中,该语言现在有了进一步的检查,为编译器实现此规则提供了更多理由。 由于A
没有依赖基类,并且A
没有声明成员questionable_type
,所以名称A<T>::questionable_type
既不是当前实例化的成员,也不是未知特化的成员。 在这种情况下,代码在实例化时无法有效编译,所以语言禁止一个名称,其中限定符是当前实例化,既不是未知专业化的成员,也不是当前实例的成员(但是,这种违规行为仍然不需要被诊断)。
示例和琐事
你可以在这个答案上尝试这些知识,看看上面的定义对于你在现实世界的例子中是否有意义(他们在那个答案中稍微不太详细)。
C ++ 11规则使下列有效的C ++ 03代码不合格(C ++委员会不打算这样做,但可能不会被修复)
struct B { void f(); };
struct A : virtual B { void f(); };
template<typename T>
struct C : virtual B, T {
void g() { this->f(); }
};
int main() {
C<A> c; c.g();
}
这个有效的C ++ 03代码会在实例化时将this->f
绑定到A::f
,并且一切正常。 然而,C ++ 11立即将它绑定到B::f
并且在实例化时需要进行双重检查,检查查找是否仍然匹配。 然而,当实例化C<A>::g
,支配优势规则并且查找将会找到A::f
。
前言
这篇文章的目的是作为一个易于阅读的文章的替代品。
其基本目的是一样的; 对“什么时候?”的解释 和“为什么?” typename
和template
必须应用。
typename
和template
的用途是什么?
typename
和template
可用于声明模板时以外的情况。
在C ++中有一定的上下文,必须明确地告诉编译器如何处理名称,所有这些上下文有一个共同点; 它们至少取决于一个模板参数。
我们指的是这样的名字,在解释中可能有不明确的地方, “依赖名称”。
这篇文章将提供对从属名称和两个关键字之间关系的解释。
一个SNIPPET说超过1000字
尝试解释下面的函数模板中发生了什么,或者是你自己,一个朋友,或者是你的猫; 标有(A)的声明中发生了什么?
template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ }
它可能并不像人们想象的那么容易,更具体地说,评估结果(A)在很大程度上取决于作为模板参数T
传递的类型的定义。
不同的T
可以彻底改变涉及的语义。
struct X { typedef int foo; }; /* (C) --> */ f_tmpl<X> ();
struct Y { static int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();
两种不同的情况 :
如果我们用类型X实例化函数模板,就像在(C)中一样,我们将有一个指向int的指针的声明,名字为x,但是;
如果我们用类型Y实例化模板,如(D)中所示,则(A)将由一个表达式计算123的乘积与一些已经声明的变量x的乘积。
理由
C ++标准关心我们的安全和幸福,至少在这种情况下。
为了防止实现可能遭受令人讨厌的意外,标准要求我们通过在任何我们希望将名称视为类型名称或模板的任何地方明确声明意图来理清依赖名称的含糊性, ID。
如果没有说明,依赖名称将被视为变量或函数。
如何处理相关的名字?
如果这是一部好莱坞电影,依赖性名称将是通过身体接触传播的疾病,立即影响主机使其感到困惑。 这种混淆可能会导致形成一个不合格的人员计划。
依赖名称是直接或间接依赖于模板参数的任何名称。
template<class T> void g_tmpl () {
SomeTrait<T>::type foo; // (E), ill-formed
SomeTrait<T>::NestedTrait<int>::type bar; // (F), ill-formed
foo.data<int> (); // (G), ill-formed
}
在上面的代码片段中我们有四个独立的名字:
SomeTrait<T>
的实例化,其中包括T
,并且; SomeTrait<T>
,并且; SomeTrait<T>
,并且; SomeTrait<T>
的实例化,所以看起来像成员函数模板的“数据”是间接的依赖名称。 如果编译器会将相关名称解释为变量/函数(如前所述,如果我们没有明确说明,会发生什么),则语句(E),(F)或(G)都不是有效的。
解决方案
为了让g_tmpl
有一个有效的定义,我们必须明确地告诉编译器我们期望(E)中的类型,(F)中的模板标识和类型,以及(G)中的模板标识。
template<class T> void g_tmpl () {
typename SomeTrait<T>::type foo; // (G), legal
typename SomeTrait<T>::template NestedTrait<int>::type bar; // (H), legal
foo.template data<int> (); // (I), legal
}
每当名称表示一个类型时,涉及的所有名称都必须是类型名称或名称空间,考虑到这一点,很容易发现我们在完全限定名称的开头应用了typename
。
但是, template
在这方面有所不同,因为没有办法得出结论,例如; “哦,这是一个模板,比这个其他的东西也必须是一个模板”。 这意味着我们将template
直接应用于任何我们想要的名称前面。
我可以在任何名称的前面粘贴关键字吗?
“我可以在任何名字前面加上typename
和template
吗?我不想担心它们出现的上下文......” - Some C++ Developer
标准中的规则规定,只要您处理限定名称(K),您就可以应用关键字,但如果名称不合格,则应用程序不合格(L)。
namespace N {
template<class T>
struct X { };
}
N:: X<int> a; // ... legal
typename N::template X<int> b; // (K), legal
typename template X<int> c; // (L), ill-formed
注意 :在不需要的情况下应用typename
或template
不被认为是好的做法; 只是因为你可以做点什么,并不意味着你应该这样做。
此外还有一些上下文,其中显式禁止了typename
和template
:
指定类继承的基础时
写在派生类的base-specifier-list中的每个名字都已被视为类型名称,明确指定typename
既是格式错误的,也是冗余的。
// .------- the base-specifier-list
template<class T> // v
struct Derived : typename SomeTrait<T>::type /* <- ill-formed */ {
...
};
当模板标识符是派生类的using-directive中引用的模板标识符时
struct Base {
template<class T>
struct type { };
};
struct Derived : Base {
using Base::template type; // ill-formed
using Base::type; // legal
};
上一篇: Where and why do I have to put the "template" and "typename" keywords?