在模板,在那里,为什么我必须把typename
和template
上依赖的名字呢?究竟什么是依赖名称?我有以下代码:
template// Tail will be a UnionNode too. struct UnionNode : public Tail { // ... template struct inUnion { // Q: where to add typename/template here? typedef Tail::inUnion dummy; }; template< > struct inUnion { }; }; template // For the last node Tn. struct UnionNode { // ... template struct inUnion { char fail[ -2 + (sizeof(U)%2) ]; // Cannot be instantiated for any U }; template< > struct inUnion { }; };
我遇到的问题就在typedef Tail::inUnion dummy
于此.我很确定这inUnion
是一个从属名称,VC++在窒息时非常正确.我也知道我应该能够添加template
一些地方告诉编译器inUnion是一个模板ID.但到底在哪里?然后它应该假设inUnion是一个类模板,即inUnion
命名一个类型而不是一个函数?
为了解析C++程序,编译器需要知道某些名称是否是类型.以下示例演示了:
t * f;
该怎么解析?对于许多语言,编译器不需要知道名称的含义就可以解析并基本知道代码行的作用.但是,在C++中,上述内容可以根据具体的t
含义产生截然不同的解释.如果它是一个类型,那么它将是一个指针的声明f
.但是,如果它不是一个类型,它将是一个乘法.所以C++标准在段落(3/7)中说:
某些名称表示类型或模板.通常,只要遇到名称,就必须在继续解析包含它的程序之前确定该名称是否表示这些实体之一.确定此过程的过程称为名称查找.
t::x
如果t
引用模板类型参数,编译器将如何找出名称所指的内容?x
可以是一个静态的int数据成员,可以成倍增加,或者同样可以是一个可以产生声明的嵌套类或typedef.如果名称具有此属性 - 在实际模板参数已知之前无法查找 - 那么它将被称为依赖名称(它"取决于"模板参数).
您可能建议等到用户实例化模板:
让我们等到用户实例化模板,然后找出真正的含义
t::x * f;
.
这将是标准作为可能的实施方法,并且实际上是允许的.这些编译器基本上将模板的文本复制到内部缓冲区中,并且只有在需要实例化时,它们才会解析模板并可能检测定义中的错误.但是,在模板的作者发生错误的情况下,其他实现选择尽早检查模板并在实例化甚至发生之前尽快给定错误,而不是困扰模板的用户(可怜的同事!).
所以必须有一种方法告诉编译器某些名称是类型而某些名称不是.
答案是:我们决定编译器应该如何解析它.如果t::x
是依赖名称,那么我们需要在其前面加上typename
告诉编译器以某种方式解析它.标准在(14.6/2)说:
假定模板声明或定义中使用的名称以及依赖于模板参数的名称不会命名类型,除非适用的名称查找找到类型名称或名称由关键字typename限定.
有许多名称typename
不是必需的,因为编译器可以在模板定义中使用适用的名称查找,找出如何解析构造本身 - 例如T *f;
,when 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
针对零(int()
),然后使用大于操作者以比较所产生的bool
对抗f
.但是你可能知道,boost::function
在现实生活中是一个模板,所以编译器知道(14.2/3):
在名称查找(3.4)发现名称是模板名称后,如果此名称后跟一个<,则<始终作为模板参数列表的开头,并且从不作为名称后跟较少的名称 - 比操作员.
现在我们回到了同样的问题typename
.如果在解析代码时我们还不知道名称是否是模板怎么办?我们需要template
在模板名称之前插入,如下所示14.2/4
.这看起来像:
t::template f(); // call a function template
模板名称不仅可以在一个成员访问之后::
,也可以在一个->
或.
多个类成员访问之后发生.您还需要在其中插入关键字:
this->template f(); // call a function template
对于那些架子上有厚厚的Standardese书籍而且想知道我究竟在说什么的人,我会谈谈如何在标准中指明这一点.
在模板声明中,一些构造具有不同的含义,具体取决于您用于实例化模板的模板参数:表达式可能具有不同的类型或值,变量可能具有不同的类型或函数调用可能最终调用不同的函数.通常认为这种构建体取决于模板参数.
标准通过构造是否依赖来精确定义规则.它将它们分成不同的逻辑组:一个捕获类型,另一个捕获表达式.表达式可能取决于它们的价值和/或类型.所以我们有附加的典型例子:
依赖类型(例如:类型模板参数T
)
依赖于值的表达式(例如:非类型模板参数N
)
依赖于类型的表达式(例如:转换为类型模板参数(T)0
)
大多数规则都是直观的并且是递归构建的:例如,构造为T[N]
依赖类型的类型,如果N
是依赖于值的表达式或是T
依赖类型.(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
.所有这些都是名称,并且通过标准中的常规用法,名称还可以包括限定符,用于说明应该查找名称的名称空间或类.
值依赖表达式1 + N
不是名称,而是名称N
.作为名称的所有依赖构造的子集称为依赖名称.但是,函数名称在模板的不同实例化中可能具有不同的含义,但遗憾的是,这个一般规则并未捕获.
主要不是本文的关注点,但仍值得一提:函数名是一个单独处理的例外.标识符函数名称不是依赖于它本身,而是依赖于调用中使用的类型相关参数表达式.在示例中f((T)0)
,f
是一个从属名称.在标准中,这是在(14.6.2/1)
.
在足够的情况下,我们需要typename
和template
.您的代码应如下所示
templatestruct UnionNode : public Tail { // ... template struct inUnion { typedef typename Tail::template inUnion dummy; }; // ... };
关键字template
并不总是出现在名称的最后部分.它可以出现在用作范围的类名之前的中间,如下例所示
typename t::template iterator::value_type v;
在某些情况下,禁止使用关键字,详情如下
在依赖基类的名称上,您不能编写typename
.假设给定的名称是类类型名称.对于基类列表和构造函数初始化列表中的两个名称都是如此:
templatestruct derive_from_Has_type : /* typename */ SomeBase ::type { };
在使用声明中,它不可能template
在最后一次之后使用::
,并且C++委员会表示不会在解决方案上工作.
templatestruct derive_from_Has_type : SomeBase { using SomeBase ::template type; // error using typename SomeBase ::type; // typename *is* allowed };
虽然C++ 03中的规则关于你何时需要typename
并且template
在很大程度上是合理的,但是它的公式有一个令人讨厌的缺点
templatestruct A { typedef int result_type; void f() { // error, "this" is dependent, "template" keyword needed this->g (); // OK g (); // error, "A " is dependent, "typename" keyword needed A ::result_type n1; // OK result_type n2; } template void g(); };
可以看出,我们需要disambiguation关键字,即使编译器可以完美地找出自己A::result_type
只能int
(并且因此是一个类型),并且this->g
只能是g
稍后声明的成员模板(即使在A
某处显式专门化,也会不影响该模板中的代码,因此其含义不会受到以后专业化的影响A
!).
为了改善这种情况,在C++ 11中,语言会在类型引用封闭模板时进行跟踪.要知道,类型必须已经通过使用名称的特定形式,这是它自己的名字形成(在上文中,A
,A
,::A
).已知这种名称引用的类型是当前实例化.如果形成名称的类型是成员/嵌套类(当时,A::NestedClass
并且A
都是当前实例),则可能有多种类型都是当前实例化.
基于这个概念,语言说CurrentInstantiation::Foo
,Foo
和CurrentInstantiationTyped->Foo
(如A *a = this; a->Foo
)都是当前实例的成员 ,如果他们被发现是一类,它是当前实例或它的非依赖的一个基类成员(由只是做名称立即查找).
关键字typename
和template
如果限定符是当前实例中的一员,现在已不再需要.这里要记住的一个关键点是,A
它仍然是一个依赖于类型的名称(毕竟T
它也是类型依赖的).但是A
已知它是一种类型 - 编译器将"神奇地"研究这种依赖类型来解决这个问题.
struct B { typedef int result_type; }; templatestruct C { }; // could be specialized! template struct D : B, C { 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 to provide it // But not a member of the current instantiation typename D::questionable_type r3; } };
这令人印象深刻,但我们能做得更好吗?该语言甚至更进一步,并且要求D::result_type
在实例化时再次查找实现D::f
(即使它在定义时已经发现其含义).当现在查找结果不同或导致模糊时,程序就会形成错误并且必须给出诊断.试想一下,如果我们定义会发生什么C
这样的
template<> struct C{ typedef bool result_type; typedef int questionable_type; };
实例化时需要编译器捕获错误D
.所以你得到了两个世界中最好的:"延迟"查找保护你,如果你可能遇到依赖基类的麻烦,还有"立即"查找,让你从typename
和template
.
在代码中D
,名称typename D::questionable_type
不是当前实例化的成员.相反,该语言将其标记为未知专业化的成员.特别是当你正在做DependentTypeName::Foo
或者DependentTypedName->Foo
依赖类型不是当前的实例化时(在这种情况下编译器可以放弃并说"我们稍后会查看Foo
它是什么)或者它是当前的实例化并且在它或其非依赖基类中找不到名称,并且还有依赖基类.
想象一下,如果我们h
在上面定义的A
类模板中有一个成员函数会发生什么
void h() { typename A::questionable_type x; }
在C++ 03中,语言允许捕获此错误,因为永远不会有一种有效的实例化方法A
(无论你给出什么参数T
).在C++ 11中,该语言现在进一步检查,以便为编译器提供更多理由来实现此规则.由于A
没有依赖性基类,和A
未声明构件questionable_type
,该名称A
是既不当前实例中的一员,也不未知专业化的成员.在这种情况下,该代码不应该在实例化时有效编译,因此该语言禁止一个名称,其中限定符是当前实例化既不是未知专业化的成员也不是当前实例化的成员(但是,这种违规行为仍然不需要被诊断出来).
您可以在这个答案上尝试这些知识,看看上面的定义是否对您在一个真实世界的例子中有意义(它们在该答案中重复得有些细节).
C++ 11规则使得以下有效的C++ 03代码格式不正确(C++委员会不打算这样做,但可能不会修复)
struct B { void f(); }; struct A : virtual B { void f(); }; templatestruct C : virtual B, T { void g() { this->f(); } }; int main() { C c; c.g(); }
这有效的C++代码03将绑定this->f
到A::f
在实例化时,一切都很好.但是,C++ 11会立即绑定它,B::f
并在实例化时需要进行双重检查,检查查找是否仍然匹配.但是,在实例化时C::g
,Dominance Rule适用,而查找将找到A::f
.
前言
这篇文章是litb帖子的一个易于阅读的替代品.
根本目的是一样的; 对"何时?"的解释 和"为什么?"
typename
并且template
必须适用.
typename
和template
?
typename
并且template
可以在声明模板以外的情况下使用.
在C++中有某些上下文,其中必须明确地告诉编译器如何处理名称,并且所有这些上下文都有一个共同点; 它们依赖于至少一个模板参数.
我们指的是这样的名称,在解释中可能存在歧义,因为; " 从属名称 ".
这篇文章将解释依赖名称和两个关键字之间的关系.
尝试解释以下功能模板中发生的事情,无论是对自己,朋友,还是你的猫; 标记为(A)的声明中发生了什么?
templatevoid f_tmpl () { T::foo * x; /* <-- (A) */ }
它可能不像人们想象的那么容易,更具体地说,评估(A)的结果在很大程度上取决于作为模板参数传递的类型的定义T
.
不同的T
s可以彻底改变所涉及的语义.
struct X { typedef int foo; }; /* (C) --> */ f_tmpl(); struct Y { static int const foo = 123; }; /* (D) --> */ f_tmpl ();
两种不同的场景:
如果我们用类型X实例化函数模板,就像在(C)中一样,我们将声明一个名为x的指向int的指针,但是;
如果我们用类型Y实例化模板,如(D)中所示,(A)将由一个表达式组成,该表达式计算123的乘积乘以一些已经声明的变量x.
C++标准关心我们的安全和幸福,至少在这种情况下.
为了防止实现可能遭受令人讨厌的意外,标准要求我们通过明确说明我们想要将名称视为类型名称或模板的任何地方来明确依赖名称的歧义.id.
如果没有说明,则依赖名称将被视为变量或函数.
如果这是一部好莱坞电影,依赖名字将是通过身体接触传播的疾病,立即影响其主人,使其混淆.混乱可能会导致一个形成不良的人,erhm ..计划.
甲从属名称是任何名称直接或间接依赖于模板的参数.
templatevoid g_tmpl () { SomeTrait ::type foo; // (E), ill-formed SomeTrait ::NestedTrait ::type bar; // (F), ill-formed foo.data (); // (G), ill-formed }
我们在上面的代码段中有四个依赖名称:
E)
"类型"取决于实例化SomeTrait
,包括T
和;
F)
"NestedTrait",它是一个模板ID,取决于SomeTrait
,和;
(F)末尾的"type"取决于NestedTrait,它取决于SomeTrait
,和;
G)
"data"看起来像一个成员函数模板,间接是一个依赖名称,因为foo的类型取决于它的实例化SomeTrait
.
如果编译器将依赖名称解释为变量/函数(如前所述,如果我们没有明确说明,则会发生的情况),语句(E),(F)或(G)都不是有效的.
为了使g_tmpl
有一个有效的定义,我们必须明确告诉编译器我们期望(E)中的类型,(F)中的模板ID和类型,以及(G)中的模板ID.
templatevoid g_tmpl () { typename SomeTrait ::type foo; // (G), legal typename SomeTrait ::template NestedTrait ::type bar; // (H), legal foo.template data (); // (I), legal }
每次名称表示一个类型时,所 涉及的所有名称都必须是类型名称或名称空间,考虑到这一点,我们很容易看到我们typename
在完全限定名称的开头应用.
template
但是,在这方面是不同的,因为没有办法得出如下结论; "哦,这是一个模板,比其他东西也必须是模板".这意味着我们template
直接在我们想要处理的任何名称前面申请.
" 我可以坚持
typename
并template
在任何名字前面吗?我不想担心它们出现的背景...... " -Some C++ Developer
标准中的规则规定,只要您处理限定名称(K),就可以应用关键字,但如果名称不合格,则应用程序格式错误(L).
namespace N { templatestruct X { }; }
N:: Xa; // ... legal typename N::template X b; // (K), legal typename template X c; // (L), ill-formed
注意:申请typename
或template
在不需要的情况下,不被视为良好做法; 仅仅因为你可以做某事,并不意味着你应该做.
此外,还有地方环境typename
和template
被明确禁止:
指定类继承的基础时
在派生类的base-specifier-list中编写的每个名称都已被视为类型名称,显式指定typename
既是格式错误又是冗余.
// .------- the base-specifier-list template// v struct Derived : typename SomeTrait ::type /* <- ill-formed */ { ... };
当template-id是派生类的using-directive中引用的那个时
struct Base { templatestruct type { }; }; struct Derived : Base { using Base::template type; // ill-formed using Base::type; // legal };
typedef typename Tail::inUnion dummy;
但是,我不确定你对inUnion的实现是否正确.如果我理解正确,这个类不应该被实例化,因此"失败"选项卡永远不会失败.也许最好用简单的布尔值来指示类型是否在union中.
templatestruct Contains; template struct Contains > { enum { result = Contains ::result }; }; template struct Contains > { enum { result = true }; }; template struct Contains { enum { result = false }; };
PS:看看Boost :: Variant
PS2:看一下类型列表,特别是Andrei Alexandrescu的书:Modern C++ Design
这个答案是一个相当简短和甜蜜的答案(部分)标题问题.如果您想要一个更详细的答案,解释为什么必须将它们放在那里,请到这里.
放置typename
关键字的一般规则主要是在您使用模板参数并且您想要访问嵌套typedef
或使用别名时,例如:
templatestruct test { using type = T; // no typename required using underlying_type = typename T::type // typename required };
请注意,这也适用于元函数或采用通用模板参数的事物.但是,如果提供的模板参数是显式类型,则您不必指定typename
,例如:
templatestruct test { // typename required using type = typename std::conditional ::type; // no typename required using integer = std::conditional ::type; };
添加template
限定符的一般规则大多相似,除非它们通常涉及本身模板化的结构/类的模板化成员函数(静态或其他),例如:
鉴于此结构和功能:
templatestruct test { template void get() const { std::cout << "get\n"; } }; template void func(const test & t) { t.get (); // error }
尝试t.get
从函数内部访问将导致错误:
main.cpp:13:11: error: expected primary-expression before 'int'
t.get();
^
main.cpp:13:11: error: expected ';' before 'int'
因此,在此上下文中,您需要template
事先使用关键字并像这样调用它:
t.template get
这样编译器就会正确地解析它而不是t.get < int
.