何处以及为什么必须放置“模板”和“types名称”关键字?
在模板中,为什么我必须将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
行。 我相当肯定,在inUnion
是一个独立的名字,和VC ++是完全正确的窒息在它。 我也知道我应该能够在某处添加template
来告诉编译器,inUnion是一个模板标识。 但究竟在哪里? 那么它是否应该假定inUnion是一个类模板,即inUnion<U>
是一个types而不是函数?
为了parsing一个C ++程序,编译器需要知道某些名字是不是types的。 以下示例演示:
t * f;
这应该如何parsing? 对于许多语言,编译器不需要知道名称的含义就可以parsing,并且基本知道一行代码的作用。 在C ++中,上面的内容可以根据不同的含义产生截然不同的解释。 如果它是一个types,那么它将是一个指针f
的声明。 但是,如果它不是一个types,它将是一个乘法。 所以C ++标准在第(3/7)段说:
有些名称表示types或模板。 一般来说,每遇到一个名字,在继续parsing包含它的程序之前,有必要确定这个名字是否表示这些实体之一。 确定这个过程称为名称查找。
如果t
指向模板types参数,编译器将如何找出t::x
引用的名称? x
可以是一个静态的int数据成员,可以相乘,也可以是一个嵌套类或typedef,可以产生一个声明。 如果一个名字具有这个属性 – 直到实际的模板参数被知道才能被查找 – 那么它被称为一个依赖名称 (它依赖于模板参数)。
您可能会推荐等到用户实例化模板:
让我们等待,直到用户实例化模板,然后找出
t::x * f;
的真正含义t::x * f;
。
这将作为一种可能的实施方法,并且实际上被标准所允许。 这些编译器基本上将模板的文本复制到内部缓冲区中,只有当需要实例化时,才会parsing模板并可能检测到定义中的错误。 但是,如果模板的作者犯了错误,而不是模板的用户(可怜的同事!),其他的实现select尽早检查模板,并在实例化发生之前尽快给出定义中的错误。
所以必须有一种方法来告诉编译器某些名称是types的,而某些名称不是。
“typename”关键字
答案是: 我们决定编译器如何parsing这个。 如果t::x
是一个依赖名称,那么我们需要在typename
加上前缀来告诉编译器以某种方式parsing它。 标准在(14.6 / 2)说:
除非可用的名称查找findtypes名称或名称由关键字typename限定,否则假定在模板声明或定义中使用的名称(取决于模板参数)不会命名types。
有许多不需要typename
名称,因为编译器可以在模板定义中使用可用的名称查找,找出如何parsing构造本身 – 例如T *f;
,当T
是一个types模板参数。 但是对于t::x * f;
作为一个声明,它必须写成typename t::x *f;
。 如果省略关键字并且该名称被认为是非types的,但是当实例化发现它表示types时,编译器会发出常见的错误消息。 有时候,错误会在定义的时候给出:
// 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::xf;
该语法只允许在限定名称之前的types名称 – 因此,如果这样做的话,那么总是可以知道非限定名称指的是types。
对于表示模板的名称也存在类似的问题,正如介绍性文本所暗示的那样。
“模板”关键字
请记住上面的初始报价以及标准如何对模板进行特殊处理? 我们来看看下面那个天真的例子:
boost::function< int() > f;
对于读者来说,这可能看起来很明显。 编译器不是这样的。 想象下面的boost::function
和f
任意定义:
namespace boost { int function = 0; } int main() { int f = 0; boost::function< int() > f; }
这实际上是一个有效的expression ! 它使用小于运算符来比较boost::function
和zero( int()
),然后使用大于运算符来比较结果bool
和f
。 然而,正如你可能知道的那样, 现实生活中的boost::function
是一个模板,所以编译器知道(14.2 / 3):
名称查找(3.4)发现名称是模板名称后,如果该名称后面跟着一个<,则<始终将<始终作为模板参数列表的开始,而不会作为名称,比运营商。
现在我们回到与typename
相同的问题。 如果我们在parsing代码的时候不知道这个名字是否是一个模板呢? 我们将需要在模板名称之前立即插入template
,如14.2/4
。 这看起来像:
t::template f<int>(); // call a function template
模板名称不仅可以出现在::
,也可以出现在->
或之后.
在一个class级成员访问。 你也需要在那里插入关键字:
this->template f<int>(); // call a function template
依赖
对于那些书架上厚厚的Standaldese书籍,想要知道我在说什么的人,我会谈谈标准中是如何规定的。
在模板声明中,根据你用来实例化模板的模板参数,一些结构具有不同的含义:expression式可能有不同的types或值,variables可能有不同的types,或者函数调用可能最终调用不同的函数。 通常认为这样的结构依赖于模板参数。
标准通过构造是否依赖来精确地定义规则。 它将它们分成逻辑上不同的组:一个捕获types,另一个捕获expression式。 expression式可能取决于其价值和/或其types。 所以我们附上典型的例子:
- 相关types(例如:types模板参数
T
) - 依赖于值的expression式(例如:非types模板参数
N
) - 依赖于types的expression式(例如:转换为types模板参数
(T)0
)
大多数规则是直观的,并recursion地构build:例如,如果N
是依赖于值的expression式,或者T
是从属types,则构造为T[N]
的types是依赖types。 详细内容可以在依赖types(14.6.2/1
)中读取, (14.6.2.2)
对于依赖于types的expression式, (14.6.2.3)
对于依赖于值的expression式。
相关的名字
该标准有点不清楚什么是一个依赖名称 。 在简单的阅读(你知道,最less惊喜的原则),所有它定义为一个独立的名字是下面的函数名称的特殊情况。 但是由于显然T::x
也需要在实例化上下文中查找,所以它也需要是一个独立的名字(幸运的是,在C ++中期之后,委员会已经开始研究如何解决这个混淆的定义) 。
为了避免这个问题,我对标准文本做了一个简单的解释。 在表示相关types或expression式的所有结构中,它们的一个子集表示名称。 这些名字因此是“依赖名字”。 名字可以采取不同的forms – 标准说:
名称是标识符(2.11),运营商function标识符(13.5),转换function标识符(12.3.2)或模板标识符(14.2)的使用,表示实体或标签(6.6.4, 6.1)
标识符只是一个普通的字符/数字序列,而接下来的两个是operator +
和operator type
表单。 最后一个forms是template-name <argument list>
。 所有这些都是名称,通过标准中的常规使用,名称还可以包含限定符,用于说明应查找名称空间或类名称。
依赖于值的expression式1 + N
不是名称,而是N
所有名称依赖结构的子集称为依赖名称 。 但是,函数名称在模板的不同实例化中可能具有不同的含义,但不幸的是,这个通用规则并没有捕获到这些含义。
依赖函数名称
主要不是这篇文章的关注,但仍值得一提:函数名是一个exception,分开处理。 标识符函数名称不是由它自己决定的,而是由调用中使用的依赖于types的参数expression式决定的。 在例子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
。 假定给出的名称是类types名称。 对于基类列表和构造函数初始化程序列表中的名称都是如此:template <typename T> struct derive_from_Has_type : /* typename */ SomeBase<T>::type { };
-
在使用声明中,在last
::
之后不可能使用template
,而C ++委员会表示不能在解决scheme上工作。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
问题
尽pipeC ++ 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(); };
可以看出,即使编译器完全知道A::result_type
只能是int
(因此也是一个types),并且this->g
只能是稍后声明的成员模板g
,我们仍需要消歧义关键字(即使A
明确地被专门化了,也不会影响该模板中的代码,所以它的含义不会受到A
的后续专门化的影响)。
当前实例
为了改善这种情况,在C ++ 11中,语言跟踪何时types引用了封闭模板。 要知道,这个types必须是通过使用某种forms的名称(在上面的A
, A<T>
, ::A<T>
)中形成的。 被这样一个名称引用的types被称为当前的实例化 。 如果名称所属的types是成员/嵌套类(然后, A::NestedClass
和A
都是当前实例化),那么可能有多个types都是当前实例化的。
基于这个概念,该语言表示CurrentInstantiation::Foo
, Foo
和CurrentInstantiationTyped->Foo
(例如A *a = this; a->Foo
)是当前实例的成员, 如果它们被发现是类是当前实例化的,或者是它的非依赖基类之一(通过立即执行名称查找)。
如果限定符是当前实例的成员,现在不再需要关键字typename
和template
。 这里需要记住的关键是A<T>
仍然是一个依赖于types的名字(毕竟T
也是依赖于types的)。 但是A<T>::result_type
被认为是一种types – 编译器会“神奇地”查看这种依赖types来解决这个问题。
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
时,情况总是如此,或者依赖types不是当前的实例化(在这种情况下,编译器可以放弃并说“我们稍后会看什么Foo
)或者它是当前的实例,并且在它或者它的非依赖的基类中没有find这个名字,并且还有依赖的基类。
想象一下,如果我们在上面定义的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; cg(); }
这个有效的C ++ 03代码会在实例化时将this->f
绑定到A::f
,一切正常。 然而,C ++ 11立即将它绑定到B::f
并且在实例化时需要进行双重检查,检查查找是否仍然匹配。 但是,当实例化C<A>::g
, 优势规则适用,查找将会findA::f
。
前言
这篇文章是一个易于阅读 的文章的替代品。
其基本目的是一样的。 对“什么时候?”的解释 和“为什么?”
typename
和template
必须被应用。
typename
和template
的用途是什么?
typename
和template
在声明模板的情况下可用。
在C ++中有一定的上下文,编译器必须明确地告诉如何处理一个名称,所有这些上下文有一个共同点; 它们依赖于至less一个模板参数 。
我们指的是这样的名字,在那里可能有一个模糊的解释,因为; “ 依赖名称 ”。
这篇文章将提供对从属名称和两个关键字之间关系的解释。
一个SNIPPET说超过1000字
尝试解释下面的函数模板中发生了什么 ,或者是你自己,一个朋友,或者是你的猫; 标有( A )的声明中发生了什么?
template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ }
它可能不如人们想象的那样容易,更具体地说,评估( A )的结果在很大程度上取决于作为模板参数T
传递的types的定义。
不同的T
可以彻底改变涉及的语义。
struct X { typedef int foo; }; /* (C) --> */ f_tmpl<X> (); struct Y { static int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();
两种不同的情况 :
-
如果我们实例化types为X的函数模板,就像在( C )中一样,我们将声明一个名为x的 int指针 ,但是;
-
如果我们使用typesY实例化模板(如( D )中所示),则( A )将由一个expression式计算123乘以某个已经声明的variablesx的乘积。
理由
C ++标准关心我们的安全和幸福,至less在这种情况下。
为了防止实现可能遭受令人讨厌的意外,标准要求我们通过在任何我们希望将名称视为types名称或模板的任何地方明确声明意图来理清依赖名称的含糊性, ID 。
如果没有说明, 依赖名称将被视为variables或函数。
如何处理相关的名字 ?
如果这是一部好莱坞电影,那么依赖名字就是通过身体接触传播的疾病,立即影响到主人,使其感到困惑。 这种混乱可能会导致一个forms不健全的个人计划。
从属名称是直接或间接依赖于模板参数的 任何名称。
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 }
上面的代码片段中有四个独立的名字:
- E )
- “type”取决于
SomeTrait<T>
的实例化,其中包括T
,
- “type”取决于
- F )
- 作为模板id的 “NestedTrait”取决于
SomeTrait<T>
,并且; - ( F )结尾处的“type”取决于NestedTrait ,它取决于
SomeTrait<T>
,并且;
- 作为模板id的 “NestedTrait”取决于
- G )
- 由于foo的types取决于
SomeTrait<T>
的实例化,所以看起来像成员函数模板的 “data”是间接的依赖名称 。
- 由于foo的types取决于
如果编译器将解释variables/函数作为variables/函数(如前所述,如果我们没有另外明确地说明会发生什么),则声明( E ),( F )或( G )都不是有效的。
解决scheme
为了使g_tmpl
有一个有效的定义,我们必须明确地告诉编译器我们期望( E )中的types ,( F )中的模板标识符和types以及( 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 }
每当一个名称表示一个types时, 所涉及的所有 名称都必须是types名称或名称空间 ,考虑到这一点,很容易看到我们在完全限定名称的开头应用了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
是显式禁止的:
-
指定类inheritance的基础
写在派生类的base-specifier-list中的每个名字都已经被当作一个type-name来对待,显式地指定
typename
是不合格的,而且是冗余的。// .------- the base-specifier-list template<class T> // v struct Derived : typename SomeTrait<T>::type /* <- ill-formed */ { ... };
-
当template-id是派生类的using-directive中引用的模板id时
struct Base { template<class T> struct type { }; }; struct Derived : Base { using Base::template type; // ill-formed using Base::type; // legal };
typedef typename Tail::inUnion<U> dummy;
但是,我不确定你在inUnion的实施是否正确。 如果我理解正确,这个类不应该被实例化,因此“失败”选项卡永远不会失败。 也许最好用一个简单的布尔值来表示types是否在联合中。
template <typename T, typename TypeList> struct Contains; template <typename T, typename Head, typename Tail> struct Contains<T, UnionNode<Head, Tail> > { enum { result = Contains<T, Tail>::result }; }; template <typename T, typename Tail> struct Contains<T, UnionNode<T, Tail> > { enum { result = true }; }; template <typename T> struct Contains<T, void> { enum { result = false }; };
PS:看看Boost :: Variant
PS2:看看types列表 ,特别是Andrei Alexandrescu的书:Modern C ++ Design
这个答案是为了回答(部分)标题问题而写的。 如果你想要更详细的答案,解释为什么你必须把它们放在那里,请到这里 。
关于放入typename
关键字的一般规则大部分是在使用模板参数时,并且您想要访问嵌套的typedef
或使用别名,例如:
template<typename T> struct test { using type = T; // no typename required using underlying_type = typename T::type // typename required };
请注意,这也适用于元函数或采用generics模板参数的东西。 但是,如果提供的模板参数是显式types,则不必指定typename
,例如:
template<typename T> struct test { // typename required using type = typename std::conditional<true, const T&, T&&>::type; // no typename required using integer = std::conditional<true, int, float>::type; };
添加template
限定符的一般规则大部分是相似的,除了它们通常涉及模板化的自身模板化的结构/类的成员函数(静态或其他),例如:
鉴于这个结构和function:
template<typename T> struct test { template<typename U> void get() const { std::cout << "get\n"; } }; template<typename T> void func(const test<T>& t) { t.get<int>(); // error }
尝试从函数内部访问t.get<int>()
将导致错误:
main.cpp:13:11: error: expected primary-expression before 'int' t.get<int>(); ^ main.cpp:13:11: error: expected ';' before 'int'
因此,在这种情况下,你需要事先使用template
关键字,并像这样调用它:
t.template get<int>()
这样编译器会正确parsing这个,而不是t.get < int
。