用过 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_type
是vector
的嵌套类型定义,其实际等价于 size_t
类型。
typedef typename std::vector<T>::size_type size_type;
那么这个例子的真是面目是,typedef
创建了存在类型的别名,而typename
告诉编译器std::vector<T>::size_type
是一个类型而不是一个成员。
加一个例子
好了,看了上面的例子你应该已经完全懂 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