在模板,在哪里,为什么我必须把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::模板类型;/ /不规范的 使用基础::类型;/ /法律 };

其他回答

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++设计

这个答案是一个相当简短和甜蜜的回答,以回答标题问题(部分)。如果你想要一个更详细的答案来解释为什么你要把它们放在那里,请点击这里。


使用typename关键字的一般规则主要是当你使用模板形参并且你想要访问一个嵌套的typedef或使用-alias时,例如:

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

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

Typename ->告诉编译器依赖项名称是实际类型 模板<类T> struct DependentType { typename T::type a; 使用Type=typename T:: Type; }; Template ->告诉编译器依赖的名称是一个模板函数/类 模板<类T> struct DependentTemplate { //模板函数 模板<类U> 静态无效func() {} //模板类 模板<类U> struct类名{}; }; 模板<类T1,类T2> 空白foo () { //调用依赖模板函数的3种方法 DependentTemplate < T1 >::模板函数< T2 > (); DependentTemplate < T1 >()。模板函数< T2 > (); (new DependentTemplate<T1>()))->template func<T2>(); //你需要同时使用typename和template来引用依赖的模板类 typename DependentTemplate<T1>::template ClassName<T2> obj; 使用Type=typename DependentTemplate<T1>::template ClassName<T2>; }

我把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,前提是您有一个引用类型并依赖于模板形参的限定名称。

前言 这篇文章旨在成为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::模板类型;/ /不规范的 使用基础::类型;/ /法律 };