何处以及为什么必须放置“模板”和“types名称”关键字?

在模板中,为什么我必须将typename名称和template放在相关名称上? 究竟什么是依赖名称呢? 我有以下代码:

 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是一个模板标识。 但究竟在哪里? 那么它是否应该假定inUnion是一个类模板,即inUnion<U>是一个types而不是函数?

为了parsing一个C ++程序,编译器需要知道某些名字是不是types的。 以下示例演示:

 t * f; 

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

有些名称表示types或模板。 一般来说,每遇到一个名字,在继续parsing包含它的程序之前,有必要确定这个名字是否表示这些实体之一。 确定这个过程称为名称查找。

如果t指向模板types参数,编译器将如何找出t::x引用的名称? x可以是一个静态的int数据成员,可以相乘,也可以是一个嵌套类或typedef,可以产生一个声明。 如果一个名字具有这个属性 – 直到实际的模板参数被知道才能被查找 – 那么它被称为一个依赖名称 (它依赖于模板参数)。

您可能会推荐等到用户实例化模板:

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

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

所以必须有一种方法来告诉编译器某些名称是types的,而某些名称不是。

“typename”关键字

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

除非可用的名称查找findtypes名称或名称由关键字typename限定,否则假定在模板声明或定义中使用的名称(取决于模板参数)不会命名types。

有许多不需要typename名称,因为编译器可以在模板定义中使用可用的名称查找,找出如何parsing构造本身 – 例如T *f; ,当T是一个types模板参数。 但是对于t::x * f; 作为一个声明,它必须写成typename t::x *f; 。 如果省略关键字并且该名称被认为是非types的,但是当实例化发现它表示types时,编译器会发出常见的错误消息。 有时候,错误会在定义的时候给出:

 // 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::xf; 

该语法只允许在限定名称之前的types名称 – 因此,如果这样做的话,那么总是可以知道非限定名称指的是types。

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

“模板”关键字

请记住上面的初始报价以及标准如何对模板进行特殊处理? 我们来看看下面那个天真的例子:

 boost::function< int() > f; 

对于读者来说,这可能看起来很明显。 编译器不是这样的。 想象下面的boost::functionf任意定义:

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

这实际上是一个有效的expression ! 它使用小于运算符来比较boost::function和zero( int() ),然后使用大于运算符来比较结果boolf 。 然而,正如你可能知道的那样, 现实生活中的boost::function是一个模板,所以编译器知道(14.2 / 3):

名称查找(3.4)发现名称是模板名称后,如果该名称后面跟着一个<,则<始终将<始终作为模板参数列表的开始,而不会作为名称,比运营商。

现在我们回到与typename相同的问题。 如果我们在parsing代码的时候不知道这个名字是否是一个模板呢? 我们将需要在模板名称之前立即插入template ,如14.2/4 。 这看起来像:

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

模板名称不仅可以出现在:: ,也可以出现在->或之后. 在一个class级成员访问。 你也需要在那里插入关键字:

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

依赖

对于那些书架上厚厚的Standaldese书籍,想要知道我在说什么的人,我会谈谈标准中是如何规定的。

