在模板,在哪里,为什么我必须把typename和模板的依赖名称? 到底什么是依赖名称?

我有以下代码:

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>虚线。我相当确定inUnion是一个依赖名称,vc++在使用它时是非常正确的。 我还知道,我应该能够在某处添加模板,告诉编译器inUnion是一个模板id。但具体在哪里呢?然后它是否应该假设inUnion是一个类模板,即inUnion<U>命名一个类型而不是一个函数?


当前回答

前言 这篇文章旨在成为litb的文章的一个易于阅读的替代品。

基本目的是一样的;必须应用typename和template来解释“什么时候?”和“为什么?”


typename和template的目的是什么?

Typename和template在声明模板以外的情况下可用。

c++中有一些特定的上下文,必须显式地告诉编译器如何处理一个名称,所有这些上下文都有一个共同点;它们依赖于至少一个模板参数。

在解释上可能有歧义的地方,我们把这样的名称称为;“依赖的名字”。

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


一个片段超过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",这是一个模板id,依赖于SomeTrait<T>,和; (F)末尾的“type”依赖于NestedTrait,而NestedTrait依赖于SomeTrait<T>,并且; G) "data",看起来像一个成员函数模板,间接是一个依赖名称,因为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,我们想这样对待。



我能把关键字放在任何名字前面吗?

“我能把typename和template放在任何名字前面吗?”我不想担心它们出现的背景……”-一些c++开发人员

