【问题标题】:Where and why do I have to put the "template" and "typename" keywords?我必须在哪里以及为什么必须放置 \"template\" 和 \"typename\" 关键字?
【发布时间】:2021-05-15 08:40:58
【问题描述】:

在模板中,我必须在哪里以及为什么必须将 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&lt;U&gt; dummy 行。我相当确定 inUnion 是一个从属名称,而 VC++ 完全正确地扼杀了它。
我也知道我应该能够在某处添加 template 以告诉编译器 inUnion 是一个模板 ID。但具体在哪里?然后它是否应该假设 inUnion 是一个类模板,即 inUnion&lt;U&gt; 命名一个类型而不是一个函数?

【问题讨论】:

  • 政治敏感性,便携性。
  • 我通过将最终问题和代码放在开头并水平缩短代码以适合 1024x 屏幕,使您的实际问题(“将模板/类型名称放在哪里?”)更加突出。
  • 从标题中删除了“从属名称”,因为似乎大多数想知道“类型名”和“模板”的人都不知道“从属名称”是什么。这样应该不会让他们感到困惑。
  • @MSalters:boost 非常便携。我想说只有政治才是 boost 经常被忽视的一般原因。我知道的唯一充分理由是增加了构建时间。否则,这一切都是为了重新发明轮子而损失数千美元。
  • 现在在我看来char fail[ -sizeof(U) ]; // Cannot be instantiated for any U 不会起作用,因为-sizeof(U) 仍然总是正数,所以它可能仍然对某些或所有U 起作用。

标签: c++ templates typename c++-faq dependent-name


【解决方案1】:

(见here also for my C++11 answer

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

t * f;

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

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

如果t 指的是模板类型参数,编译器将如何找出名称t::x 指的是什么? x 可以是可以相乘的静态 int 数据成员,也可以是嵌套类或可以屈服于声明的 typedef。如果一个名字有这个属性——在知道实际的模板参数之前不能查找它——那么它被称为附属名称(它“取决于”模板参数)。

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

我们等到用户实例化模板,稍后再找出t::x * f;的真正含义。

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

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

“类型名”关键字

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

在模板声明或定义中使用并依赖于模板参数的名称是 假设不命名类型,除非适用的名称查找找到类型名称或名称是限定的 通过关键字类型名。

有许多名称不需要 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::functionf 的以下任意定义:

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

这实际上是一个有效的表达!它使用小于运算符将 boost::function 与零 (int()) 进行比较,然后使用大于运算符将结果 boolf 进行比较。然而,您可能很清楚,boost::functionin real life 是一个模板,所以编译器知道 (14.2/3):

name lookup (3.4) 发现一个name是一个template-name后,如果这个name后跟一个<,那么<就是 始终作为模板参数列表的开头,从不作为名称后跟小于号 操作员。

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

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

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

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) 值依赖表达式部分。

相关名称

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

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

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

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

值依赖表达式 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,并且 C++ 委员会 said 不致力于解决方案。

     template <typename T>
     struct derive_from_Has_type : SomeBase<T> {
        using SomeBase<T>::template type; // error
        using typename SomeBase<T>::type; // typename *is* allowed
     };
    

【讨论】:

  • 这个答案是从我之前删除的 FAQ 条目中复制的,因为我发现我应该更好地使用现有的类似问题,而不是为了回答它们而编造新的“伪问题”。感谢 @Prasoon,他将最后一部分的想法(禁止使用 typename/template 的情况)编辑为答案。
  • 你能帮我什么时候使用这个语法吗?这个->模板 f<int>();我收到此错误“模板”(作为消除歧义符)仅在模板内允许但没有模板关键字,它工作正常。
  • 我今天问了一个类似的问题,很快就被标记为重复:stackoverflow.com/questions/27923722/…。我被指示重新提出这个问题,而不是创建一个新问题。我必须说我不同意它们是重复的,但我是谁,对吧?那么,是否有任何理由强制执行 typename,即使此时语法不允许除类型名称之外的其他解释?
  • @Pablo 你没有遗漏任何东西。但是即使完整的行不再有歧义,仍然需要编写消歧。
  • @如果。请创建一个新的 C++20 答案,就像我对 C++11 所做的那样。
【解决方案2】:

C++11

问题

虽然 C++03 中关于何时需要 typenametemplate 的规则在很大程度上是合理的,但它的表述有一个恼人的缺点

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-&gt;g只能是成员模板g稍后声明(即使 A 在某处明确特化,也不会影响该模板中的代码,因此其含义不会受到 A 的后续特化的影响!)。

当前实例化

为了改善这种情况,在 C++11 中,当类型引用封闭模板时,语言会进行跟踪。要知道,这个类型一定是用某种形式的名字形成的,也就是它自己的名字(上面的AA&lt;T&gt;::A&lt;T&gt;)。已知由这样的名称引用的类型是当前实例化.如果形成名称的类型是成员/嵌套类(那么,A::NestedClassA 都是当前实例化),则可能有多个类型都是当前实例化。

