身无所拘,心无疆

C++ 模版元编程 第 1 节 --- 「泛化」和「特化」

2019-12-28

我今天又看了 10 MB 的错误信息

前言

上一篇文章讲了一些基础前提以后,这里开始进入 TMP 真正有趣的地方。

泛化

我们有一个做除法的静态成员函数(别问我为什么是静态成员函数而不是全剧函数):

1
2
3
4
5
class Div {
static double doit(double lhs, double rhs) {
return lhs / rhs;
}
}

但是这个函数只能对 double 类型做除法,现在我们需要将其中的算法逻辑抽象出来,让它能对所有类型都做除法,于是我们引入模版参数 T,来代表一个任意类型

1
2
3
4
5
6
template <typename T>
class Div {
static T doit(T lhs, T rhs) {
return lhs / rhs;
}
}

好了,现在我们的 Div::doit 就能应用到任意支持「除法」运算的类型上了。(这里强调支持「除法」运算是因为如果 T 不是原始数据类型并且也没有重载正确的 operator/ 时,编译器会报错)。


如你所见,上面的过程就叫做「泛化」。我们说泛化后的模版类 Div 是「原型」,也叫 Div 的「泛化版本」。


(其实我非常不想扯名词的,但后文需要,没办法

特化

在上面的例子中,当 Tint 的时候,而 rhs 又恰好为 0,那么上面的代码就会出现运行时异常。

所以我们可以int 类型特化 Div 来处理这种特殊情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <>
class Div<int> { /* 为 int 类型特化 */
static int doit(int lhs, int rhs) {
if (rhs == 0) {
printf("Divide by zero\n");
return 0;
}
return lhs / rhs;
}
}

void foo() {
Div<int>::doit(1, 0); /* Divide by zero */
Div<double>::doit(1, 2); /* 0.5 */
}

等等!那个 template <> 是什么玩意?!
你哪来这么多问题!,在模版匹配规则的地方会详细讲这个东西。

现在可以理解为 「Div 在为 int 特化后,不再需要额外的模版参数」
那既然不需要模版参数了,那能不能去掉这一行呢?

答案是不行,因为这个特化依赖于原型。关键字 template 相当于告诉编译器:这是一个模版类的特化版本,不是独立存在的一个类。


让「原型」在遇到不同的类型时执行不同的代码,从而做到处理特殊情况。这种方法就叫做「特化」,更准确的说,

特化(specialization)是根据一个或多个特殊的整数或类型,给出模板实例化时的一个指定内容。

特化,就是处理特例。我们在数学中学过「斐波那契数列」,这个数列满足下面的关系:

1
2
3
F(0) = 1
F(1) = 1
F(n) = F(n - 1) + F(n - 2), n >= 2

它有两个特例,当 n == 0 或 n == 1 时,F(n) 的值恒为 1。

显然, F(0) 和 F(1) 就是在处理斐波那契数列中的特例,这就是「特化」。

实战:编译期斐波那契数列

真就说什么来什么,我们来整一个利用 TMP 特性实现的编译期斐波那契数列。

我们先把一般情况写出来,也就是「原型」:

1
2
3
4
5
6
7
8
/* 这里有疑惑的自觉看上一章 */
template <int N>
struct Fib {
/* 递归调用元函数 Fib 计算第 N 项的值 */
/* constexpr 会「请求」编译器在编译期试着对这个值求值 */
static constexpr int value =
Fib<N - 1>::value + Fib<N - 2>::value;
};

然后再处理 N 为 1 和 2 时的特殊情况,注意!!!我要特化了!!!

处理 $F_{0} = 1$

1
2
3
4
template <>
struct Fib<0> {
static constexpr int value = 1;
};

处理 $F_{1} = 1$

1
2
3
4
template <>
struct Fib<1> {
static constexpr int value = 1;
};

然后我们来测试一下:

1
static_assert(Fib<20>::value == 10946, "You wrote a bug");

嗯!我没写出 bug!

偏特化

后面讲到模版匹配规则的时候会再详细来说。
可以先看参考文献里的资料。

参考文献

Tags: C++
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章