标准中的规则规定,只要您处理的是限定名称(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

注意:在不需要的上下文中应用typename或template并不被认为是好的实践;仅仅因为你能做某件事,并不意味着你就应该去做。


此外,还有一些上下文显式禁止typename和template:

当指定类继承的基类时 在派生类的base-specifier-list中写入的每个名称都已被视为类型-name,显式指定typename既格式错误,又冗余。 // .------- base-specifier-list 模板<class T> // v struct派生:typename SomeTrait<T>::type /* <-格式错误*/ { ... }; 当template-id是派生类的using-directive中引用的模板id时 结构基{ 模板类T > < 结构类型{}; }; 派生结构:基{ 使用Base::模板类型;/ /不规范的 使用基础::类型;/ /法律 };

其他回答

c++ 20 aka + C+ 2a

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

特别是,大多数using和typedef声明现在都可以不用typename来编写。在方法返回类型的声明(包括后面的返回类型)中,在方法和lambda参数的声明中,以及在static_cast、const_cast、dynamic_cast和reinterpret_cast的类型参数中,Typename也可以省略。

一个值得注意的例外是,在用户或库定义模板的实例化参数列表中,typename仍然是必需的:甚至,如果某个特定参数被声明为类型,typename关键字仍然是必需的。因此,static_cast<A::B>(arg)在c++ 20中是合法的,但my_template_class<A::B>(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;

我把JLBorges对cplusplus.com上一个类似问题的出色回答逐字逐句地放在这里,因为这是我读过的关于这个问题的最简洁的解释。

在我们编写的模板中,可以使用两种类型的名称——依赖名称和非依赖名称。依赖名称是依赖于模板形参的名称;无论模板参数是什么,非依赖名称具有相同的含义。 例如: T > void foo(T& x, std::string str, int count) { //这些名字在第二阶段被查找 //当foo实例化并且类型T是已知的 x.size ();//依赖名称(非类型) T: instance_count;//依赖名称(非类型) T::迭代器i;//依赖名称(type) //在第一阶段, // T::instance_count被视为非类型(这是默认值) // typename关键字指定将T::iterator视为类型。 //在第一阶段中查找这些名称 Std::string::size_type s//不依赖的名称(type) std:: string:非营利组织;//不依赖的名称(non-type) str.empty ();//不依赖的名称(non-type) 计数;//不依赖的名称(non-type) } 对于模板的每个不同实例化,依赖名称所引用的内容可能是不同的。因此,c++模板受制于“两阶段名称查找”。当模板最初被解析时(在任何实例化发生之前),编译器会查找不依赖的名称。当模板的特定实例化发生时,模板参数那时就知道了,编译器会查找相关名称。 在第一个阶段,解析器需要知道依赖名称是类型名称还是非类型名称。默认情况下,依赖名称被假定为非类型的名称。依赖名称前的typename关键字指定它是类型的名称。


总结

仅在模板声明和定义中使用关键字typename,前提是您有一个引用类型并依赖于模板形参的限定名称。

C++11

问题

虽然c++ 03中关于何时需要typename和template的规则在很大程度上是合理的,但它的公式有一个令人讨厌的缺点

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中,当类型引用封闭模板时,语言会跟踪。要知道这一点,类型必须是通过使用某种形式的名称来形成的,该名称是它自己的名称(在上面的例子中,a, a <T>,:: a <T>)。通过这样的名称引用的类型就是当前实例化。如果形成名称的类型是一个成员/嵌套类,则可能有多个类型都是当前实例化(那么,a::NestedClass和a都是当前实例化)。

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

如果限定符是当前实例化的成员,关键字typename和template现在不再是必需的。这里需要记住的关键点是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时,需要编译器来捕获错误。因此,您可以获得两个世界的最佳效果:“延迟”查找保护您,如果您可能遇到依赖基类的麻烦,以及“立即”查找将您从typename和template中解放出来。

未知的专门化

在D的代码中,名称typename D::questionable_type不是当前实例化的成员。相反,该语言将其标记为未知专门化的成员。特别是,当你在执行DependentTypeName::Foo或DependentTypedName->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代码将在实例化时将->f绑定到A::f,一切正常。然而,c++ 11立即将其绑定到B::f,并要求在实例化时进行双重检查,检查查找是否仍然匹配。然而,当实例化C<A>::g时,优势规则应用,查找将找到A::f。

(这里也有我c++ 11的答案)

为了解析c++程序,编译器需要知道某些名称是否是类型。下面的例子说明:

t * f;

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

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

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

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

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

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

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

“typename”关键字

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

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

有许多名称不需要使用typename,因为编译器可以通过模板定义中适用的名称查找,找出如何解析结构本身——例如,当T是类型模板形参时,使用T *f;。但对于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::function和f的任意定义:

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

这实际上是一个有效的表达式!它使用小于操作符将boost::function与0 (int())进行比较,然后使用大于操作符将结果bool与f进行比较。然而,您可能已经知道,boost::function在现实生活中是一个模板,因此编译器知道(14.2/3):

在名称查找(3.4)发现一个名称是template-name之后,如果该名称后面跟着一个<,则<为 总是作为模板参数列表的开头,从不作为名称后面跟着小于号 操作符。

现在我们又回到了与typename相同的问题。如果在解析代码时还不知道名称是否是模板,该怎么办?我们需要在模板名称之前插入模板,就像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显然也需要在实例化上下文中查找,所以它也需要是一个依赖名称(幸运的是,从c++ 14中期开始,委员会已经开始研究如何修复这个令人困惑的定义)。

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

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

标识符只是字符/数字的普通序列,而接下来的两个是操作符+和操作符类型形式。最后一个形式是template-name <argument list>。所有这些都是名称,按照标准中的常规用法,名称还可以包含限定符,表示应该在哪个名称空间或类中查找名称。

依赖于值的表达式1 + N不是名称,但N是。作为名称的所有依赖结构的子集称为依赖名称。然而,在模板的不同实例化中,函数名可能有不同的含义,但不幸的是,这个通用规则不会捕捉到函数名。

依赖函数名

这不是本文主要关注的问题,但仍然值得一提:函数名是单独处理的异常。标识符函数名不是由其本身决定的,而是由调用中使用的类型依赖参数表达式决定的。在例子f((T)0)中,f是一个依赖名称。在标准中,这是在(14.6.2/1)中规定的。

其他注释和示例

在足够多的情况下,我们同时需要typename和template。您的代码应该如下所示

template <typename T, typename Tail>
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        typedef typename Tail::template inUnion<U> dummy;
    };
    // ...
};

关键字模板并不总是必须出现在名称的最后一部分。它可以出现在用作作用域的类名之前的中间,如下面的示例所示

typename t::template iterator<int>::value_type v;

在某些情况下,关键字是禁止的,具体如下所示

在依赖基类的名称上不允许写入typename。假设给出的名称是类类型名。基类列表和构造函数初始化列表中的名称都是这样: template <typename T> struct derive_from_Has_type: /* typename */ SomeBase<T>::type {}; 在using-declarations中,不可能在last::之后使用模板,c++委员会说不做解决方案。 template <typename T> struct derive_from_Has_type: SomeBase<T> { 使用SomeBase<T>::模板类型/ /错误 使用typename SomeBase<T>::type;// typename *is* allowed };

typedef typename Tail::inUnion<U> dummy;

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

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:看一看打字员,特别是在Andrei Alexandrescu的书:现代c++设计