在模板声明中,根据你用来实例化模板的模板参数,一些结构具有不同的含义:expression式可能有不同的types或值,variables可能有不同的types,或者函数调用可能最终调用不同的函数。 通常认为这样的结构依赖于模板参数。

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

  • 相关types(例如:types模板参数T
  • 依赖于值的expression式(例如:非types模板参数N
  • 依赖于types的expression式(例如:转换为types模板参数(T)0

大多数规则是直观的,并recursion地构build:例如,如果N是依赖于值的expression式,或者T是从属types,则构造为T[N]的types是依赖types。 详细内容可以在依赖types(14.6.2/1 )中读取, (14.6.2.2)对于依赖于types的expression式, (14.6.2.3)对于依赖于值的expression式。

相关的名字

该标准有点不清楚什么是一个依赖名称 。 在简单的阅读(你知道,最less惊喜的原则),所有它定义为一个独立的名字是下面的函数名称的特殊情况。 但是由于显然T::x也需要在实例化上下文中查找,所以它也需要是一个独立的名字(幸运的是,在C ++中期之后,委员会已经开始研究如何解决这个混淆的定义) 。

为了避免这个问题,我对标准文本做了一个简单的解释。 在表示相关types或expression式的所有结构中,它们的一个子集表示名称。 这些名字因此是“依赖名字”。 名字可以采取不同的forms – 标准说:

名称是标识符(2.11),运营商function标识符(13.5),转换function标识符(12.3.2)或模板标识符(14.2)的使用,表示实体或标签(6.6.4, 6.1)

标识符只是一个普通的字符/数字序列,而接下来的两个是operator +operator type表单。 最后一个forms是template-name <argument list> 。 所有这些都是名称,通过标准中的常规使用,名称还可以包含限定符,用于说明应查找名称空间或类名称。

依赖于值的expression式1 + N不是名称,而是N 所有名称依赖结构的子集称为依赖名称 。 但是,函数名称在模板的不同实例化中可能具有不同的含义,但不幸的是,这个通用规则并没有捕获到这些含义。

依赖函数名称

主要不是这篇文章的关注,但仍值得一提:函数名是一个exception,分开处理。 标识符函数名称不是由它自己决定的,而是由调用中使用的依赖于types的参数expression式决定的。 在例子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 。 假定给出的名称是类types名称。 对于基类列表和构造函数初始化程序列表中的名称都是如此:

      template <typename T> struct derive_from_Has_type : /* typename */ SomeBase<T>::type { }; 
  • 在使用声明中,在last ::之后不可能使用template ,而C ++委员会表示不能在解决scheme上工作。

      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

问题

尽pipeC ++ 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 (因此也是一个types),并且this->g只能是稍后声明的成员模板g ,我们仍需要消歧义关键字(即使A明确地被专门化了,也不会影响该模板中的代码,所以它的含义不会受到A的后续专门化的影响)。

当前实例

为了改善这种情况,在C ++ 11中,语言跟踪何时types引用了封闭模板。 要知道,这个types必须是通过使用某种forms的名称(在上面的AA<T>::A<T> )中形成的。 被这样一个名称引用的types被称为当前的实例化 。 如果名称所属的types是成员/嵌套类(然后, A::NestedClassA都是当前实例化),那么可能有多个types都是当前实例化的。

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

如果限定符是当前实例的成员,现在不再需要关键字typenametemplate 。 这里需要记住的关键是A<T> 仍然是一个依赖于types的名字(毕竟T也是依赖于types的)。 但是A<T>::result_type被认为是一种types – 编译器会“神奇地”查看这种依赖types来解决这个问题。

 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时,情况总是如此,或者依赖types不是当前的实例化(在这种情况下,编译器可以放弃并说“我们稍后会看什么Foo )或者它当前的实例,并且在它或者它的非依赖的基类中没有find这个名字,并且还有依赖的基类。

想象一下,如果我们在上面定义的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; cg(); } 

这个有效的C ++ 03代码会在实例化时将this->f绑定到A::f ,一切正常。 然而,C ++ 11立即将它绑定到B::f并且在实例化时需要进行双重检查,检查查找是否仍然匹配。 但是,当实例化C<A>::g , 优势规则适用,查找将会findA::f

前言

这篇文章是一个易于阅读 的文章的替代品。

其基本目的是一样的。 对“什么时候?”的解释 和“为什么?” typenametemplate必须被应用。


typenametemplate的用途是什么?

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

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

我们指的是这样的名字,在那里可能有一个模糊的解释,因为; “ 依赖名称 ”。

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


一个SNIPPET说超过1000字

尝试解释下面的函数模板中发生了什么 ,或者是你自己,一个朋友,或者是你的猫; 标有( A )的声明中发生了什么?

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


它可能不如人们想象的那样容易,更具体地说,评估( A )的结果在很大程度上取决于作为模板参数T传递的types的定义。

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

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


两种不同的情况

  • 如果我们实例化types为X的函数模板,就像在( C )中一样,我们将声明一个名为x的 int指针 ,但是;

  • 如果我们使用typesY实例化模板(如( D )中所示),则( A )将由一个expression式计算123乘以某个已经声明的variablesx的乘积。


理由

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

为了防止实现可能遭受令人讨厌的意外,标准要求我们通过在任何我们希望将名称视为types名称模板的任何地方明确声明意图来理清依赖名称的含糊性, ID

如果没有说明, 依赖名称将被视为variables或函数。


如何处理相关的名字

如果这是一部好莱坞电影,那么依赖名字就是通过身体接触传播的疾病,立即影响到主人,使其感到困惑。 这种混乱可能会导致一个forms不健全的个人计划。

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

 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
    • “type”取决于SomeTrait<T>的实例化,其中包括T
  • F
    • 作为模板id的 “NestedTrait”取决于SomeTrait<T> ,并且;
    • F )结尾处的“type”取决于NestedTrait ,它取决于SomeTrait<T> ,并且;
  • G
    • 由于foo的types取决于SomeTrait<T>的实例化,所以看起来像成员函数模板的 “data”是间接的依赖名称

如果编译器将解释variables/函数作为variables/函数(如前所述,如果我们没有另外明确地说明会发生什么),则声明( E ),( F )或( G )都不是有效的。

解决scheme

为了使g_tmpl有一个有效的定义,我们必须明确地告诉编译器我们期望( E )中的types ,( F )中的模板标识符types以及( 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 } 

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

但是, template在这方面是不同的,因为没有办法得出如下结论: “哦,这是一个模板,比这个其他的东西还必须是模板” 。 这意味着我们将template直接应用于任何我们想要处理的名称前面。


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

我可以在名称前面加上typenametemplate吗?我不想担心它们出现的上下文… ” – 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显式禁止的:

  • 指定类inheritance的基础

    写在派生类的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中引用的模板id时

      struct Base { template<class T> struct type { }; }; struct Derived : Base { using Base::template type; // ill-formed using Base::type; // legal }; 
 typedef typename Tail::inUnion<U> dummy; 

但是,我不确定你在inUnion的实施是否正确。 如果我理解正确,这个类不应该被实例化,因此“失败”选项卡永远不会失败。 也许最好用一个简单的布尔值来表示types是否在联合中。

 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:看看types列表 ,特别是Andrei Alexandrescu的书:Modern C ++ Design

这个答案是为了回答(部分)标题问题而写的。 如果你想要更详细的答案,解释为什么你必须把它们放在那里,请到这里 。


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

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

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

鉴于这个结构和function:

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

这样编译器会正确parsing这个,而不是t.get < int