为什么必须在何处以及为什么要放置“模板”和“类型名”关键字?


1125

在模板,在那里,为什么我必须把typenametemplate上依赖的名字呢?
无论如何,从属名称到底是什么?

我有以下代码:

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是一个模板ID。但是到底在哪里?然后应该假设inUnion是一个类模板,即inUnion<U>命名一个类型而不是一个函数吗?


1
恼人的问题:为什么不增强boost :: Variant?
阿萨夫·拉维(

58
政治敏感性,便携性。
MSalters

5
我通过将最后一个问题和代码放在开头,并水平缩短了代码以适合1024x屏幕,使您的实际问题(“在哪里放置模板/类型名称?”)更加突出。
Johannes Schaub-litb 2010年

7
从标题中删除了“从属名称”,因为看起来大多数对“类型名”和“模板”感到疑惑的人都不知道什么是“从属名称”。这样应该不会给他们造成混乱。
约翰内斯·绍布

2
@MSalters:boost非常易于移植。我会说,只有政治才是助推器常常不被接受的普遍原因。我知道的唯一好的原因是增加了构建时间。否则,这一切都是为了浪费数千美元来重新发明轮子。
v.oddou

Answers:


1160

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

为了解析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;,当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<int>(); // call a function template

模板名称不仅可以出现在a 成员之后,::而且可以在a 成员中->或之后出现.。您还需要在其中插入关键字:

this->template f<int>(); // 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)值相关表达式。

相关名称

该标准对于从属名称到底是什么还不清楚。简单阅读(您知道,最少惊奇的原理)后,它定义为从属名称的全部就是下面函数名称的特例。但是由于显然也需要在实例化上下文中进行查找,因此它也必须是从属名称(很幸运,从C ++ 14中期开始,委员会已开始研究如何解决这个令人困惑的定义)。T::x

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

名称是指表示实体或标签(6.6.4, 6.1)

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

值相关表达式1 + N不是名称,而是名称N。名称的所有相关构造的子集称为相关名称。但是,函数名称在模板的不同实例中可能具有不同的含义,但不幸的是,该通用规则没有抓住函数名称。

从属函数名称

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

附加说明和示例

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

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。假定给定的名称是类类型名称。对于基类列表和构造函数初始化器列表中的名称都是如此:

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

22
该答案是从我之前删除的FAQ条目中复制的,因为我发现我应该更好地使用现有的类似问题,而不是仅仅为了回答这些问题而创建新的“伪问题”。感谢@Prasoon,他编辑了最后一部分的想法(禁止输入typename / template的情况)。
Johannes Schaub-litb

1
您何时应该使用此语法可以帮我吗?this-> template f <int>(); 我收到此错误“模板”(作为歧义消除器)仅在模板中允许,但没有template关键字,则可以正常工作。
巴尔基

1
今天我问了一个类似的问题,该问题很快被标记为重复:stackoverflow.com/questions/27923722/…。我被指示重振这个问题,而不是创建一个新的问题。我必须说我不同意他们是重复的,但我是谁,对吗?那么,typename即使此时语法不允许除类型名之外的其他替代解释,是否还有任何理由被强制执行?
JorenHeit 2015年

1
@Pablo,您什么都不会丢失。但是即使整行不再是模棱两可的,仍然需要写下歧义。
约翰内斯·绍布

1
@Pablo的目的是使语言和编译器更简单。有一些建议可以使更多情况自动解决,从而减少了您对关键字的使用频率。请注意,在您的示例中,标记模棱两可的,并且只有在您看到“>”之后才可以将其消除为模板尖括号。有关更多详细信息,我是问错人了,因为我没有实现C ++编译器解析器的经验。
约翰内斯·绍布

135

C ++ 11

问题

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

可以看出,即使编译器可以完全弄清楚自己A::result_type只能是int(因此是一种类型),并且this->g只能是g以后声明的成员模板(即使在A某处显式地专门化了),我们也需要歧义关键字。不会影响该模板中的代码,因此以后的A!特殊化不会影响其含义。

当前实例

为了改善这种情况,在C ++ 11中,该语言会跟踪类型引用封闭模板的时间。要知道,类型必须已经通过使用名称的特定形式,这是它自己的名称(在上述中,形成AA<T>::A<T>)。此类名称引用的类型被称为当前实例。可以存在多个类型的全部当前实例,如果从形成有所述名称的类型是成员/嵌套类(然后,A::NestedClassA都是当前实例)。

基于此概念,该语言表示CurrentInstantiation::FooFoo和和CurrentInstantiationTyped->Foo(例如A *a = this; a->Foo)都是当前实例化的成员, 如果发现它们是属于当前实例化的类的成员或其非依赖基类之一(通过执行以下操作)立即查找名称)。

如果限定符是当前实例的成员,则不再需要关键字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;        
  }
};

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

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

