C++ 在哪里以及为什么必须放置“模板”和“类型名称”关键字?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/610245/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 16:14:02  来源:igfitidea点击:

Where and why do I have to put the "template" and "typename" keywords?

c++templatestypenamec++-faqdependent-name

提问by MSalters

In templates, where and why do I have to put typenameand templateon dependent names?
What exactly are dependent names anyway?

在模板,在那里,为什么我必须把typenametemplate上依赖的名字呢?
究竟什么是依赖名称?

I have the following code:

我有以下代码:

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> {
    };
};

The problem I have is in the typedef Tail::inUnion<U> dummyline. I'm fairly certain that inUnionis a dependent name, and VC++ is quite right in choking on it.
I also know that I should be able to add templatesomewhere to tell the compiler that inUnion is a template-id. But where exactly? And should it then assume that inUnion is a class template, i.e. inUnion<U>names a type and not a function?

我的问题是typedef Tail::inUnion<U> dummy在线。我相当确定这inUnion是一个从属名称,而 VC++ 非常正确地扼杀了它。
我也知道我应该能够添加template某个地方来告诉编译器 inUnion 是一个模板 ID。但具体在哪里?那么它是否应该假设 inUnion 是一个类模板,即inUnion<U>命名一个类型而不是一个函数?

回答by Johannes Schaub - litb

(See here also for my C++11 answer)

