我必须在何处以及为何要使用“模板”和“typename”关键字?

时间:2009-03-04 11:56:17

标签: c++ templates typename c++-faq dependent-name

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

8 个答案:

答案 0 :(得分:1039)

答案 1 :(得分:130)

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在某处明确专门化,也不会影响该模板中的代码,因此其含义不会受到{{1 }}!)。

当前实例化

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

根据这一概念,该语言表示ACurrentInstantiation::FooFoo(例如CurrentInstantiationTyped->Foo)都是当前实例化的成员 if 它们被发现是一个类的成员,该类是当前实例化或其非依赖基类之一(通过立即执行名称查找)。

如果限定符是当前实例化的成员,则现在不再需要关键字A *a = this; a->Footypename。这里要记住的一个关键点是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::result_type(即使它在定义时已经发现其含义)。当现在查找结果不同或导致模糊时,程序是不正确的,必须给出诊断。想象一下,如果我们像这样定义D::f会发生什么

C

在实例化template<> struct C<int> { typedef bool result_type; typedef int questionable_type; }; 时,需要编译器捕获错误。因此,您将获得两个世界中最好的一个:“延迟”查找保护您,如果您可能遇到依赖基类的问题,以及“立即”查找,使您从D<int>::ftypename中解脱出来。

未知专业化

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

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

A

在C ++ 03中,语言允许捕获此错误,因为永远不会有一种有效的方法来实例化void h() { typename A<T>::questionable_type x; } (无论你给A<T>::h提供什么参数)。在C ++ 11中,该语言现在进一步检查,以便为编译器提供更多理由来实现此规则。由于T没有依赖基类,A声明没有成员A,因此名称questionable_type 当前实例化的成员< em>也不是未知专业化的成员。在这种情况下,该代码无法在实例化时有效编译,因此该语言禁止一个名称,其中限定符是当前实例化既不是未知专业化的成员也不是当前实例化的成员(但是,这种违规行为仍然不需要被诊断出来。)

示例和琐事

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

C ++ 11规则使以下有效的C ++ 03代码格式不正确(C ++委员会不打算这样做,但可能不会修复)

A<T>::questionable_type

这个有效的C ++ 03代码会在实例化时将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(); } 绑定到this->f,一切都很好。但是,C ++ 11会立即将其绑定到A::f,并在实例化时需要进行双重检查,检查查找是否仍然匹配。但是,在实例化B::f时,Dominance Rule会应用,而查找会找到C<A>::g

答案 2 :(得分:85)

  

<强>前言

     

这篇文章旨在成为litb's post易于阅读的替代方案。

     

根本目的是相同的;解释&#34;何时?&#34;和&#34;为什么?&#34; typename必须适用。


templatetypename的目的是什么?

templatetypename可用于声明模板以外的其他情况。

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

我们指的是这样的名称,在解释中可能存在歧义,因为; &#34; 依赖名称&#34;。

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


一个SNACKET超过1000字

尝试向您自己,朋友或您的猫解释以下 function-template 中发生的事情;标记为( A )的语句中发生了什么?

template


它可能不像人们想象的那么容易,更具体地说,评估( A )的结果依赖对作为模板参数传递的类型的定义{{1} }。

不同的template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ } 可以彻底改变所涉及的语义。

T


两种不同的情景

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

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



THE RATIONALE

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

为了防止实施可能遭受令人讨厌的意外,标准要求我们通过明确地在任何地方陈述意图来解决依赖名称的含糊不清# 39; d喜欢将名称视为类型名称模板ID

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



如何处理相关名称

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

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

T

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

  • <强>电子
    • &#34;类型&#34; 取决于struct X { typedef int foo; }; /* (C) --> */ f_tmpl<X> (); struct Y { static int const foo = 123; }; /* (D) --> */ f_tmpl<Y> (); 的实例化,其中包括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 } 和;
  • ˚F
    • &#34; NestedTrait&#34; ,这是模板ID ,取决于SomeTrait<T>和;
    • &#34;在<(em> F )末尾输入&#34; 取决于 NestedTrait ,这取决于{{1} },和;
  • <强“G
    • &#34;数据&#34; ,看起来像成员函数模板,间接是依赖名称,因为 foo 的类型取决于T
    • 的实例化

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

解决方案

要使SomeTrait<T>具有有效的定义,我们必须明确告诉编译器我们期望( E )中的类型, template-id 和( F )中的类型,( G )中的 template-id

SomeTrait<T>

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

然而,{p> g_tmpl在这方面有所不同,因为没有办法得出结论,例如; &#34;哦,这是一个模板,那么另外一件事也必须是模板&#34; 。这意味着我们会将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 } 直接应用于我们希望视为的任何名称



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

  

&#34; 我可以将typenametemplate放在任何名字前面吗?我不想担心它们出现的背景...... &#34; - template

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

typename

template

注意:在不需要的情况下应用Some C++ Developernamespace 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 明确不允许的情境:

  • 指定类继承的基础

    在派生类的 base-specifier-list 中编写的每个名称都被视为类型名称,明确指定template是既形象错误又多余。

    typename


  • template-id 是派生类中引用的 using-directive < / p>

    template

答案 3 :(得分:20)

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

PS2:看看typelists,特别是在Andrei Alexandrescu的书中:现代C ++设计

答案 4 :(得分:18)

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


放置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

答案 5 :(得分:2)

我将JLBorges出色的response放在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 known
    x.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 phase
    std::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,前提是您具有引用类型且取决于模板参数的限定名称。

答案 6 :(得分:1)

C++20 又名 C++2a

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

特别是,大多数 usingtypedef 声明现在可以不用 typename 编写。 typename 也可以在方法返回类型的声明(包括尾随返回类型)、方法和 lambda 参数的声明以及 static_castconst_cast、{ {1}} 和 dynamic_cast

一个值得注意的例外,其中 reinterpret_cast 仍然需要,是在用户或库定义的模板实例化的参数列表中:即使,如果该特定参数被声明为类型,typename关键字仍然是必需的。所以 typename 在 C++20 中是合法的,但 static_cast<A::B>(arg) 是格式错误的,如果 A 是一个依赖作用域并且 my_template_class<A::B>(arg) 需要一个类型。

几个例子:

my_template_class

答案 7 :(得分:0)

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

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

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

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