实例化时,需要编译器来捕获错误D<int>::f。因此,您可以充分利用两个世界的优势:“延迟”查找(如果您可能会遇到依赖基类的麻烦)可以保护您,还可以通过“立即”查找将您从typename和中解放出来template

未知专业

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

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

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

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


fyi-此处引用了此答案:stackoverflow.com/questions/56411114/… 此答案中的许多代码无法在各种编译器上编译。
亚当·拉基斯

@AdamRackis假设C ++规范自2013年以来(我写此答案的日期)以来没有发生过变化,那么您尝试使用代码的编译器还没有实现C ++ 11 +功能。
Johannes Schaub-litb

98

前言

该帖子旨在代替litb的帖子易于阅读

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


typename和的目的是什么template什么?

typenametemplate可以在声明模板以外的其他情况下使用。

C ++中有某些上下文,必须明确告知编译器如何处理名称,并且所有这些上下文有一个共同点;即,在某些情况下,编译器必须知道如何使用名称。它们取决于至少一个模板参数

我们将这样的名称称为:“ 从属名称 ”。

这篇文章将对从属名称和这两个关键字之间的关系进行解释。


片段多于1000个字

尝试向自己,朋友或您的猫解释以下功能模板中发生的情况;标有(A)的语句中发生了什么?

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


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

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

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


两种不同的情况

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

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



理据

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

为了防止实现从讨厌的惊喜可能遭受的标准规定,我们理出的不确定性相关的名称明确指出意图的任何地方,我们希望把名字无论是作为类型名称,或模板- id

如果未指定任何内容,则从属名称将被视为变量或函数。



如何处理相关名称

如果这是一部好莱坞电影,依赖人的名字就是通过身体接触传播的疾病,立即影响其主人,使之困惑。混乱可能会导致畸形的pers.erm ..程序。

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

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>,其中包括T和;
  • F
    • “ NestedTrait”是一个模板ID,取决于SomeTrait<T>和;
    • F)结尾的“类型”取决于NestedTrait,后者取决于SomeTrait<T>和;
  • G
    • 看起来像成员函数模板的“数据”间接地是一个从属名称,因为foo的类型取决于的实例化SomeTrait<T>

如果编译器将依赖项名称解释为变量/函数,则语句(E),(F)或(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
}

每次名称表示一种类型时,涉及的所有 名称都必须是类型名称名称空间,考虑到这一点,很容易看出我们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

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


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

  • 指定类继承的基础时

    派生类的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中所引用的

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

20
typedef typename Tail::inUnion<U> dummy;

但是,我不确定您所实现的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:看看Boost :: Variant

PS2:查看类型列表,特别是在Andrei Alexandrescu的书:Modern C ++ Design中


例如,如果您尝试使用U == int调用Union <float,bool> :: operator =(U),则inUnion <U>将被实例化。它调用一个私有集(U,inUnion <U> * = 0)。
MSalters 2009年

结果为true / false的工作是我需要boost :: enable_if <>,它与我们当前的OSX工具链不兼容。但是,单独的模板仍然是一个好主意。
MSalters 2009年

Luc表示typedef Tail :: inUnion <U>虚拟;线。将实例化尾巴。但不是inUnion <U>。当需要它的完整定义时,它将实例化。例如,如果您使用sizeof,或访问成员(使用:: foo),就会发生这种情况。@MSalters无论如何,您还有另一个问题:
Johannes Schaub-litb

-sizeof(U)永远不会为负:),因为size_t是无符号整数类型。你会得到一些很高的数字。您可能想要做sizeof(U)> = 1吗?-1:1或类似的:)
Johannes Schaub-litb

我只是让它保持未定义状态,只声明它:template <typename U> struct inUnion; 因此它肯定无法实例化。我认为使用sizeof允许它,即使您实例化它,也允许编译器给您一个错误,因为如果知道sizeof(U)始终> = 1并且...
Johannes Schaub-litb 2009年

20

这个答案的意思是回答标题问题(的一部分)的简短而甜美的答案。如果您想获得更详细的答案以说明为什么必须将其放在此处,请转到此处


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

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

请注意,这也适用于元函数或带有通用模板参数的事物。但是,如果提供的template参数是显式类型,则不必指定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限定符的一般规则几乎是相似的,除了它们通常涉及本身是模板的struct / class的模板成员函数(静态或其他),例如:

鉴于此结构和功能:

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

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


2

对于来自cplusplus.com的类似问题,我将JLBorges的出色回答置于最佳位置,因为这是我在该主题上读到的最简洁的解释。

在我们编写的模板中,可以使用两种名称-从属名称和非从属名称。从属名称是取决于模板参数的名称。无论模板参数是什么,一个非依赖名称的含义都相同。

例如:

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关键字指定它是类型的名称。


摘要

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.