(另请参阅此处了解我的 C++11 答案

In order to parse a C++ program, the compiler needs to know whether certain names are types or not. The following example demonstrates that:

为了解析 C++ 程序,编译器需要知道某些名称是否是类型。以下示例说明:

t * f;

How should this be parsed? For many languages a compiler doesn't need to know the meaning of a name in order to parse and basically know what action a line of code does. In C++, the above however can yield vastly different interpretations depending on what tmeans. If it's a type, then it will be a declaration of a pointer f. However if it's not a type, it will be a multiplication. So the C++ Standard says at paragraph (3/7):

这应该如何解析?对于许多语言,编译器不需要知道名称的含义来解析并且基本上知道一行代码的作用。然而,在 C++ 中,根据t含义的不同,上述内容可能会产生截然不同的解释。如果它是一个类型,那么它将是一个指针的声明f。但是,如果它不是一个类型,它将是一个乘法。所以 C++ 标准在第 (3/7) 段说:

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.

一些名称表示类型或模板。通常,无论何时遇到名称,在继续解析包含它的程序之前,都必须确定该名称是否表示这些实体之一。确定这一点的过程称为名称查找。

How will the compiler find out what a name t::xrefers to, if trefers to a template type parameter? xcould be a static int data member that could be multiplied or could equally well be a nested class or typedef that could yield to a declaration. If a name has this property - that it can't be looked up until the actual template arguments are known - then it's called a dependent name(it "depends" on the template parameters).

t::x如果t引用模板类型参数,编译器将如何找出名称所指的内容?x可以是可以相乘的静态 int 数据成员,也可以是可以产生声明的嵌套类或 typedef。如果名称具有此属性 - 在知道实际模板参数之前无法查找它 - 那么它被称为依赖名称(它“取决于”模板参数)。

You might recommend to just wait till the user instantiates the template:

您可能会建议等到用户实例化模板:

Let's wait until the user instantiates the template, and then later find out the real meaning of t::x * f;.

等用户实例化了模板,再看看真正的含义t::x * f;

This will work and actually is allowed by the Standard as a possible implementation approach. These compilers basically copy the template's text into an internal buffer, and only when an instantiation is needed, they parse the template and possibly detect errors in the definition. But instead of bothering the template's users (poor colleagues!) with errors made by a template's author, other implementations choose to check templates early on and give errors in the definition as soon as possible, before an instantiation even takes place.

作为一种可能的实施方法,这将起作用并且实际上被标准允许。这些编译器基本上将模板的文本复制到内部缓冲区中,并且仅当需要实例化时,它们才会解析模板并可能检测定义中的错误。但是,与其用模板作者犯的错误来打扰模板的用户(可怜的同事!),其他实现选择尽早检查模板并尽快在定义中给出错误,甚至在实例化发生之前。

So there has to be a way to tell the compiler that certain names are types and that certain names aren't.

所以必须有一种方法告诉编译器某些名称是类型而某些名称不是。

The "typename" keyword

“类型名称”关键字

The answer is: Wedecide how the compiler should parse this. If t::xis a dependent name, then we need to prefix it by typenameto tell the compiler to parse it in a certain way. The Standard says at (14.6/2):

答案是:我们决定编译器应该如何解析它。如果t::x是一个依赖名称,那么我们需要在它typename前面加上前缀来告诉编译器以某种方式解析它。标准在 (14.6/2) 上说:

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.

模板声明或定义中使用且依赖于模板参数的名称被假定为不命名类型,除非适用的名称查找找到类型名称或名称由关键字 typename 限定。

There are many names for which typenameis not necessary, because the compiler can, with the applicable name lookup in the template definition, figure out how to parse a construct itself - for example with T *f;, when Tis a type template parameter. But for t::x * f;to be a declaration, it must be written as typename t::x *f;. If you omit the keyword and the name is taken to be a non-type, but when instantiation finds it denotes a type, the usual error messages are emitted by the compiler. Sometimes, the error consequently is given at definition time:

有许多名称typename不是必需的,因为编译器可以通过模板定义中的适用名称查找,找出如何解析构造本身 - 例如,使用T *f;, whenT是类型模板参数。但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;

The syntax allows typenameonly before qualified names- it is therefor taken as granted that unqualified names are always known to refer to types if they do so.

语法typename只允许在限定名称之前- 因此,如果非限定名称总是知道引用类型,则理所当然。

A similar gotcha exists for names that denote templates, as hinted at by the introductory text.

正如介绍性文本所暗示的那样,表示模板的名称存在类似的问题。

The "template" keyword

“模板”关键字

Remember the initial quote above and how the Standard requires special handling for templates as well? Let's take the following innocent-looking example:

还记得上面的初始引用以及标准如何要求对模板进行特殊处理吗?让我们举一个看起来很无辜的例子:

boost::function< int() > f;

It might look obvious to a human reader. Not so for the compiler. Imagine the following arbitrary definition of boost::functionand f:

对于人类读者来说,它可能看起来很明显。对于编译器来说并非如此。想象一下boost::functionand的以下任意定义f

namespace boost { int function = 0; }
int main() { 
  int f = 0;
  boost::function< int() > f; 
}

That's actually a valid expression! It uses the less-than operator to compare boost::functionagainst zero (int()), and then uses the greater-than operator to compare the resulting boolagainst f. However as you might well know, boost::functionin real lifeis a template, so the compiler knows (14.2/3):

这实际上是一个有效的表达!它使用小于运算符boost::function与零 ( int())进行比较,然后使用大于运算符将结果bool与进行比较f。然而你可能知道,boost::function在现实生活中是一个模板,所以编译器知道 (14.2/3):

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.

在名称查找(3.4)发现名称是模板名称后,如果该名称后跟 <,则 < 始终作为模板参数列表的开头,而永远不会作为名称后跟较少的-比运营商。

Now we are back to the same problem as with typename. What if we can't know yet whether the name is a template when parsing the code? We will need to insert templateimmediately before the template name, as specified by 14.2/4. This looks like:

现在我们又回到了与typename. 如果我们在解析代码时还不知道名称是否是模板怎么办?我们需要template在模板名称之前插入,如14.2/4. 这看起来像:

t::template f<int>(); // call a function template

Template names can not only occur after a ::but also after a ->or .in a class member access. You need to insert the keyword there too:

模板名称不仅可以出现在 a 之后::,也可以出现在 a之后,->或者.在类成员访问中。您还需要在那里插入关键字:

this->template f<int>(); // call a function template


Dependencies

依赖关系

For the people that have thick Standardese books on their shelf and that want to know what exactly I was talking about, I'll talk a bit about how this is specified in the Standard.

对于那些书架上有厚厚的标准书籍并且想知道我在说什么的人,我将谈谈标准中是如何规定的。

In template declarations some constructs have different meanings depending on what template arguments you use to instantiate the template: Expressions may have different types or values, variables may have different types or function calls might end up calling different functions. Such constructs are generally said to dependon template parameters.

在模板声明中,根据您用于实例化模板的模板参数,某些构造具有不同的含义:表达式可能具有不同的类型或值,变量可能具有不同的类型,或者函数调用可能最终调用不同的函数。这种构造通常被认为依赖于模板参数。

The Standard defines precisely the rules by whether a construct is dependent or not. It separates them into logically different groups: One catches types, another catches expressions. Expressions may depend by their value and/or their type. So we have, with typical examples appended:

该标准通过构造是否依赖来精确定义规则。它将它们分成逻辑上不同的组:一个捕获类型,另一个捕获表达式。表达式可能取决于它们的值和/或它们的类型。所以我们有,并附上了典型的例子:

  • Dependent types (e.g: a type template parameter T)
  • Value-dependent expressions (e.g: a non-type template parameter N)
  • Type-dependent expressions (e.g: a cast to a type template parameter (T)0)
  • 依赖类型(例如:类型模板参数T
  • 值依赖表达式(例如:非类型模板参数N
  • 依赖于类型的表达式(例如:转换为类型模板参数(T)0

Most of the rules are intuitive and are built up recursively: For example, a type constructed as T[N]is a dependent type if Nis a value-dependent expression or Tis a dependent type. The details of this can be read in section (14.6.2/1) for dependent types, (14.6.2.2)for type-dependent expressions and (14.6.2.3)for value-dependent expressions.

大多数规则都是直观的,并且是递归构建的:例如,构造为T[N]依赖类型的类型 ifN是值依赖表达式或T依赖类型。(14.6.2/1对于依赖类型、依赖(14.6.2.2)于类型的表达式和依赖(14.6.2.3)于值的表达式,可以在部分) 中阅读这方面的详细信息。

Dependent names

从属名称

The Standard is a bit unclear about what exactlyis a dependent name. On a simple read (you know, the principle of least surprise), all it defines as a dependent nameis the special case for function names below. But since clearly T::xalso needs to be looked up in the instantiation context, it also needs to be a dependent name (fortunately, as of mid C++14 the committee has started to look into how to fix this confusing definition).

该标准是一个有点不清楚什么究竟是一个从属名称。在简单的阅读中(你知道,最小惊奇原则),它定义为依赖名称的所有内容都是下面函数名称的特殊情况。但由于显然T::x也需要在实例化上下文中查找,它也需要是一个依赖名称(幸运的是,从 C++14 中期开始,委员会已经开始研究如何解决这个令人困惑的定义)。

To avoid this problem, I have resorted to a simple interpretation of the Standard text. Of all the constructs that denote dependent types or expressions, a subset of them represent names. Those names are therefore "dependent names". A name can take different forms - the Standard says:

为了避免这个问题,我对标准文本进行了简单的解释。在表示依赖类型或表达式的所有构造中,它们的一个子集表示名称。因此,这些名称是“从属名称”。名称可以采用不同的形式 - 标准说:

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)

名称是使用标识符 (2.11)、运算符功能 ID (13.5)、转换功能 ID (12.3.2) 或模板 ID (14.2) 来表示实体或标签 (6.6.4, 6.1)

An identifier is just a plain sequence of characters / digits, while the next two are the operator +and operator typeform. The last form is template-name <argument list>. All these are names, and by conventional use in the Standard, a name can also include qualifiers that say what namespace or class a name should be looked up in.

标识符只是一个简单的字符/数字序列,而接下来的两个是operator +andoperator type形式。最后一种形式是template-name <argument list>. 所有这些都是名称,按照标准中的常规用法,名称还可以包含限定符,说明应该在哪个名称空间或类中查找名称。

A value dependent expression 1 + Nis not a name, but Nis. The subset of all dependent constructs that are names is called dependent name. Function names, however, may have different meaning in different instantiations of a template, but unfortunately are not caught by this general rule.

值依赖表达式1 + N不是名称,而是名称N。作为名称的所有依赖结构的子集称为依赖名称。然而,函数名在模板的不同实例化中可能具有不同的含义,但不幸的是没有被这个一般规则所捕获。

Dependent function names

依赖函数名

Not primarily a concern of this article, but still worth mentioning: Function names are an exception that are handled separately. An identifier function name is dependent not by itself, but by the type dependent argument expressions used in a call. In the example f((T)0), fis a dependent name. In the Standard, this is specified at (14.6.2/1).

不是本文的主要关注点,但仍然值得一提:函数名称是单独处理的异常。标识符函数名称不依赖于自身,而是依赖于调用中使用的类型相关参数表达式。在示例中f((T)0)f是依赖名称。在标准中,这在 中指定(14.6.2/1)

Additional notes and examples

附加说明和示例

In enough cases we need both of typenameand template. Your code should look like the following

在足够多的情况下,我们同时需要typenametemplate。您的代码应如下所示

template <typename T, typename Tail>
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        typedef typename Tail::template inUnion<U> dummy;
    };
    // ...
};

The keyword templatedoesn't always have to appear in the last part of a name. It can appear in the middle before a class name that's used as a scope, like in the following example

关键字template并不总是必须出现在名称的最后一部分。它可以出现在用作作用域的类名之前的中间,如下例所示

typename t::template iterator<int>::value_type v;

In some cases, the keywords are forbidden, as detailed below

在某些情况下,关键字被禁止,详情如下

  • On the name of a dependent base class you are not allowed to write typename. It's assumed that the name given is a class type name. This is true for both names in the base-class list and the constructor initializer list:

     template <typename T>
     struct derive_from_Has_type : /* typename */ SomeBase<T>::type 
     { };
    
  • In using-declarations it's not possible to use templateafter the last ::, and the C++ committee saidnot to work on a solution.

     template <typename T>
     struct derive_from_Has_type : SomeBase<T> {
        using SomeBase<T>::template type; // error
        using typename SomeBase<T>::type; // typename *is* allowed
     };
    
  • 不允许在依赖基类的名称上写typename. 假定给定的名称是类类型名称。对于基类列表和构造函数初始值设定项列表中的名称都是如此:

     template <typename T>
     struct derive_from_Has_type : /* typename */ SomeBase<T>::type 
     { };
    
  • 在 using 声明中,不能template在 last 之后使用::,并且 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
     };
    

