4

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

 1 year ago
source link: https://www.luozhiyun.com/archives/742
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

Posted on 2023年1月15日 by luozhiyun

用过 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

CategoriescppTagscpp


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK