还是我们在之前的的一篇文章中说到过的问题:如何判断比一个给定数字小的最大质数呢?如果我们可以限定一个条件,就可以把这个计算过程从运行时搬编译时,也就是让编译器替我们计算好。
这个条件是什么呢?就是我们可以提前确定好要判断的数字,也就是说这个数字必须是硬编码在源代码里的,而不能是在运行时由用户输入的。其实,在现实的项目中,有很多要处理的数据都是静态的,因此这也并不是什么太过分的假设。
接下来,我们就来看下做法。这个算法还是分两部分:
- 判断一个数字是否是质数;
- 得到比给定数字小的最大质数;
让编译器判断是否是质数
先来看第一部分,我们直接上代码:
template<int M, int N = M - 1>
struct is_prime {
static_assert(M > 1, "M must be greater than 1.")
static const bool result = (M % N) && is_prime<M, N-1>::result;
};
其中,is_prime
有两个type parameter,M
表示我们要判断的数字,N
则是一个用于辅助计算的参数,它的默认值是M-1
。
在is_prime
的定义里,我们用递归的方式定义了result
。其实,算法本身要比运行时的版本简单很多,我们只要从M-1
开始比较,只要发现能整除M
的因子N
,result
的结果就是true
,否则,就继续递归到is_prime<M, N-1>::result
。并且,由于最小的值数是2,因此这里,我们用static_assert
强行约束了M
的值。
接下来,要计算is_prime<5>::result
,编译器就会生成这样的代码:
is_prime<5, 4>::result;
is_prime<5, 3>::result;
is_prime<5, 2>::result;
is_prime<5, 1>::result;
is_prime<5, 0>::result;
is_prime<5, -1>::result;
// ...
发现什么了么?编译器会在编译的过程中一直循环下去。这显然是不对的,因此,和定义运行时递归结束条件一样,我们也要给编译器定义一个编译时的递归终止条件:
template<int N>
struct is_prime<N, 1> {
static const bool result = true;
};
这样,只要编译器递归到is_prime<M, 1>
,就说明之前从未遇到过M
的因子,所以result
的值就是true
,整个递归的过程也就结束了。
这样,我们就利用编译器对泛型类型的推导,定义了一个静态版的质数判断方法。这个方法的参数是通过模板参数来传递的,例如:is_prime<36>::result
,接下来,就是第二部分的实现了。
让编译器计算小于N的最大质数
我们直接来看代码:
template<int N>
struct max_prime {
static const int result = is_prime<N-1>::result ?
(N-1) : max_prime<N-1>::result;
};
可以看到,其实max_prime
和is_prime
的定义如出一辙,我们从N-1
开始,只要找到质数就返回,否则就递归查找下一个数字。
同样,max_prime
也有无限递归的问题,我们也要约束一个终止条件。这个终止条件,就是遍历到最小的质数:
template<>
struct max_prime<3> {
static const int result = 2;
};
这里,我们用模板全特化技术,定义了N
的最小值是3,因为不存在比2小的最大质数了。至此,我们就能用max_prime<35>::result
这样的形式,依靠编译器对代码的处理,在编译时进行某些形式的计算了,而这,就是所谓模板元编程的思想。
另外,你可能会想,如果我们使用max_prime<2>::result
不又会导致无限编译了么。没错,说的特别对。这里我们介绍除了static_assert
之外,另外一种在编译期处理错误的方法,就是利用模板特化,让某些语法错误的代码无法通过编译。例如:
template<>
struct max_prime<2> {
};
template<>
struct max_prime<1> {
};
有了这些定义,max_prime<2>::result
和max_prime<1>::result
就无法通过编译了。
最后,列出完成的代码,大家可以自己试试。元编程是C++把泛型编程用到极致的产物,很有意思:
#include <iostream>
using std::cout;
using std::endl;
template<int M, int N = M - 1>
struct is_prime {
static_assert(M > 1, "M must be greater than 1.");
static const bool result = (M % N) && is_prime<M, N-1>::result;
};
template<int N>
struct is_prime<N, 1> {
static const bool result = true;
};
template<int N>
struct max_prime {
static const int result = is_prime<N-1>::result ?
(N-1) : max_prime<N-1>::result;
};
template<>
struct max_prime<3> {
static const int result = 3;
};
template<>
struct max_prime<2> {
};
template<>
struct max_prime<1> {
};
int main() {
cout<<"Largest prime smaller than 24 is: "
<<max_prime<24>::result<<endl;
return 0;
}
如果你在面试的时候,可以皮这一下,应该会很开心 :]