回答by Johannes Schaub - litb

C++11

C++11

Problem

问题

While the rules in C++03 about when you need typenameand templateare largely reasonable, there is one annoying disadvantage of its formulation

而在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();
};

As can be seen, we need the disambiguation keyword even if the compiler could perfectly figure out itself that A::result_typecan only be int(and is hence a type), and this->gcan only be the member template gdeclared later (even if Ais explicitly specialized somewhere, that would not affect the code within that template, so its meaning cannot be affected by a later specialization of A!).

可以看出,我们需要消歧关键字,即使编译器可以完美地确定自己A::result_type只能是int(因此是类型),并且this->g只能是g稍后声明的成员模板(即使在A某处显式专门化,那将不会影响该模板中的代码,因此其含义不会受到以后的专业化A!)的影响。

Current instantiation

当前实例化

To improve the situation, in C++11 the language tracks when a type refers to the enclosing template. To know that, the type must have been formed by using a certain form of name, which is its own name (in the above, A, A<T>, ::A<T>). A type referenced by such a name is known to be the current instantiation. There may be multiple types that are all the current instantiation if the type from which the name is formed is a member/nested class (then, A::NestedClassand Aare both current instantiations).

为了改善这种情况,在 C++11 中,语言会在类型引用封闭模板时进行跟踪。要知道,该类型必须是通过使用某种形式的名称形成的,即它自己的名称(在上面,A, A<T>, ::A<T>)。由这样的名称引用的类型被称为当前实例化。如果形成名称的类型是成员/嵌套类(然后,A::NestedClass并且A都是当前实例化),则可能有多种类型都是当前实例化。

