Where and why do I have to put the “template” and “typename” keywords?
在模板中,我在哪里和为什么要将
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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> { }; }; |
我遇到的问题是在
为了分析C++程序,编译器需要知道某些名称是否为类型。以下示例说明:好的。
1 | t * f; |
这应该如何解析?对于许多语言来说,编译器不需要知道名称的含义,就可以解析和基本了解一行代码的作用。然而,在C++中,根据EDOCX1的0含义,上述解释会产生很大不同的解释。如果它是一个类型,那么它将是指针
Some names denote types or templates. In general, whenever a name is encountered it is necessary to determine whether that name denotes one of these entities before continuing to parse the program that contains it. The process that determines this is called name lookup.
Ok.
如果
您可能建议等待用户实例化模板:好的。
Let's wait until the user instantiates the template, and then later find out the real meaning of
t::x * f; .Ok.
这将起作用,并且作为一种可能的实现方法被标准实际允许。这些编译器基本上将模板的文本复制到一个内部缓冲区中,并且只有在需要实例化时,才会解析模板并可能检测到定义中的错误。但是不要打扰模板的用户(可怜的同事!)对于模板作者所犯的错误,其他实现选择尽早检查模板并在实例化发生之前尽快给出定义中的错误。好的。
所以必须有一种方法来告诉编译器某些名称是类型,而某些名称不是。好的。"typename"关键字
答案是:我们决定编译器应该如何解析这个问题。如果
A name used in a template declaration or definition and that is dependent on a template-parameter is
assumed not to name a type unless the applicable name lookup finds a type name or the name is qualified
by the keyword typename.Ok.
有许多名称不需要使用
1 2 3 | // 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; |
语法只允许在限定名之前使用
类似的gotcha存在于表示模板的名称中,正如介绍性文本所暗示的那样。好的。"template"关键字
记住上面的初始报价,以及标准如何要求对模板进行特殊处理?让我们以下面这个无辜的例子为例:好的。
1 | boost::function< int() > f; |
对于人类读者来说,这可能是显而易见的。对编译器来说不是这样。想象一下以下对
1 2 3 4 5 | namespace boost { int function = 0; } int main() { int f = 0; boost::function< int() > f; } |
这实际上是一个有效的表达式!它使用小于运算符将
After name lookup (3.4) finds that a name is a template-name, if this name is followed by a <, the < is always taken as the beginning of a template-argument-list and never as a name followed by the less-than operator.
Ok.
现在我们又回到了与
1 | t::template f<int>(); // call a function template |
模板名不仅可以出现在
1 | this->template f<int>(); // call a function template |
依赖关系
对于那些书架上有厚厚的标准书籍的人,如果他们想知道我到底在说什么,我会稍微谈一谈标准中如何规定这一点。好的。
在模板声明中,根据用于实例化模板的模板参数,某些构造具有不同的含义:表达式可能具有不同的类型或值,变量可能具有不同的类型,或者函数调用可能最终调用不同的函数。这种构造通常被称为依赖于模板参数。好的。
标准通过构造是否依赖来精确定义规则。它将它们分成逻辑上不同的组:一个捕获类型,另一个捕获表达式。表达式可能取决于其值和/或类型。因此,我们附上了典型的例子:好的。
- 依赖类型(例如:类型模板参数
T ) - 依赖于值的表达式(例如:非类型模板参数
N ) - 依赖于类型的表达式(例如:强制转换为类型模板参数
(T)0 )
大多数规则都是直观的,并且是递归构建的:例如,如果
标准中有点不清楚什么是从属名称。在一个简单的阅读(你知道,最不惊讶的原则)中,它定义的所有依赖名称都是下面函数名的特殊情况。但是,由于明确的EDCOX1〔20〕还需要在实例化上下文中查找,所以它也需要是一个从属的名称(幸运的是,正如C++中的14,委员会已经开始研究如何修复这个令人困惑的定义)。好的。
为了避免这个问题,我对标准文本进行了简单的解释。在所有表示依赖类型或表达式的构造中,一个子集表示名称。因此,这些名称是"从属名称"。一个名字可以采用不同的形式-标准规定:好的。
A name is a use of an identifier (2.11), operator-function-id (13.5), conversion-function-id (12.3.2), or template-id (14.2) that denotes an entity or label (6.6.4, 6.1)
Ok.
标识符只是一个简单的字符/数字序列,接下来的两个是
依赖于值的表达式
主要不是本文关注的问题,但仍然值得一提:函数名是一个单独处理的异常。标识符函数名不依赖于它本身,而是依赖于调用中使用的依赖于类型的参数表达式。在示例
在足够的情况下,我们需要
1 2 3 4 5 6 7 8 | template <typename T, typename Tail> struct UnionNode : public Tail { // ... template<typename U> struct inUnion { typedef typename Tail::template inUnion<U> dummy; }; // ... }; |
关键字
1 | typename t::template iterator<int>::value_type v; |
在某些情况下,禁止使用关键字,详情如下好的。
在依赖基类的名称上,不允许编写
typename 。假定给定的名称是类类型名称。对于基类列表和构造函数初始值设定项列表中的两个名称都是这样的:好的。1
2
3template <typename T>
struct derive_from_Has_type : /* typename */ SomeBase<T>::type
{ };在使用声明时,在最后一个EDOCX1引用8引用之后不可能使用EDCOX1 OR 4,并且C++委员会表示不在解决方案上工作。好的。
1
2
3
4
5template <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问题
虽然C++ 03中的规则在需要EDCOX1、0和EDCX1〔1〕时基本上是合理的,但它的公式有一个恼人的缺点。好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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(); }; |
可以看出,我们需要消歧关键字,即使编译器能够很好地发现
为了改善这种情况,在C++ 11中,当一个类型引用封闭模板时,语言会跟踪。为了知道这一点,类型必须是通过使用某种形式的名称形成的,即它自己的名称(在上面,
基于这个概念,该语言表示,如果发现
如果限定符是当前实例化的成员,那么现在不再需要关键字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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; } }; |
这很令人印象深刻,但我们能做得更好吗?该语言甚至更进一步,要求在实例化
1 2 3 4 5 | template<> struct C<int> { typedef bool result_type; typedef int questionable_type; }; |
在实例化
在
想象一下,如果在上面定义的
1 2 3 | void h() { typename A<T>::questionable_type x; } |
在C++ 03中,语言可以捕获这个错误,因为从来没有一种有效的方法来实例化EDCOX1×35(无论您给EDCOX1引用20个参数)。在C++ 11中,语言现在有了进一步的检查,以便为编译器实现这个规则提供更多的理由。由于
您可以在这个答案上尝试这些知识,看看上面的定义对于您在现实世界中的一个例子是否有意义(在这个答案中,这些定义的重复稍微不那么详细)。好的。
C++ 11规则使下面的有效C++ 03代码不正确(这不是C++委员会想要的,但可能不固定)。好的。
1 2 3 4 5 6 7 8 9 10 11 | 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代码在实例化的时候将EDCOX1与0的EDCOX1,1,EXOX1,EXOX1,EXOX1,EXOX1,EXX1,EXX1,EXX1,EXX1,EXX1,EXX1,EXX1,EXX1,EXX1,EXX1,EXX1,EXX1,A然而,C++ 11立即将其绑定到EDCOX1(2),并且在实例化时需要双重检查,检查查找是否仍然匹配。然而,当实例化
PREFACE
Ok.
This post is meant to be an easy-to-read alternative to litb's post.
Ok.
The underlying purpose is the same; an explanation to"When?" and"Why?"
typename andtemplate must be applied.Ok.
C++中有一定的上下文,编译器必须明确地告诉如何处理一个名称,所有这些上下文都有一个共同点,它们依赖于至少一个模板参数。好的。
我们将这些名称称为"从属名称",在解释中可能存在歧义。好的。
这篇文章将解释从属名称和这两个关键字之间的关系。好的。一个片段可以说1000多个单词
尝试向您自己、朋友或您的猫解释以下函数模板中发生了什么;标记为(a)的语句中发生了什么?好的。
1 | template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ } |
< BR>这可能不像人们想象的那么简单,更具体地说,评估(a)的结果很大程度上取决于作为模板参数
不同的
1 2 | struct X { typedef int foo; }; /* (C) --> */ f_tmpl<X> (); struct Y { static int const foo = 123; }; /* (D) --> */ f_tmpl<Y> (); |
< BR>好的。
两种不同的情况:好的。
如果我们用类型x实例化函数模板,如(c)中所示,我们将有一个指向名为x的int的指针声明,但是;好的。
如果我们用Y类型实例化模板,如(d)中所示,(a)将由一个表达式组成,该表达式计算123乘以已声明的变量x的乘积。好的。
< BR>好的。理据
C++标准关心我们的安全和福祉,至少在这种情况下。好的。
为了防止实现可能遭受令人不快的意外,标准要求我们通过在希望将名称视为类型名称或模板ID的任何位置显式地声明意图来解决依赖名称的模糊性。好的。
如果未声明任何内容,则从属名称将被视为变量或函数。好的。
< BR>好的。如何处理从属名称?
如果这是一部好莱坞电影,依赖性的名字将是通过身体接触传播的疾病,会立即影响到它的主人,使之困惑。可能会导致一个不健全的人,二胡的困惑。程序。好的。
从属名称是直接或间接依赖于模板参数的任何名称。
BR/>好的。
1 2 3 4 5 | 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)
- "类型"取决于
SomeTrait 的实例化,其中包括T 和;
- "类型"取决于
- f)
- "nestedtrait"是一个模板ID,依赖于
SomeTrait 和; - (f)末尾的"类型"取决于nestedtrait,这取决于
SomeTrait ,以及;
- "nestedtrait"是一个模板ID,依赖于
- g)
- 由于foo的类型取决于
SomeTrait 的实例化,因此看起来像成员函数模板的"data"间接地是依赖名称。
- 由于foo的类型取决于
如果编译器将依赖名称解释为变量/函数(如前所述,如果我们不明确地说是这样的话,就会发生这种情况),那么(e)、(f)或(g)语句都无效。
BR/>好的。解决方案
为了使
1 2 3 4 5 | 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 } |
每当一个名称表示一个类型时,所有涉及的名称都必须是类型名称或名称空间,考虑到这一点,很容易看到我们在完全限定名称的开头应用了
但是,
< BR>好的。我可以把关键词放在任何名字前面吗?
"Can I just stick
typename andtemplate in front of any name? I don't want to worry about the context in which they appear..." -Some C++ Developer Ok.
标准中的规则规定,只要处理限定名(k),就可以应用关键字,但如果名称不限定,则应用程序格式错误(l)。好的。
1 2 3 4 | namespace N { template<class T> struct X { }; } |
好的。
1 2 3 | N:: X<int> a; // ... legal typename N::template X<int> b; // (K), legal typename template X<int> c; // (L), ill-formed |
注:在不需要的情况下应用
< BR>好的。
此外,在某些情况下,明确禁止
指定类继承的基时好的。
在派生类的基说明符列表中写入的每个名称都已被视为类型名,显式地指定
typename 是格式错误的,也是冗余的。好的。1
2
3
4
5// .------- the base-specifier-list
template<class T> // v
struct Derived : typename SomeTrait<T>::type /* <- ill-formed */ {
...
};< BR>好的。
当模板ID是派生类的using指令中引用的模板ID时好的。
1
2
3
4
5
6
7
8
9struct Base {
template<class T>
struct type { };
};
struct Derived : Base {
using Base::template type; // ill-formed
using Base::type; // legal
};
好啊。
1 | typedef typename Tail::inUnion<U> dummy; |
但是,我不确定您是否正确实现了inunion。如果我理解正确,这个类不应该被实例化,因此"fail"选项卡永远不会实际失败。也许最好用一个简单的布尔值来指示类型是否在联合中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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:看看打字员,特别是在Andrei Alexandrescu的书中:现代C++设计
放置
1 2 3 4 5 | template<typename T> struct test { using type = T; // no typename required using underlying_type = typename T::type // typename required }; |
注意,这也适用于采用通用模板参数的元函数或事物。但是,如果提供的模板参数是显式类型,则不必指定
1 2 3 4 5 6 7 | 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; }; |
添加
给定此结构和函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 | template<typename T> struct test { template<typename U> void get() const { std::cout <<"get "; } }; template<typename T> void func(const test<T>& t) { t.get<int>(); // error } |
试图从函数内部访问
1 2 3 4 | main.cpp:13:11: error: expected primary-expression before 'int' t.get<int>(); ^ main.cpp:13:11: error: expected ';' before 'int' |
因此,在这种情况下,您需要预先使用
这样编译器就可以正确地解析它,而不是
我将jlborges对cplusplus.com上类似问题的出色回答逐字逐句,因为这是我读过的关于这个主题的最简洁的解释。
In a template that we write, there are two kinds of names that could be used - dependant names and non- dependant names. A dependant name is a name that depends on a template parameter; a non-dependant name has the same meaning irrespective of what the template parameters are.
For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 template< typename T > void foo( T& x, std::string str, int count )
{
// these names are looked up during the second phase
// when foo is instantiated and the type T is known
x.size(); // dependant name (non-type)
T::instance_count ; // dependant name (non-type)
typename T::iterator i ; // dependant name (type)
// during the first phase,
// T::instance_count is treated as a non-type (this is the default)
// the typename keyword specifies that T::iterator is to be treated as a type.
// these names are looked up during the first phase
std::string::size_type s ; // non-dependant name (type)
std::string::npos ; // non-dependant name (non-type)
str.empty() ; // non-dependant name (non-type)
count ; // non-dependant name (non-type)
}What a dependant name refers to could be something different for each different instantiation of the template. As a consequence, C++ templates are subject to"two-phase name lookup". When a template is initially parsed (before any instantiation takes place) the compiler looks up the non-dependent names. When a particular instantiation of the template takes place, the template parameters are known by then, and the compiler looks up dependent names.
During the first phase, the parser needs to know if a dependant name is the name of a type or the name of a non-type. By default, a dependant name is assumed to be the name of a non-type. The typename keyword before a dependant name specifies that it is the name of a type.
总结
仅在模板声明和定义中使用关键字type name,前提是您具有引用类型并依赖于模板参数的限定名称。