关于C++:在哪里和为什么我必须把”模板”和”类型名称”关键字放在一起?“template” “typename”

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

在模板中,我在哪里和为什么要将typenametemplate放在从属名称上?究竟什么是从属名称?我有以下代码:

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

我遇到的问题是在typedef Tail::inUnion dummy行。我相当肯定,inUnion是一个依赖名称,而vc++完全正确地扼杀了它。我也知道我应该能够在某个地方添加template,告诉编译器inunion是一个模板ID,但具体在哪里呢?那么它是否应该假定inunion是一个类模板,即inUnion命名一个类型而不是一个函数?


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

1
t * f;

这应该如何解析?对于许多语言来说,编译器不需要知道名称的含义,就可以解析和基本了解一行代码的作用。然而,在C++中,根据EDOCX1的0含义,上述解释会产生很大不同的解释。如果它是一个类型,那么它将是指针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.

Ok.

如果t引用模板类型参数,编译器将如何确定名称t::x所指的是什么?x可以是一个静态int数据成员,可以被乘法,也可以是一个嵌套类或typedef,可以生成一个声明。如果一个名称有这个属性——在知道实际的模板参数之前无法查找它——那么它被称为依赖名称(它"依赖"于模板参数)。好的。

您可能建议等待用户实例化模板:好的。

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

Ok.

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

所以必须有一种方法来告诉编译器某些名称是类型,而某些名称不是。好的。"typename"关键字

答案是:我们决定编译器应该如何解析这个问题。如果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.

Ok.

有许多名称不需要使用typename,因为编译器可以通过模板定义中的适用名称查找来了解如何分析构造本身-例如,当t是类型模板参数时,使用T *f;)。但要使t::x * f;成为一个声明,必须写为typename t::x *f;。如果省略关键字,并且名称被视为非类型,但是当实例化发现它表示一个类型时,编译器会发出通常的错误消息。有时,错误会在定义时给出:好的。

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;

语法只允许在限定名之前使用typename—因此,如果不限定名引用了类型,则认为它们总是已知的。好的。

类似的gotcha存在于表示模板的名称中,正如介绍性文本所暗示的那样。好的。"template"关键字

记住上面的初始报价,以及标准如何要求对模板进行特殊处理?让我们以下面这个无辜的例子为例:好的。

1
boost::function< int() > f;

对于人类读者来说,这可能是显而易见的。对编译器来说不是这样。想象一下以下对boost::functionf的任意定义:好的。

1
2
3
4
5
namespace boost { int function = 0; }
int main() {
  int f = 0;
  boost::function< int() > f;
}

这实际上是一个有效的表达式!它使用小于运算符将boost::function与零(int()进行比较,然后使用大于运算符将得到的boolf进行比较。然而,正如您所知,现实生活中的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.

Ok.

现在我们又回到了与typename相同的问题上。如果在解析代码时我们还不知道名称是否是模板呢?如14.2/4所规定,我们需要在模板名称前插入template。这看起来像:好的。

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

模板名不仅可以出现在::之后,也可以出现在类成员访问中的->.之后。您还需要在此处插入关键字:好的。

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

依赖关系

对于那些书架上有厚厚的标准书籍的人,如果他们想知道我到底在说什么,我会稍微谈一谈标准中如何规定这一点。好的。

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

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

  • 依赖类型(例如:类型模板参数T)
  • 依赖于值的表达式(例如:非类型模板参数N)
  • 依赖于类型的表达式(例如:强制转换为类型模板参数(T)0)

大多数规则都是直观的,并且是递归构建的:例如,如果N是值相关表达式,或者T是依赖类型,那么构造为T[N]的类型就是依赖类型。这方面的细节可以在依赖类型的(14.6.2/1节、依赖类型表达式的(14.6.2.2)节和依赖值表达式的(14.6.2.3)节中阅读。好的。从属名

标准中有点不清楚什么是从属名称。在一个简单的阅读(你知道,最不惊讶的原则)中,它定义的所有依赖名称都是下面函数名的特殊情况。但是,由于明确的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.

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

依赖于值的表达式1 + N不是名称,但N是名称。作为名称的所有依赖构造的子集称为依赖名称。然而,在模板的不同实例化中,函数名可能具有不同的含义,但不幸的是,此一般规则并未捕捉到函数名。好的。依赖函数名

主要不是本文关注的问题,但仍然值得一提:函数名是一个单独处理的异常。标识符函数名不依赖于它本身,而是依赖于调用中使用的依赖于类型的参数表达式。在示例f((T)0)中,f是从属名称。在本标准中,(14.6.2/1)中规定了这一点。好的。附加说明和示例

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

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;
    };
    // ...
};

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

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

在某些情况下,禁止使用关键字,详情如下好的。

  • 在依赖基类的名称上,不允许编写typename。假定给定的名称是类类型名称。对于基类列表和构造函数初始值设定项列表中的两个名称都是这样的:好的。

    1
    2
    3
     template <typename T>
     struct derive_from_Has_type : /* typename */ SomeBase<T>::type
     { };
  • 在使用声明时,在最后一个EDOCX1引用8引用之后不可能使用EDCOX1 OR 4,并且C++委员会表示不在解决方案上工作。好的。

    1
    2
    3
    4
    5
     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问题

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

可以看出,我们需要消歧关键字,即使编译器能够很好地发现A::result_type只能是int(因此是一种类型),this->g只能是稍后声明的成员模板g(即使A在某个地方显式地专门化,这不会影响代码与in该模板,因此其含义不受随后A的专门化的影响).好的。电流实例化