Based on this notion, the language says that CurrentInstantiation::Foo, Fooand CurrentInstantiationTyped->Foo(such as A *a = this; a->Foo) are all member of the current instantiationifthey are found to be members of a class that is the current instantiation or one of its non-dependent base classes (by just doing the name lookup immediately).

基于这个概念,语言说CurrentInstantiation::FooFooCurrentInstantiationTyped->Foo(如A *a = this; a->Foo)都是当前实例的成员,如果他们被发现是一类,它是当前实例或它的非依赖的一个基类成员(由只是做名称立即查找)。

The keywords typenameand templateare now not required anymore if the qualifier is a member of the current instantiation. A keypoint here to remember is that A<T>is stilla type-dependent name (after all Tis also type dependent). But A<T>::result_typeis known to be a type - the compiler will "magically" look into this kind of dependent types to figure this out.

如果限定符是当前实例化的成员,则现在不再需要关键字typenametemplate。这里要记住关键点是,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;        
  }
};

That's impressive, but can we do better? The language even goes further and requiresthat an implementation again looks up D::result_typewhen instantiating D::f(even if it found its meaning already at definition time). When now the lookup result differs or results in ambiguity, the program is ill-formed and a diagnostic must be given. Imagine what happens if we defined Clike this

这令人印象深刻,但我们能做得更好吗?该语言甚至更进一步,要求D::result_type在实例化时再次查找实现D::f(即使它在定义时已经找到了它的含义)。当现在查找结果不同或导致歧义时,程序格式错误并且必须给出诊断。试想一下,如果我们定义会发生什么C这样的

