c++ - 函数别名 - template typename type




我在哪里以及为什么必须放置“模板”和“类型名称”关键字? (4)

前言

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

其基本目的是一样的; 对“什么时候?”的解释 和“为什么?” 必须应用类型名称和template

typenametemplate的用途是什么?

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

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

我们指的是这样的名字,在解释中可能有不明确的地方, “ 依赖名称 ”。

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

一个SNIPPET说超过1000字

尝试解释下面的函数模板中发生了什么 ,或者是你自己,一个朋友,或者是你的猫; 标有( 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

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


如何处理相关的名字

如果这是一部好莱坞电影, 依赖性名称将是通过身体接触传播的疾病,立即影响主机使其感到困惑。 这种混淆可能会导致形成一个不合格的人员计划。

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

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
    • 作为模板标识的 “NestedTrait”取决于SomeTrait<T> ,并且;
    • F )结尾的“type”取决于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直接应用于任何我们想要的名称前面。


我可以在任何名称的前面添加关键字吗?

我可以在任何名字前面加上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

  • 指定类继承的基础时

    写在派生类的base-specifier-list中的每个名字都已被视为类型名称 ,明确指定typename既是格式错误的,也是冗余的。

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


  • 模板标识符是派生类的using-directive中引用的模板标识符时

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

在模板中,为什么我必须在相关名称上放置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的问题是在typedef Tail::inUnion<U> dummy行中。 我相当肯定,在inUnion是一个独立的名称,而VC ++是完全正确的inUnion它。 我也知道我应该可以在某处添加template来告诉编译器inUnion是模板标识。 但究竟在哪里? 那么它是否应该假设inUnion是一个类模板,即inUnion<U>是一个类型而不是函数?


C ++ 11

问题

尽管关于何时需要typenametemplate C ++ 03中的规则在很大程度上是合理的,但其制定有一个恼人的缺点

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的后续专门化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: 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 既不是当前实例化的成员, 也不是未知专业化的成员。 在这种情况下,代码在实例化时无法有效编译,所以语言禁止一个名称,其中限定符是当前实例化,既不是未知专业化的成员,也不是当前实例的成员(但是,这种违规行为仍然不需要被诊断)。

示例和琐事

你可以在这个答案上尝试这些知识,看看上面的定义对于你是否对现实世界中的例子有意义(它们在那个答案中稍微不那么详细)。

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


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

t * f;

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

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

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

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

让我们等待,直到用户实例化模板,然后再找出t::x * f;的真实含义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;

该语法仅允许在限定名称之前使用类型名称 - 因此,如果他们这样做,就会认为非限定名称总是被称为类型。

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

“模板”关键字

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

boost::function< int() > f;

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

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

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

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

现在我们回到类型名称相同的问题。 如果我们在解析代码时不知道该名称是否为模板,该怎么办? 我们需要在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.3)以及与值相关的表达式的(14.6.2.3)

相关名称

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

为了避免这个问题,我对标准文本做了一个简单的解释。 在表示相关类型或表达式的所有结构中,它们的一个子集表示名称。 这些名字因此是“独立名称”。 名称可以采用不同的形式 - 标准说:

名称是标识符(2.11),运算符函数id(13.5),转换函数id(12.3.2)或模板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 
     { };
    
  • 在使用声明中,不可能在last ::之后使用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
     };
    

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





dependent-name