还是我们在之前的的一篇文章中说到过的问题:如何判断比一个给定数字小的最大质数呢?如果我们可以限定一个条件,就可以把这个计算过程从运行时搬编译时,也就是让编译器替我们计算好。

这个条件是什么呢?就是我们可以提前确定好要判断的数字,也就是说这个数字必须是硬编码在源代码里的,而不能是在运行时由用户输入的。其实,在现实的项目中,有很多要处理的数据都是静态的,因此这也并不是什么太过分的假设。

接下来,我们就来看下做法。这个算法还是分两部分:

  • 判断一个数字是否是质数;
  • 得到比给定数字小的最大质数;

让编译器判断是否是质数

先来看第一部分,我们直接上代码:

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的因子Nresult的结果就是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_primeis_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>::resultmax_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;
}

如果你在面试的时候,可以皮这一下,应该会很开心 :]

所有订阅均支持 12 期免息分期

¥ 59

按月订阅

一个月,观看并下载所有视频内容。初来泊学,这可能是个最好的开始。

开始订阅

¥ 512

按年订阅

一年的时间,让我们一起疯狂地狩猎知识吧。比按月订阅优惠 28%

开始订阅

¥ 1280

泊学终身会员

永久观看和下载所有泊学网站视频,并赠送 100 元商店优惠券。

我要加入
如需帮助,欢迎通过以下方式联系我们