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


当前回答

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。

其他回答

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

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


使用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>; }

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;

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。