基于这个概念,语言表示CurrentInstantiation::FooFooCurrentInstantiationTyped-&gt;Foo(例如A *a = this; a-&gt;Foo)都是当前实例化的成员 如果发现它们是当前实例化的类或其非依赖基类之一的成员(通过立即进行名称查找)。

如果限定符是当前实例化的成员,则不再需要关键字 typenametemplate。这里要记住的一个关键点是A&lt;T&gt;仍然类型相关的名称(毕竟T 也是类型相关的)。但是 A&lt;T&gt;::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::f时再次查找D::result_type(即使它已经在定义时找到了它的含义)。如果现在查找结果不同或导致歧义,则程序格式错误,必须给出诊断。想象一下如果我们这样定义C会发生什么

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

在实例化D&lt;int&gt;::f 时,编译器需要捕获错误。所以你得到了两个世界中最好的:“延迟”查找保护你,如果你可能遇到依赖基类的麻烦,以及“立即”查找,让你从typenametemplate中解放出来。

未知专业

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

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

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

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

例子和琐事

您可以在 this answer 上尝试了解这些知识,并在真实世界的示例中查看上述定义对您是否有意义(在该答案中重复的细节略有减少)。

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-&gt;f 绑定到 A::f,一切都很好。然而,C++11 立即将其绑定到 B::f 并在实例化时需要仔细检查,检查查找是否仍然匹配。但是,当实例化 C&lt;A&gt;::g 时,将应用 Dominance Rule,查找将找到 A::f

【讨论】:

  • fyi - 这里引用了这个答案:stackoverflow.com/questions/56411114/…这个答案中的大部分代码都不能在各种编译器上编译。
  • @AdamRackis 假设 C++ 规范自 2013 年以来没有改变(我写这个答案的日期),那么您尝试使用代码的编译器根本就没有实现这个 C++11+ 功能。
【解决方案3】:

前言

这篇文章的目的是易于阅读替代litb's post

根本目的是相同的;对“什么时候?”的解释和“为什么?”必须申请typenametemplate


typenametemplate的目的是什么?

typenametemplate 可用于声明模板以外的情况。

在某些情况下C++必须明确告诉编译器如何处理名称,所有这些上下文都有一个共同点;他们至少依赖于一个模板参数.

我们指的是这样的名称,在解释中可能存在歧义,如: “从属名称”。

这篇文章将解释两者之间的关系从属名, 和两个关键字。


一个 sn-p 说了 1000 多个单词

试着解释下面发生了什么函数模板,无论是对你自己,朋友,还是你的猫;标记为 (一种)?

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


它可能不像人们想象的那么容易,更具体地说是评估的结果(一种) 沉重要看关于作为模板参数传递的类型的定义T

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

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


两种不同的场景:

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

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



理由

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

为了防止实现可能遭受令人讨厌的意外,标准要求我们理清 a 的歧义从属名经过明确地在任何我们想将名称视为类型名称, 或者一个模板ID.

如果没有说明,则从属名将被视为变量或函数。



如何处理相关名称?

如果这是一部好莱坞电影,从属名将是通过身体接触传播的疾病,立即影响其宿主,使其感到困惑。混乱可能会导致一个错误的个人,呃……程序。

一种从属名任何直接或间接依赖于一个名称模板参数.

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    
}

我们有四个依赖的上述 sn-p 中的名称:

  • )
    • “类型”取决于SomeTrait&lt;T&gt;的实例化,其中包括T,以及;
  • F)
    • “嵌套特征”, 这是一个模板ID, 取决于SomeTrait&lt;T&gt;, 和;
    • “类型”在......的最后 (F) 取决于嵌套特征,这取决于SomeTrait&lt;T&gt;,以及;
  • G)
    • “数据”, 这看起来像成员函数模板, 是间接的从属名因为类型取决于SomeTrait&lt;T&gt;的实例化。

声明(), (F) 或者 (G) 是有效的,如果编译器将解释从属名作为变量/函数(如前所述,如果我们没有明确说明,就会发生这种情况)。

解决方案

为了使 g_tmpl 具有有效的定义,我们必须明确地告诉编译器我们期望一个类型 (), 一种模板ID和一个类型在 (F), 和一个模板ID在 (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
}

每次一个名称表示一种类型,全部 名字涉及的必须是类型名称或者名称空间,考虑到这一点,很容易看出我们在完整的开始时应用了typename限定名称.

template 然而,在这方面是不同的,因为没有办法得出这样的结论;“哦,这是模板,那这东西肯定也是模板”.这意味着我们直接在任何前面申请template名称我们想这样对待。



我可以坚持关键词在任何名字前面?

我可以在任何名字前加上typenametemplate 吗?我不想担心它们出现的背景......“-Some C++ Developer

标准中的规则规定,只要您处理的是限定名称(), 但如果名字不是合格的应用程序格式错误(大号).

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的上下文明确地不允许:

  • 指定类继承的基础时

    派生类中的每个名字基本说明符列表已经被视为类型名称, 显式指定 typename 既不正确又多余。

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

  • 当。。。的时候模板ID是在派生类的使用指令

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

