正式地说,类型名是什么意思?

有时候,我看到 gcc在使用模板时吐出了一些难以辨认的错误信息... ... 具体来说,我遇到过这样的问题: 看似正确的声明会导致非常奇怪的编译错误,通过在声明的开头加上 typename关键字,这些错误就神奇地消失了... ... (例如,就在上周,我声明了两个迭代器作为另一个模板类的成员,我不得不这样做) ... ..。

typename有什么新闻?

94002 次浏览

以下是约瑟蒂斯书中的一段话:

引入了关键字 typename 指定标识符 Following 是一个类型 以下例子:

template <class T>
Class MyClass
{
typename T::SubType * ptr;
...
};

在这里,使用 typename来澄清这一点 SubTypeclass T的一种。因此, ptr是指向类型的指针 没有 typenameSubType 将被视为静态成员。 因此

T::SubType * ptr

就是价值的倍增 T型与 ptr型的 SubType

考虑一下代码

template<class T> somefunction( T * arg )
{
T::sometype x; // broken
.
.

不幸的是,编译器并不需要是通灵的,并且不知道 T: : some type 最终是引用类型名还是 T 的静态成员,因此,使用 typename来告诉它:

template<class T> somefunction( T * arg )
{
typename T::sometype x; // works!
.
.

两种用途:

  1. 作为 template参数关键字(而不是 class)
  2. typename关键字告诉编译器标识符是类型(而不是静态成员变量)
template <typename T> class X  // [1]
{
typename T::Y _member;  // [2]
}

秘诀在于模板可以针对某些类型进行专门化。这意味着它还可以为几种类型定义完全不同的接口。例如,你可以写:

template<typename T>
struct test {
typedef T* ptr;
};


template<>         // complete specialization
struct test<int> { // for the case T is int
T* ptr;
};

有人可能会问,为什么这样做是有用的,实际上: 这看起来真的没有用。但是,请记住,例如 std::vector<bool>reference类型看起来完全不同于其他 T。诚然,它不会改变类型的 reference从一个不同的东西,但它仍然可能发生。

现在,如果使用这个 test模板编写自己的模板,会发生什么情况呢

template<typename T>
void print(T& x) {
test<T>::ptr p = &x;
std::cout << *p << std::endl;
}

对你来说似乎没问题,因为你的 期待test<T>::ptr是一种类型。但是编译器并不知道,事实上,标准甚至建议他期望相反的结果,test<T>::ptr不是一种类型。为了告诉编译器你想要什么,你必须在之前添加一个 typename。正确的模板如下所示

template<typename T>
void print(T& x) {
typename test<T>::ptr p = &x;
std::cout << *p << std::endl;
}

底线: 每当在模板中使用模板的嵌套类型之前,必须添加 typename。(当然,只有在模板的模板参数用于该内部模板的情况下。)

在某些情况下,如果你引用一个所谓的 受供养人类型的成员(意思是“依赖于模板参数”) ,编译器不能总是毫不含糊地推断出结果结构的语义含义,因为它不知道这是什么类型的名称(即是否是一个类型的名称,一个数据成员的名称或其他名称)。在这种情况下,您必须通过显式地告诉编译器该名称属于定义为该依赖类型的成员的类型名称来消除这种情况的歧义。

比如说

template <class T> struct S {
typename T::type i;
};

在此示例中,编译代码所必需的关键字 typename

当您希望引用依赖类型的模板成员时,也会发生同样的事情,即引用指定模板的名称。您还必须使用关键字 template来帮助编译器,尽管它的位置不同

template <class T> struct S {
T::template ptr<int> p;
};

在某些情况下,可能有必要同时使用这两种方法

template <class T> struct S {
typename T::template ptr<int>::type i;
};

(如果我的语法正确的话)。

当然,关键字 typename的另一个角色是在模板参数声明中使用。

#include <iostream>


class A {
public:
typedef int my_t;
};


template <class T>
class B {
public:
// T::my_t *ptr; // It will produce compilation error
typename T::my_t *ptr; // It will output 5
};


int main() {
B<A> b;
int my_int = 5;
b.ptr = &my_int;
std::cout << *b.ptr;
std::cin.ignore();
return 0;
}

我想所有的答案都提到了 typename关键字,在两种不同的情况下使用:

A)在声明模板类型参数时。

template<class T> class MyClass{};        // these two cases are
template<typename T> class MyNewClass{};  // exactly the same.

他们之间没有区别,他们是完全一样的。

B)在使用 < em > 嵌套依赖类型名称 作为模板前。

template<class T>
void foo(const T & param)
{
typename T::NestedType * value; // we should use typename here
}

不使用 typename会导致解析/编译错误。

我想对第二种情况进行补充,正如 Scott Meyers 的书 有效的 C + + 中提到的,在 < em > 嵌套的依赖类型名称 之前使用 typename是一个例外。例外的情况是,如果您使用 < em > 嵌套的依赖类型名称 作为 基础课程或在 成员初始化列表中,您不应该在那里使用 typename:

template<class T>
class D : public B<T>::NestedType               // No need for typename here
{
public:
D(std::string str) : B<T>::NestedType(str)   // No need for typename here
{
typename B<T>::AnotherNestedType * x;     // typename is needed here
}
}

注意: 对于第二种情况(即在嵌套依赖类型名称之前)使用 typename是不需要的,因为 C + + 20。