为了改善这种情况,在C++ 11中,当一个类型引用封闭模板时,语言会跟踪。为了知道这一点,类型必须是通过使用某种形式的名称形成的,即它自己的名称(在上面,AA::A)。由此类名称引用的类型已知为当前实例化。如果从中形成名称的类型是成员/嵌套类(那么,A::NestedClassA都是当前实例化),则可能有多个类型都是当前实例化。好的。

基于这个概念,该语言表示,如果发现CurrentInstantiation::FooFooCurrentInstantiationTyped->Foo(例如A *a = this; a->Foo)是当前实例化的成员,或者是它的一个非依赖基类的成员(只需立即进行名称查找),那么它们都是当前实例化的成员。好的。

如果限定符是当前实例化的成员,那么现在不再需要关键字typenametemplate。这里要记住的一个关键点是,A仍然是一个依赖于类型的名称(毕竟T也是依赖于类型的)。但是,A::result_type是已知的一种类型,编译器将"神奇地"研究这种依赖类型来解决这个问题。好的。

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

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

1
2
3
4
5
template<>
struct C<int> {
  typedef bool result_type;
  typedef int questionable_type;
};

在实例化D::f时,需要一个编译器来捕获错误。因此,您可以从这两个世界中获得最好的结果:"延迟"查找保护您,如果您可能遇到依赖基类的问题,也可以"立即"查找,使您从typenametemplate中解脱出来。好的。未知的专业

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

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

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

在C++ 03中,语言可以捕获这个错误,因为从来没有一种有效的方法来实例化EDCOX1×35(无论您给EDCOX1引用20个参数)。在C++ 11中,语言现在有了进一步的检查,以便为编译器实现这个规则提供更多的理由。由于A没有依赖的基类,并且A声明没有成员questionable_type,因此名称A::questionable_type既不是当前实例化的成员,也不是未知专门化的成员。在这种情况下,代码不应该在实例化时有效地编译,因此语言禁止限定符为当前实例化的名称既不是未知专门化的成员,也不是当前实例化的成员(但是,仍然不需要诊断此冲突)。好的。示例和琐事

您可以在这个答案上尝试这些知识,看看上面的定义对于您在现实世界中的一个例子是否有意义(在这个答案中,这些定义的重复稍微不那么详细)。好的。

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),并且在实例化时需要双重检查,检查查找是否仍然匹配。然而,当实例化C::g时,应用优势规则,查找将找到A::f。好的。好啊。


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 and template must be applied.

Ok.

typenametemplate的目的是什么?

typenametemplate在声明模板以外的情况下可用。好的。

C++中有一定的上下文,编译器必须明确地告诉如何处理一个名称,所有这些上下文都有一个共同点,它们依赖于至少一个模板参数。好的。

我们将这些名称称为"从属名称",在解释中可能存在歧义。好的。

这篇文章将解释从属名称和这两个关键字之间的关系。好的。一个片段可以说1000多个单词

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

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

< BR>这可能不像人们想象的那么简单,更具体地说,评估(a)的结果很大程度上取决于作为模板参数T传递的类型的定义。好的。

不同的T可以极大地改变所涉及的语义。好的。

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,以及;
  • g)
    • 由于foo的类型取决于SomeTrait的实例化,因此看起来像成员函数模板的"data"间接地是依赖名称。

如果编译器将依赖名称解释为变量/函数(如前所述,如果我们不明确地说是这样的话,就会发生这种情况),那么(e)、(f)或(g)语句都无效。
BR/>好的。解决方案

为了使g_tmpl有一个有效的定义,我们必须明确地告诉编译器,我们需要输入(e)、模板ID和输入(f)以及模板ID(g)。好的。

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
}

每当一个名称表示一个类型时,所有涉及的名称都必须是类型名称或名称空间,考虑到这一点,很容易看到我们在完全限定名称的开头应用了typename。好的。

但是,template在这方面是不同的,因为没有办法得出这样的结论:"哦,这是一个模板,而另一个东西也必须是一个模板"。这意味着我们直接在任何我们愿意这样对待的名字前面应用template。好的。

< BR>好的。我可以把关键词放在任何名字前面吗?

"Can I just stick typename and template 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

注:在不需要的情况下应用typenametemplate并不被视为良好实践;仅仅因为你可以做一些事情,并不意味着你应该这样做。好的。

< BR>好的。

此外,在某些情况下,明确禁止typenametemplate:好的。

  • 指定类继承的基时好的。

    在派生类的基说明符列表中写入的每个名称都已被视为类型名,显式地指定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
    9
     struct 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++设计


这个答案应该是一个相当简短和甜蜜的答案(部分)标题的问题。如果你想要一个更详细的答案来解释为什么你必须把它们放在那里,请到这里来。

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

1
2
3
4
5
template<typename T>
struct test {
    using type = T; // no typename required
    using underlying_type = typename T::type // typename required
};

注意,这也适用于采用通用模板参数的元函数或事物。但是,如果提供的模板参数是显式类型,则不必指定typename,例如:

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

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

给定此结构和函数:

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
}

试图从函数内部访问t.get()将导致错误:

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'

因此,在这种情况下,您需要预先使用template关键字,并按如下方式调用它:

t.template get()

这样编译器就可以正确地解析它,而不是t.get < 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&amp; 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,前提是您具有引用类型并依赖于模板参数的限定名称。