【讨论】:

    【解决方案4】:

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


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

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

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

    鉴于此结构和功能:

    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&lt;int&gt;() 将导致错误:

    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&lt;int&gt;()

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

    【讨论】:

    【解决方案5】:
    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:看看typelists,特别是在 Andrei Alexandrescu 的书中:Modern C++ Design

    【讨论】:

    • inUnion<U> 将被实例化,例如,如果您尝试使用 U==int 调用 Union<float,bool>::operator=(U)。它调用一个私有集(U, inUnion<U>* = 0)。
    • result=true/false 的工作是我需要 boost::enable_if< >,它与我们当前的 OSX 工具链不兼容。不过,单独的模板仍然是个好主意。
    • Luc 表示 typedef Tail::inUnion<U> 虚拟对象;线。这将实例化 Tail。但不在 Union<U> 中。当它需要它的完整定义时,它会被实例化。例如,如果您采用 sizeof 或访问成员(使用 ::foo),就会发生这种情况。 @MSalters 无论如何,你还有另一个问题:
    • -sizeof(U) 永远不会是负数 :) 因为 size_t 是无符号整数类型。你会得到一些非常高的数字。你可能想做 sizeof(U) >= 1 ? -1 : 1 或类似 :)
    • ...然后 char f[sizeof(U) >= 1 ? -1 : 1] 或 -sizeof(U) 永远无效。我很久以前就读过,但今天早上我又找到了这段话:14.6/7。不需要拒绝它,但它可以这样做。但是,如果您只放置模板的声明,就可以了。
    【解决方案6】:

    C++20 又名 C++2a

    Proposal 所述,C++20 / C++2a 进一步放宽了对typename 关键字的要求。特别是,typename 现在可以在所有这些地方省略,在这些地方语法上只有一个类型是合法的。所以,如果一个未知的标记必须是一个类型,C++20 实际上会把它当作一个类型来对待。不过,为了向后兼容,typename 仍可使用。

    特别是,大多数 usingtypedef 声明现在可以在没有 typename 的情况下编写。 typename 也可以在方法返回类型的声明(包括尾随返回类型)、方法和 lambda 参数的声明以及static_castconst_castdynamic_castreinterpret_cast 的类型参数中省略.

    一个值得注意的例外是用户或库定义模板实例化的参数列表中仍然需要 typename:即使该特定参数被声明为类型,仍然需要 typename 关键字。所以 static_cast&lt;A::B&gt;(arg) 在 C++20 中是合法的,但是 my_template_class&lt;A::B&gt;(arg) 是错误的,如果 A 是依赖范围并且 my_template_class 需要一个类型。

    几个例子:

    class A { public: typedef int type; static const int val { 1 }; };
    class B { public: typedef float type; static const int val { 2 }; };
    template<typename T> class C {};
    template<int I> class D {};
    template<typename T> class X {
        T::type v;                                  // OK
        T::type f(T::type arg) { return arg; }      // OK
        T::type g(double arg) { return static_cast<T::type>(arg); } // OK
        // C<T::type> c1;                           // error
        D<T::val> d;                                // OK (as has always been)
        C<typename T::type> c2;                     // OK (old style)
        typedef T::type mytype;                     // OK
        using mytypeagain = T::type;                // OK
        C<mytype> c3;                               // OK (via typedef / using)
    };
    X<A> xa;
    X<B> xb;
    

    【讨论】:

    • 作为 C++20 的 DR,template 解析器指南在相同的上下文中是可选的。
    【解决方案7】:

    从属名称是依赖于模板参数的名称,我们需要指示编译器在实际实例化它们之前正确编译模板类/函数。

    • typename -> 告诉编译器依赖名称是实际类型

      template <class T>
      struct DependentType
      {
        typename T::type a;
        using Type=typename T::type;
      };
      
      
    • template -> 告诉编译器依赖名称是一个模板函数/类

      template <class T>
      struct DependentTemplate
      {
        // template function
        template <class U>
        static void func() {}
      
        // template class
        template <class U>
        struct ClassName{};
      };
      
      
      template <class T1, class T2>
      void foo()
      {
        // 3 ways to call a dependent template function
        DependentTemplate<T1>::template func<T2>();
        DependentTemplate<T1>().template func<T2>();
        (new DependentTemplate<T1>())->template func<T2>();
      
        // You need both typename and template to reference a dependent template class
        typename DependentTemplate<T1>::template ClassName<T2> obj;
        using Type=typename DependentTemplate<T1>::template ClassName<T2>;
      }
      

    【讨论】:

      【解决方案8】:

      我将 JLBorges 的优秀 response 逐字逐句地放在 cplusplus.com 的类似问题上,因为这是我读过的关于该主题的最简洁的解释。

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

      例如:

      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,前提是您有一个引用类型并依赖于模板参数的限定名称。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-09-29
        • 1970-01-01
        • 2014-07-06
        • 1970-01-01
        • 2013-10-30
        • 2018-09-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多