在模板中,我必须在哪里以及为什么必须将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 ++ 在窒息它是非常正确的。我也知道我应该能够在某处添加template
来告诉编译器 inUnion 是一个模板 ID。但到底在哪里?然后它应该假设 inUnion 是一个类模板,即inUnion<U>
命名一个类型而不是一个函数?
为了解析 C ++ 程序,编译器需要知道某些名称是否是类型。以下示例演示了:
t * f;
该怎么解析?对于许多语言,编译器不需要知道名称的含义就可以解析并基本知道代码行的作用。在 C ++ 中,上面的内容可以根据t
含义产生截然不同的解释。如果它是一个类型,那么它将是一个指针f
的声明。但是,如果它不是一个类型,它将是一个乘法。所以 C ++ 标准在段落(3/7)中说:
某些名称表示类型或模板。通常,只要遇到名称,就必须在继续解析包含它的程序之前确定该名称是否表示这些实体之一。确定此过程的过程称为名称查找。
如果t
引用模板类型参数,编译器将如何找出名称t::x
引用的内容? x
可以是一个静态 int 数据成员,可以成倍增加,或者同样可以是一个可以产生声明的嵌套类或 typedef。如果名称具有此属性 - 在实际模板参数已知之前无法查找 - 那么它将被称为依赖名称 (它 “取决于” 模板参数)。
您可能建议等到用户实例化模板:
让我们等到用户实例化模板,然后找出
t::x * f;
的真正含义t::x * f;
。
这将是标准作为可能的实施方法,并且实际上是允许的。这些编译器基本上将模板的文本复制到内部缓冲区中,并且只有在需要实例化时,它们才会解析模板并可能检测定义中的错误。但是,在模板的作者发生错误的情况下,其他实现选择尽早检查模板并在实例化甚至发生之前尽快给定错误,而不是困扰模板的用户(可怜的同事!)。
所以必须有一种方法告诉编译器某些名称是类型而某些名称不是。
答案是: 我们决定编译器应该如何解析它。如果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;
}
这实际上是一个有效的表达 !它使用 less-than 运算符将boost::function
与零( int()
)进行比较,然后使用 greater-than 运算符将得到的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),operator-function-id(13.5),conversion-function-id(12.3.2)或 template-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
{ };
在使用声明中,不可能在最后一个::
之后使用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 ++ 03 中关于何时需要typename
和template
的规则在很大程度上是合理的,但是它的公式有一个令人讨厌的缺点
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();
};
可以看出,我们需要 disambiguation 关键字,即使编译器可以完全弄清楚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
)都是当前实例化的成员, 如果它们被发现是 a 的成员作为当前实例化的类或其非依赖基类之一(通过立即执行名称查找)。
如果限定符是当前实例化的成员,则现在不再需要关键字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
, Dominance Rule适用,查找将找到A::f
。
前言
这篇文章是litb 帖子的一个易于阅读的替代品。
根本目的是一样的; 对 “何时?” 的解释和 “为什么?” 必须应用
typename
和template
。
typename
和template
的目的是什么? typename
和template
可用于声明模板之外的其他情况。
在C ++中有某些上下文,其中必须明确地告诉编译器如何处理名称,并且所有这些上下文都有一个共同点; 它们依赖于至少一个模板参数 。
我们指的是这样的名称,在解释中可能存在歧义,因为; “ 从属名称 ”。
这篇文章将解释依赖名称和两个关键字之间的关系。
尝试解释以下功能模板中发生的事情,无论是对自己,朋友,还是你的猫; 标记为( 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 )中一样,我们将声明一个名为x的指向 int的指针 ,但是;
如果我们用类型Y实例化模板,如( D )中所示,( A )将由一个表达式组成,该表达式计算123 的乘积乘以一些已经声明的变量x 。
C ++ 标准关心我们的安全和幸福,至少在这种情况下。
为了防止实现可能遭受令人讨厌的意外,标准要求我们通过明确说明我们想要将名称视为类型名称或模板的任何地方来明确 依赖名称的歧义。 id 。
如果没有说明,则依赖名称将被视为变量或函数。
如果这是一部好莱坞电影, 依赖名字将是通过身体接触传播的疾病,立即影响其主人,使其混淆。混乱可能会导致一个形成不良的人,erhm .. 计划。
从属名称是直接或间接依赖于模板参数的 任何名称。
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 )中的模板 ID和类型 ,以及( G )中的模板 ID 。
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
既是typename
是冗余。
// .------- the base-specifier-list
template<class T> // v
struct Derived : typename SomeTrait<T>::type /* <- ill-formed */ {
...
};
当template-id是派生类的using-directive 中引用的那个时
struct Base {
template<class T>
struct type { };
};
struct Derived : Base {
using Base::template type; // ill-formed
using Base::type; // legal
};