提问



在模板中,我在哪里以及为什么必须将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是一个template-id。但究竟在哪里呢?那么它应该假设inUnion是一个类模板,即inUnion<U>命名一个类型而不是一个函数?

最佳参考


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


t * f;


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



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



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


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



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



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


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


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;


语法仅在限定名称之前允许typename - 因此,如果他们这样做,那么非限定名称总是被称为引用类型。


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


template关键字



还记得上面的初始引用以及标准如何要求对模板进行特殊处理吗?让我们采取以下无辜的例子:


boost::function< int() > f;


人类读者可能看起来很明显。编译器不是这样。想象一下boost::functionf的以下任意定义:


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


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



  在名称查找(3.4)发现名称是模板名称后,如果此名称后跟一个< ;,则<是
  始终作为模板参数列表的开头,从不作为名称后跟小于
  运营商。



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


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


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


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





依赖关系



对于那些书架上有厚厚的Standardese书籍且想知道我究竟在说什么的人,我将谈谈在标准中如何指定这一点。


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


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



  • 依赖类型(例如:类型模板参数T)

  • 依赖于值的表达式(例如:非类型模板参数N)

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



大多数规则都是直观的并且是递归构建的:例如,如果N是依赖于值的表达式,或T是依赖类型,则构造为T[N]的类型是依赖类型。有关依赖类型的详细信息,请参阅(14.6.2/1部分,依赖于类型的表达式(14.6.2.2)和依赖于值的表达式的(14.6.2.3)


从属名称



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


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



  名称是标识符(2.11),operator-function-id(13.5),conversion-function-id(12.3.2)或template-id(14.2)的使用,表示实体或标签(6.6.4, 6.1)



标识符只是一个简单的字符/数字序列,而接下来的两个是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 
     { };
    

  • 在使用声明中,在最后::之后不可能使用template,并且C ++委员会表示不会在解决方案上工作。[168]


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


其它参考1


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


当前实例化



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


根据这一概念,该语言表示CurrentInstantiation::FooFooCurrentInstantiationTyped->Foo(如A *a = this; a->Foo)都是当前实例化的成员 < strong> if 它们被发现是当前实例化或其非依赖基类之一的类的成员(通过立即执行名称查找)。


如果限定符是当前实例化的成员,则现在不再需要关键字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::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 既不是当前实例化的成员< em>也不是未知专业化的成员。在这种情况下,该代码不应该在实例化时有效编译,因此该语言禁止一个名称,其中限定符是当前实例化既不是未知专业化的成员也不是当前实例化的成员(但是,这种违规行为仍然不需要被诊断出来)。


例子和琐事



您可以在这个答案上尝试这些知识,看看上面的定义是否对您在一个真实世界的例子中有意义(在该答案中,它们的重复性稍差一些)。


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时,Dominance规则适用,而查找将找到A::f[170]

其它参考2



  的前言

  
  这篇文章是一个易于阅读的替代litb的帖子。

  
  根本目的是一样的;对何时?的解释和为什么?必须适用typenametemplate






typenametemplate的目的是什么?



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


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


我们提到这样的名称,在解释中可能存在歧义,因为; 依赖名称。


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





一个SNACKET超过1000字



尝试解释以下 function-template 中发生的事情,无论是对自己,朋友还是你的猫;标记为( A )的语句中发生了什么?


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




它可能不像人们想象的那么容易,更具体地说,评估( 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 )中一样,我们将声明一个指针到int 命名为 x ,但是;

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










理由



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


为了防止实现可能遭受令人讨厌的意外,标准要求我们通过显式地来解决依赖名称的模糊性,在任何我们想要的地方陈述意图将名称视为类型名称或模板ID 。


如果未说明任何内容,则依赖名称将被视为变量或函数。









如何处理相关名称?



如果这是好莱坞电影,依赖名称将是通过身体接触传播的疾病,立即影响其主人,使其混淆。混乱可能会导致一个形式不良的perso-,erhm ..计划。


依赖名称是任何名称,它直接或间接依赖于模板参数。





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    
}


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



  • 电子)


    • type取决于SomeTrait<T>的实例化,包括T和;


  • ˚F)


    • NestedTrait,这是模板ID ,取决于SomeTrait<T>和;

    • ( F )末尾的
    • type取决于 NestedTrait ,它取决于SomeTrait<T>和;


  • <强G )


    • 数据,看起来像成员函数模板,间接是依赖名称,因为的类型foo 取决于SomeTrait<T>的实例化。




如果编译器解释依赖名称<,则语句( E ),( F )或( G )均无效/em>作为变量/函数(如前所述,如果我们没有明确说明,会发生什么)。





解决方案



要使g_tmpl具有有效的定义,我们必须明确告诉编译器我们期望( E )中的类型, template-id 和在( F )中键入,在( G )中键入 template-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
}


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


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









我是否只能将关键字粘贴在任何名称的前面?




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



标准中的规则规定,只要您处理限定名称( K ),就可以应用关键字,但如果名称不是t 合格应用程序格式错误( 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 明确不允许的上下文:



  • 指定类继承的基础


    在派生类s base-specifier-list 中编写的每个名称都被视为类型名称,明确指定typename是不正确的,多余的。


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





  • template-id 是派生类s using-directive 中引用的模板ID


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


其它参考3


typedef typename Tail::inUnion<U> dummy;


但是,我我不确定你重新实现inUnion是否正确。如果我理解正确,这个类不应该被实例化,因此失败选项卡永远不会失败。也许最好用简单的布尔值来指示类型是否在union中。


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 [172]


PS2:看一下类型列表,特别是在Andrei Alexandrescu的书中:现代C ++设计[173]

其它参考4


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






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


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\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