template<>
struct C<int> {
  typedef bool result_type;
  typedef int questionable_type;
};

A compiler is required to catch the error when instantiating D<int>::f. So you get the best of the two worlds: "Delayed" lookup protecting you if you could get in trouble with dependent base classes, and also "Immediate" lookup that frees you from typenameand template.

实例化时需要编译器来捕获错误D<int>::f。因此,您将获得两全其美的好处:“延迟”查找可以在您遇到依赖基类的问题时保护您,以及“立即”查找可以将您从typenametemplate.

Unknown specializations

未知专业

In the code of D, the name typename D::questionable_typeis not a member of the current instantiation. Instead the language marks it as a member of an unknown specialization. In particular, this is always the case when you are doing DependentTypeName::Fooor DependentTypedName->Fooand either the dependent type is notthe current instantiation (in which case the compiler can give up and say "we will look later what Foois) or it isthe current instantiation and the name was not found in it or its non-dependent base classes and there are also dependent base classes.

在 的代码中D,名称typename D::questionable_type不是当前实例化的成员。相反,该语言将其标记为未知专业化成员。特别是,当您执行DependentTypeName::FooorDependentTypedName->Foo并且依赖类型不是当前实例化(在这种情况下编译器可以放弃并说“我们稍后会查看是什么” Foo)或者它当前实例化和在它或其非依赖基类中找不到名称,并且还有依赖基类。

Imagine what happens if we had a member function hwithin the above defined Aclass template

想象一下如果我们h在上面定义的A类模板中有一个成员函数会发生什么

void h() {
  typename A<T>::questionable_type x;
}

In C++03, the language allowed to catch this error because there could never be a valid way to instantiate A<T>::h(whatever argument you give to T). In C++11, the language now has a further check to give more reason for compilers to implement this rule. Since Ahas no dependent base classes, and Adeclares no member questionable_type, the name A<T>::questionable_typeis neithera member of the current instantiation nora member of an unknown specialization. In that case, there should be no way that that code could validly compile at instantiation time, so the language forbids a name where the qualifier is the current instantiation to be neither a member of an unknown specialization nor a member of the current instantiation (however, this violation is still not required to be diagnosed).

在 C++03 中,该语言允许捕获此错误,因为永远没有有效的实例化方法A<T>::h(无论您给 什么参数T)。在 C++11 中,该语言现在有进一步的检查,以便为编译器提供更多理由来实现此规则。由于A没有依赖性基类,和A未声明构件questionable_type,该名称A<T>::questionable_type既不当前实例中的一员,也不未知专业的成员。在这种情况下,该代码应该无法在实例化时有效编译,因此该语言禁止限定符是当前实例化的名称既不是未知专业化的成员也不是当前实例化的成员(但是,这种违规仍然不需要诊断)。

Examples and trivia

例子和琐事

You can try this knowledge on this answerand see whether the above definitions make sense for you on a real-world example (they are repeated slightly less detailed in that answer).

您可以在此答案中尝试这些知识,并在实际示例中查看上述定义是否对您有意义(在该答案中重复的内容稍微不那么详细)。

The C++11 rules make the following valid C++03 code ill-formed (which was not intended by the C++ committee, but will probably not be fixed)

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(); 
}

