模板可以被遞歸調用,在模板遞歸的過程中,可以執行前面我們提到的兩種編譯期計算:數值計算和類型計算。
下面我們用模板遞歸來做數值計算,在編譯期計算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++模板元編程,類模板在里面充當了函數的角色。本文用到的關于類模板所有知識基本就是前文所述這些。后面我們將會對類模板進行規范化,讓其能夠和函數式計算更加地一致,最大程度發揮編譯期函數式計算的威力。
關于模板的基本知識就介紹這里,我們接下來正式介紹所謂的“模板元編程”!