在哪里以及为什么我必须把“模板”和“typename”关键字?

在模板中,在哪里以及为什么我必须将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>命名一个类型而不是一个函数?

237705 次浏览
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:看看提升::变体

PS2:看看打字员,特别是在安德烈·亚历山大斯库的书中:现代C++设计

(见第11C++答案

为了解析C++程序,编译器需要知道某些名称是否为类型。

t * f;

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

有些名称表示类型或模板。一般来说,每当遇到名称时,在继续解析包含它的程序之前,都有必要确定该名称是否表示这些实体之一。确定这一点的过程称为名称查找。

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

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

让我们等到用户实例化模板,然后再找出#0的真正含义。

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

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

“typename”关键字

答案是:我们决定编译器应该如何解析它。如果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;

语法只允许#0在限定名称之前-因此,不合格的名称总是被认为是指类型,如果它们这样做的话。

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

“模板”关键字

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

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::function在现实生活是一个模板,所以编译器知道(14.2/3):

在名称查找(3.4)发现名称是模板名称后,如果此名称后跟<,则<是总是作为模板参数列表的开头,永远不要作为名称后跟小于操作符。

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

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

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

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也需要在实例化上下文中查找,它也需要是一个依赖名称(幸运的是,截至14C++中旬,委员会已经开始研究如何修复这个令人困惑的定义)。

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

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

标识符只是一个简单的字符/数字序列,接下来的两个是operator +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{ };
  • In using-declarations it's not possible to use template after the last ::, and the C++ committee said not to work on a solution.

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

C++11

问题

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

template<typename T>struct A {typedef int result_type;
void f() {// error, "this" is dependent, "template" keyword neededthis->g<float>();
// OKg<float>();
// error, "A<T>" is dependent, "typename" keyword neededA<T>::result_type n1;
// OKresult_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::FooFooCurrentInstantiationTyped->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: intD::result_type r1;
// error, not a member of the current instantiationD::questionable_type r2;
// OK for now - relying on C<T> to provide it// But not a member of the current instantiationtypename D::questionable_type r3;}};

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

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

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

未知专业

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

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

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->f绑定到A::f,一切正常。C++11然而立即将其绑定到B::f,并在实例化时需要仔细检查,检查查找是否仍然匹配。然而,当实例化C<A>::g时,优势规则应用,查找将找到A::f

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


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

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

请注意,这也适用于元函数或采用泛型模板参数的东西。但是,如果提供的模板参数是显式类型,那么您不必指定typename,例如:

template<typename T>struct test {// typename requiredusing type = typename std::conditional<true, const T&, T&&>::type;// no typename requiredusing 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

前言

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

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


typenametemplate的目的是什么?

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

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

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

这篇文章将解释依赖名和两个关键字之间的关系。


一个片段超过1000字

试着向你自己、朋友或者你的猫解释下面的函数模版组件类型中发生了什么;标记(一个)的语句中发生了什么?

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


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

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

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


两种不同的场景

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

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



的理由

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

为了防止实现可能遭受令人讨厌的意外,标准要求我们通过明确来解决依赖项名称的模糊性,并在任何我们希望将名称视为应用类型temete-id错误信息的地方说明意图。

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



如何处理依赖名称?

如果这是一部好莱坞电影,那么依赖名就是通过身体接触传播的疾病,它会立即影响宿主,使其感到困惑。这种困惑可能会导致一个畸形的人,呃…程序。

依赖项名称任何的名称,直接或间接地依赖于模板参数类型

template<class T> void g_tmpl () {SomeTrait<T>::type                   foo; // (E), ill-formedSomeTrait<T>::NestedTrait<int>::type bar; // (F), ill-formedfoo.data<int> ();                         // (G), ill-formed}

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

  • E
    • “类型”依赖于SomeTrait<T>的实例化,其中包括T和;
  • F
    • "嵌套特征",即temete-id错误信息,依赖于SomeTrait<T>,并且;
    • F)末尾的“类型”依赖于雀巢特征,后者依赖于SomeTrait<T>,并且;
  • g
    • “数据”,看起来像成员函数模板,间接地是依赖项名称,因为foo的类型取决于SomeTrait<T>的实例化。

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

的解决方案

为了使g_tmpl有一个有效的定义,我们必须明确地告诉编译器,我们希望在(E)中有一个类型,在(F)中有一个temete-id错误信息类型,在(g)中有一个temete-id错误信息

template<class T> void g_tmpl () {typename SomeTrait<T>::type foo;                            // (G), legaltypename SomeTrait<T>::template NestedTrait<int>::type bar; // (H), legalfoo.template data<int> ();                                  // (I), legal}

每次姓名表示类型时,所涉及的所有名字必须是类型名称命名空间,考虑到这一点,很容易看出我们在完全限定名的开头应用typename

template然而,在这方面是不同的,因为没有办法得出像“哦,这是模板,那这个东西也一定是模板。”这样的结论。这意味着我们将template直接应用于我们想要处理的任何姓名前面。



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

"我能把#0和#1放在任何名字前面吗?我不想担心它们出现的上下文…"-Some C++ Developer

标准中的规则规定,只要您处理的是限定名K),您就可以应用关键字,但如果名称不是合格,则应用程序格式不正确(L)。

namespace N {template<class T>struct X { };}

         N::         X<int> a; // ...  legaltypename N::template X<int> b; // (K), legaltypename template    X<int> c; // (L), ill-formed

注意:在不需要的上下文中应用typenametemplate不被认为是良好的实践;仅仅因为你可以做某事,并不意味着你应该。


此外,还有一些上下文,其中typenametemplate是不允许的明确

  • 当指定一个类继承的基时

    在派生类基础指定符列表中写入的每个名称都已被视为应用类型,显式指定typename既是错误的,也是多余的。

                        // .------- the base-specifier-listtemplate<class T> // vstruct Derived      : typename SomeTrait<T>::type /* <- ill-formed */ {...};

  • temete-id是派生类的using-Directive中引用的那个时

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

我把JLBorges的优秀反应逐字放在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 knownx.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 phasestd::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,前提是您有一个引用类型并依赖于模板参数的限定名称。

C++20又名C++2a

如本文提案所述,C++20/C++2a进一步放宽了对typename关键字的要求。特别是,现在只有语法上的类型是合法的,typename可以在所有这些地方省略。因此,如果一个未知标记必须是一个类型,C++20实际上会将其视为一个类型。不过,为了向后兼容,typename仍然可以使用。

typename也可以在方法返回类型(包括尾随返回类型)的声明、方法和lambda参数的声明以及static_castconst_castdynamic_castreinterpret_cast的类型参数中省略。

一个值得注意的例外,仍然需要typename,是在用户或库定义模板实例化的参数列表中:即使该特定参数被声明为类型,仍然需要typename关键字。因此,static_cast<A::B>(arg)在C++20中是合法的,但如果A是依赖范围,而my_template_class需要一个类型,则my_template_class<A::B>(arg)是病态的。

举几个例子:

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;                                  // OKT::type f(T::type arg) { return arg; }      // OKT::type g(double arg) { return static_cast<T::type>(arg); } // OK// C<T::type> c1;                           // errorD<T::val> d;                                // OK (as has always been)C<typename T::type> c2;                     // OK (old style)typedef T::type mytype;                     // OKusing mytypeagain = T::type;                // OKC<mytype> c3;                               // OK (via typedef / using)};X<A> xa;X<B> xb;

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

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

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

    template <class T>struct DependentTemplate{// template functiontemplate <class U>static void func() {}
    // template classtemplate <class U>struct ClassName{};};
    
    template <class T1, class T2>void foo(){// 3 ways to call a dependent template functionDependentTemplate<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 classtypename DependentTemplate<T1>::template ClassName<T2> obj;using Type=typename DependentTemplate<T1>::template ClassName<T2>;}