C++ 中让人头晕的 typedef & typename

用过 C++ 的同学对 typename 和 typedef 相信并不是很陌生,但是当我看到下面这段代码的时候仍然无法理解:

typedef typename std::vector<T>::size_type size_type;

按理来说 typedef 一般不是用来定义一种类型的别名,如下:

typedef int SpeedType;

定义了一个 int 的别名是 SpeedType,那么我就可以这样用:

int main(void)
{
    SpeedType s = 10;
    printf("speed is %d m/s",s);
    return 0;
}

但是 typedef 后面接 typename 表示什么意思呢?typename 不是用来定义模板参数的吗?下面我们分别归纳一下 typedef & typename 的用法。

typedef

首先来看看 typedef 的几种常见用法。

为特定含义的类型取别名

这个我在上面已经讲过了,但是它是定义一种类型的别名,而不只是简单的宏替换,也可以用作同时声明指针型的多个对象的。

比如:

char* pa, pb;  
cout << typeid(pa).name() << endl; //Pc
cout << typeid(pb).name() << endl; //c

本来想要把 pa、pb两个变量都声明为字符串,但是这样只能成功声明了一个。但是我们使用 typedef 就可以成功声明两个:

typedef char* PCHAR; 
PCHAR pa, pb; // 可行
cout << typeid(pa).name() << endl; //Pc
cout << typeid(pb).name() << endl; //Pc

为结构体取别名

在声明变量的时候,需要带上struct,即像下面这样使用:

typedef struct info
{
    char name[128];
    int length;
}Info;

Info var;

用来定义与平台无关的类型

比如定义一个叫 REAL 的浮点类型,在目标平台一上,让它表示最高精度的类型为:

typedef long double REAL;

在不支持 long double 的平台二上,改为:

typedef double REAL;

当跨平台时,只要改下 typedef 本身就行,不用对其他源码做任何修改。

typename

typename关键字用于引入一个模板参数,这个关键字用于指出模板声明(或定义)中的非独立名称(dependent names)是类型名,而非变量名:

template <typename T>
const T& max(const T& x, const T& y)
{
  if (y < x) {
    return x;
  }
  return y;
}

typename 在这里的意思表明 T 是一个类型。如果没有它的话,在某些情况下会出现模棱两可的情况,比如下面这种情况:

template <class T>
void foo() {
    T::iterator * iter;
    // ...

}

作者想定义一个指针iter,它指向的类型是包含在类作用域T中的iterator。可能存在这样一个包含iterator类型的结构:

struct ContainsAType {
    struct iterator { /*...*/ }; 
};

那么 foo<ContainsAType>(); 这样用的是时候确实可以知道 iter是一个ContainsAType::iterator类型的指针。但是T::iterator实际上可以是以下三种中的任何一种类型:

  • 静态数据成员
  • 静态成员函数
  • 嵌套类型

所以如果是下面这样的情况:

struct ContainsAnotherType {
    static int iterator;
    // ... 
};

T::iterator * iter;被编译器实例化为ContainsAnotherType::iterator * iter;,变成了一个静态数据成员乘以 iter ,这样编译器会找不到另一个变量 iter 的定义 。所以为了避免这样的歧义,我们加上 typename,表示 T::iterator 一定要是个类型才行。

template <class T>
void foo() {
    typename T::iterator * iter;
    // ... 
}

得出结论

我们回到一开始的例子,对于 vector::size_type,我们可以知道:

template <class T,class Alloc=alloc>
class vector{
public:
    //...
    typedef size_t size_type;
    //...
};

vector::size_typevector的嵌套类型定义,其实际等价于 size_t类型。

typedef typename std::vector<T>::size_type size_type;

那么这个例子的真是面目是,typedef创建了存在类型的别名,而typename告诉编译器std::vector<T>::size_type是一个类型而不是一个成员。

加一个例子

img

好了,看了上面的例子你应该已经完全懂 typedef & typename 的精髓了,我们下面来讲解一个例子,用模板实现类似下面的循环:

int result = 0;
while (n != 0) {
  result = result + n;
  n = n - 1;
}

首先我们需要一个循环模板:

template <bool condition,typename Body>
struct WhileLoop;

template <typename Body>
struct WhileLoop<true, Body> {
    typedef typename WhileLoop<Body::cond_value, typename Body::next_type>::type type;
};

template <typename Body>
struct WhileLoop<false, Body> {
    typedef typename Body::res_type type;
};

template <typename Body>
struct While {
    typedef typename WhileLoop< Body::cond_value, Body>::type  type;
};

这里应该可以看的懂,这几个模板,无论是 res_type 还是 type 都用 typename 修饰,表明都是类型,然后再接上 typedef 表示给这个类型定义了一个别名。

再定义循环模板的时候,有一个约定,它必须提供一个静态数据成员,cond_value,及两个子类型定义,res_type 和 next_type:

  • cond_value 代表循环的条件(真或假),表明直接是一个确定的 bool 类型的静态数据成员,没有用 typename 修饰;

  • res_type 代表退出循环时的状态,是一个类型而不是一个成员;

  • next_type 代表下面循环执行一次时的状态,是一个类型而不是一个成员;

WhileLoop 用特化来决定走递归分支还是退出循环分支。

然后我们定义一个模板代表数值:

template <class T, T v>
struct integral_constant {
    static const T value = v;
    typedef T value_type;
    typedef integral_constant type;
};

通过 value 可以获取到对应的数值,value_type 则是这个数值的类型。

template <int result, int n>
struct SumLoop {
    static const bool cond_value =
        n != 0;
    static const int res_value =
        result;
    typedef integral_constant< int, res_value>  res_type;
    typedef SumLoop<result + n, n - 1>  next_type;
};

template <int n>
struct Sum {
    typedef SumLoop<0, n> type;
};

通过上面的模板可以实现 While<Sum<10>::type>::type::value 1 加到 10 的结果。实际上就是通过类型的循环展开实现了 1 加到 10 运算结果。

Reference

https://feihu.me/blog/2014/the-origin-and-usage-of-typename/

https://www.cnblogs.com/charley_yang/archive/2010/12/15/1907384.html

https://time.geekbang.org/column/intro/100040501

扫码_搜索联合传播样式-白色版 1