This valid C++03 code would bind this->fto A::fat instantiation time and everything is fine. C++11 however immediately binds it to B::fand requires a double-check when instantiating, checking whether the lookup still matches. However when instantiating C<A>::g, the Dominance Ruleapplies and lookup will find A::finstead.

这有效的C ++代码03将绑定this->fA::f在实例化时,一切都很好。然而,C++11 立即将它绑定到B::f并在实例化时需要仔细检查,检查查找是否仍然匹配。但是,在实例化 时C<A>::g支配规则适用并且查找将A::f改为查找。

回答by Filip Roséen - refp

PREFACE

This post is meant to be an easy-to-readalternative to litb's post.

The underlying purpose is the same; an explanation to "When?" and "Why?" typenameand templatemust be applied.

前言

这篇文章旨在成为litb 文章的一种易于阅读的替代品。

基本目的是相同的;对“什么时候?”的解释 和“为什么?” typename并且template必须应用。



What's the purpose of typenameand template?

typename和的目的是template什么?

typenameand templateare usable in circumstances other than when declaring a template.

typename并且template可用于声明模板以外的情况。

There are certain contexts in C++where the compiler must explicitly be told how to treat a name, and all these contexts have one thing in common; they depend on at least one template-parameter.

C++中有某些上下文必须明确告知编译器如何处理名称,并且所有这些上下文都有一个共同点;它们至少依赖于一个模板参数

We refer to such names, where there can be an ambiguity in interpretation, as; "dependent names".

我们指的是这样的名字,在解释上可能会有歧义,如;“从属名称”。

This post will offer an explanation to the relationship between dependent-names, and the two keywords.

这篇文章将解释dependent-names和这两个关键字之间的关系。



A SNIPPET SAYS MORE THAN 1000 WORDS

一段超过 1000 字的片段

Try to explain what is going on in the following function-template, either to yourself, a friend, or perhaps your cat; what is happening in the statement marked (A)?

尝试向您自己、朋友或您的猫解释以下函数模板中发生的事情;标记为 ( A)的语句中发生了什么?

template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ }


It might not be as easy as one thinks, more specifically the result of evaluating (A) heavily dependson the definition of the type passed as template-parameter T.


这可能不像人们想象的那么容易,更具体地说,评估 ( A)的结果在很大程度上取决于作为模板参数传递的类型的定义T

Different Ts can drastically change the semantics involved.

不同的Ts 可以彻底改变所涉及的语义。

struct X { typedef int       foo;       }; /* (C) --> */ f_tmpl<X> ();
struct Y { static  int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();



The two different scenarios:

两种不同的场景

  • If we instantiate the function-template with type X, as in (C), we will have a declaration of a pointer-to intnamed x, but;

  • if we instantiate the template with type Y, as in (D), (A) would instead consist of an expression that calculates the product of 123multiplied with some already declared variable x.

  • 如果我们使用类型X实例化函数模板,如 ( C) 中,我们将声明一个名为x指向 int指针,但是;

  • 如果我们实例化类型为Y的模板,如 ( D) 中,则 ( A) 将改为由一个表达式组成,该表达式计算123乘以某个已声明的变量x 的乘积。





THE RATIONALE

基本原理

The C++ Standard cares about our safety and well-being, at least in this case.

C++ 标准关心我们的安全和福祉,至少在这种情况下是这样。

To prevent an implementation from potentially suffering from nasty surprises, the Standard mandates that we sort out the ambiguity of a dependent-nameby explicitlystating the intent anywhere we'd like to treat the name as either a type-name, or a template-id.

为了防止实现可能遭受令人讨厌的意外,标准要求我们通过在我们希望将名称视为类型名称模板的任何地方明确说明意图来解决依赖名称的歧义-

If nothing is stated, the dependent-namewill be considered to be either a variable, or a function.

如果没有说明,则依赖名称将被视为变量或函数。





HOW TO HANDLE DEPENDENT NAMES?

如何处理相关的名字

If this was a Hollywood film, dependent-nameswould be the disease that spreads through body contact, instantly affects its host to make it confused. Confusion that could, possibly, lead to an ill-formed perso-, erhm.. program.

如果这是一部好莱坞电影,依附名字就是通过身体接触传播的疾病,它会立即影响到它的主人,使它变得混乱。可能会导致格式错误的个人,erhm .. 程序的混乱。

A dependent-nameis anyname that directly, or indirectly, depends on a template-parameter.

从属名称任何名称直接或间接依赖于模板的参数

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    
}

