模板可以被递归调用,在模板递归的过程中,可以执行前面我们提到的两种编译期计算:数值计算和类型计算。
下面我们用模板递归来做数值计算,在编译期计算N的阶乘。
template<int N>
struct Factorial
{
enum { Value = N * Factorial<N - 1>::Value };
};
template<>
struct Factorial<1>
{
enum { Value = 1 };
};
可以看到,我们在主模板template<int N> struct Factorial
的定义中,使用了模板自身Factorial<N - 1>::Value
。编译器会一直用不同的N - 1
的值来具现化主模板,一直到N变为1,这时选择Factorial的特化版本template<> struct Factorial<1>
,内部的Value变为1,递归终止了。
我们可以在编译期运行这个函数__print(Factorial<5>::Value)
,可以看到编译器会打印出120。
对于上面的例子,我们看到是通过模板特化来终止递归的。事实上我们对比一下Haskell语言中计算阶乘的函数实现:
factorial :: Int -> Int
factorial n = n * factorial (n - 1)
factorial 1 = 1
Haskell是一门纯函数式语言,它通过模式匹配进行条件选择,通过递归来进行循环控制。对于上面的factorial定义,当入参是1的时候模式匹配会选择到factorial 1 = 1
实现,否则匹配到factorial n = n * factorial (n - 1)
的递归实现。
可以看到上面我们使用C++模板的方式和Haskell中定义函数是如此的类似。编译器对模板的特化版本选择就相当是Haskell在做模式匹配,而两者的循环控制都是通过递归来完成。已经证明模板的这种编译时计算能力就是一种纯函数式编程范式,是图灵完备的!
不同的是,C++这种编译期计算支持的计算对象只能是整形常量和类型。
C++这种编译期函数式计算就是本文要介绍的C++模板元编程,类模板在里面充当了函数的角色。本文用到的关于类模板所有知识基本就是前文所述这些。后面我们将会对类模板进行规范化,让其能够和函数式计算更加地一致,最大程度发挥编译期函数式计算的威力。
关于模板的基本知识就介绍这里,我们接下来正式介绍所谓的“模板元编程”!