We have four dependentnames in the above snippet:

我们在上面的代码片段中有四个依赖名称:

  • E)
    • "type"depends on the instantiation of SomeTrait<T>, which include T, and;
  • F)
    • "NestedTrait", which is a template-id, depends on SomeTrait<T>, and;
    • "type"at the end of (F) depends on NestedTrait, which depends on SomeTrait<T>, and;
  • G)
    • "data", which looks like a member-function template, is indirectly a dependent-namesince the type of foodepends on the instantiation of SomeTrait<T>.
  • )
    • “类型”取决于 的实例化SomeTrait<T>,其中包括T、 和;
  • ˚F
    • "NestedTrait"是一个模板 ID,取决于SomeTrait<T>, 和;
    • ( F)末尾的“类型”取决于NestedTrait,它取决于SomeTrait<T>, 和;
  • )
    • "data"看起来像一个成员函数模板,它间接地是一个从属名称,因为foo的类型取决于 的实例化SomeTrait<T>

Neither of statement (E), (F) or (G) is valid if the compiler would interpret the dependent-namesas variables/functions (which as stated earlier is what happens if we don't explicitly say otherwise).

如果编译器将依赖名称解释为变量/函数(如前所述,如果我们不明确说明会发生什么),则语句 ( E)、( F) 或 ( G) 都无效。

THE SOLUTION

解决方案

To make g_tmplhave a valid definition we must explicitly tell the compiler that we expect a type in (E), a template-idand a typein (F), and a template-idin (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
}

Every time a namedenotes a type, allnamesinvolved must be either type-namesor namespaces, with this in mind it's quite easy to see that we apply typenameat the beginning of our fully qualified name.

每次名称表示一种类型时,所有涉及的名称都必须是type-namesnamespaces,考虑到这一点,很容易看出我们typename在完全限定名称的开头应用。

templatehowever, is different in this regard, since there's no way of coming to a conclusion such as; "oh, this is a template, then this other thing must also be a template". This means that we apply templatedirectly in front of any namethat we'd like to treat as such.

template然而,在这方面是不同的,因为没有办法得出这样的结论;“哦,这是一个模板,那么其他东西也必须是一个模板”。这意味着我们可以template直接应用在我们希望这样对待的任何名称之前。





CAN I JUST STICK THE KEYWORDSIN FRONT OF ANY NAME?

我可以在任何名字前面粘贴关键字吗?

"Can I just stick typenameand templatein front of any name? I don't want to worry about the context in which they appear..." - Some C++ Developer

可我只是坚持typename,并template在任何名称前面我不想对它们出现的背景下愁......?” -Some C++ Developer

The rules in the Standard states that you may apply the keywords as long as you are dealing with a qualified-name(K), but if the name isn't qualifiedthe application is ill-formed (L).

标准中的规则规定,只要您正在处理限定名称( 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

Note: Applying typenameor templatein a context where it is not required is not considered good practice; just because you can do something, doesn't mean that you should.

注意:应用typenametemplate在不需要它的情况下不被视为良好做法;仅仅因为您可以做某事,并不意味着您应该这样做。



Additionally there are contexts where typenameand templateare explicitlydisallowed:

此外,还有地方环境typenametemplate明确禁止:

  • When specifying the bases of which a class inherits

    Every name written in a derived class's base-specifier-listis already treated as a type-name, explicitly specifying typenameis both ill-formed, and redundant.

                       // .------- the base-specifier-list
     template<class T> // v
     struct Derived      : typename SomeTrait<T>::type /* <- ill-formed */ {
       ...
     };
    


  • When the template-idis the one being referred to in a derived class's using-directive

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };
    
  • 指定类继承的基类时

    写在派生类的base-specifier-list中的每个名称都已经被视为type-name,显式指定typename既不正确又多余。

                       // .------- the base-specifier-list
     template<class T> // v
     struct Derived      : typename SomeTrait<T>::type /* <- ill-formed */ {
       ...
     };
    


  • 模板 ID是派生类的using 指令中引用的模板 ID 时

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };
    

回答by Rapptz

This answer is meant to be a rather short and sweet one to answer (part of) the titled question. If you want an answer with more detail that explains why you have to put them there, please go here.

这个答案旨在回答标题问题的(部分)一个相当简短而甜蜜的答案。如果您想要更详细的答案来解释为什么必须将它们放在那里,请转到此处



The general rule for putting the typenamekeyword is mostly when you're using a template parameter and you want to access a nested typedefor using-alias, for example:

放置typename关键字的一般规则主要是当您使用模板参数并且想要访问嵌套typedef或使用别名时,例如:

template<typename T>
struct test {
    using type = T; // no typename required
    using underlying_type = typename T::type // typename required
};

Note that this also applies for meta functions or things that take generic template parameters too. However, if the template parameter provided is an explicit type then you don't have to specify typename, for example:

请注意,这也适用于元函数或采用通用模板参数的事物。但是,如果提供的模板参数是显式类型,则不必指定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;
};

The general rules for adding the templatequalifier are mostly similar except they typically involve templated member functions (static or otherwise) of a struct/class that is itself templated, for example:

添加template限定符的一般规则大多相似,但它们通常涉及本身已模板化的结构/类的模板化成员函数(静态或其他),例如:

Given this struct and 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
}

Attempting to access t.get<int>()from inside the function will result in an 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'

Thus in this context you would need the templatekeyword beforehand and call it like so:

因此,在这种情况下,您需要template事先使用关键字并像这样调用它:

t.template get<int>()

t.template get<int>()

That way the compiler will parse this properly rather than t.get < int.

这样编译器将正确解析它而不是t.get < int.

回答by Luc Touraille

typedef typename Tail::inUnion<U> dummy;

However, I'm not sure you're implementation of inUnion is correct. If I understand correctly, this class is not supposed to be instantiated, therefore the "fail" tab will never avtually fails. Maybe it would be better to indicates whether the type is in the union or not with a simple boolean value.

但是,我不确定您对 inUnion 的实现是否正确。如果我理解正确,这个类不应该被实例化,因此“失败”选项卡永远不会失败。也许用一个简单的布尔值来指示类型是否在联合中会更好。

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: Have a look at Boost::Variant

PS:看看Boost::Variant

PS2: Have a look at typelists, notably in Andrei Alexandrescu's book: Modern C++ Design

PS2:看看typelists,特别是在 Andrei Alexandrescu 的书:Modern C++ Design

回答by Nikos

I am placing JLBorges's excellent responseto a similar question verbatim from cplusplus.com, as it is the most succinct explanation I've read on the subject.

我将 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:

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.

在我们编写的模板中,可以使用两种名称 - 依赖名称和非依赖名称。依赖名称是依赖模板参数的名称;无论模板参数是什么,非依赖名称都具有相同的含义。

例如:

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)
}

对于模板的每个不同实例,依赖名称所指的内容可能有所不同。因此,C++ 模板受制于“两阶段名称查找”。当最初解析模板时(在任何实例化发生之前),编译器查找非依赖名称。当模板的特定实例化发生时,模板参数到那时就知道了,编译器会查找依赖名称。

在第一阶段,解析器需要知道依赖名称是类型名称还是非类型名称。默认情况下,依赖名称被假定为非类型的名称。依赖名称之前的 typename 关键字指定它是类型的名称。



Summary

概括

Use the keyword typename only in template declarations and definitions provided you have a qualified name that refers to a type and depends on a template parameter.

仅在模板声明和定义中使用关键字 typename,前提是您具有引用类型并依赖于模板参数的限定名称。