先來看一個例子:
#include <stdio.h>
template <int depth>
class Fibnacci
{
public:
static const int value = Fibnacci<depth-1>::value + Fibnacci<depth-2>::value;
};
template <>
class Fibnacci<0>
{
public:
static const int value = 0;
};
template <>
class Fibnacci<1>
{
public:
static const int value = 1;
};
template <int depth>
void printFibnacci()
{
printFibnacci<depth-1>();
wprintf(L"%d\n", Fibnacci<depth>::value);
}
template <>
void printFibnacci<0>()
{
wprintf(L"%d\n", Fibnacci<0>::value);
}
int main()
{
printFibnacci<8>();
return 0;
}
Fibnacci數列,相信是個程序員都能寫出來,重點是,這個Fibnacci數列的計算完全是在編譯時完成!後面的print也是如此,當你把參數調得很大時,運行時間不會有任何改變,但是你會花費長時間在編譯階段。
如果你聽說過一些模板元編程,你一定會知道"C++模板是圖靈完備的"這個說法。模板元是如何圖靈完備的?答案是,模板元跟Functional原理是一樣的。
模板的本質是定義與替換,lambda函數的本質也是定義與替換,這裡的替換實際上符合的是數學中的lambda演算理論:
Lambda演算
λ演算(lambda calculus)是一套用於研究函數定義、函數應用和遞歸的形式系統。它由丘奇(Alonzo Church)和他的學生克萊尼(Stephen Cole Kleene)在20世紀30年代引入。Church 運用λ演算在1936年給出判定性問題(Entscheidungsproblem)的一個否定的答案。這種演算可以用來清晰地定義什麼是一個可計算函數。關於兩個 lambda 演算表達式是否等價的命題無法通過一個“通用的算法”來解決,這是不可判定性能夠證明的頭一個問題,甚至還在停機問題之先。Lambda 演算對函數式編程語言有巨大的影響,比如 Lisp 語言、ML 語言和 Haskell 語言。
Lambda 演算可以被稱為最小的通用程序設計語言。它包括一條變換規則(變量替換)和一條函數定義方式,Lambda 演算之通用在於,任何一個可計算函數都能用這種形式來表達和求值。因而,它是等價於圖靈機的。盡管如此,Lambda 演算強調的是變換規則的運用,而非實現它們的具體機器。可以認為這是一種更接近軟件而非硬件的方式。v
所以C++的模板元編程實際上屬於函數式風格編程,這也是很多C++程序員覺得它不舒服的原因。
另外說一點,所謂圖靈完備的語言,則必定可以用它來表達任何算法,那麼我們現在使用的這個直接Fibnacci是一個非常低效的算法。
有一個常見的說法"Fibnacci的迭代算法比遞歸算法快",這裡我想強調,遞歸只是形式,與算法無關,下面奉上O(n)的Fibnacci算法(這回就不那麼折磨編譯器了):
#include <stdio.h>
template <int depth>
class Fibnacci
{
public:
static const int value = Fibnacci<depth-1>::value + Fibnacci<depth-1>::last;
static const int last = Fibnacci<depth-1>::value ;
};
template <>
class Fibnacci<0>
{
public:
static const int value = 0;
};
template <>
class Fibnacci<1>
{
public:
static const int value = 1;
static const int last = 0;
};
template <int depth>
void printFibnacci()
{
printFibnacci<depth-1>();
wprintf(L"%d\n", Fibnacci<depth>::value);
}
template <>
void printFibnacci<0>()
{
wprintf(L"%d\n", Fibnacci<0>::value);
}
int main()
{
printFibnacci<8>();
return 0;
}
最後留一道題目,各位看官如果有興趣,可以做做,回復在下面或者留下鏈接均可,用C++模板或C#lambda函數都可以:
1994年的一次會議上,Erwin Unruh寫了一個程序,在編譯錯誤裡面打印出一個素數序列。這個程序當時震驚了當時在場的包括C++之父Bjarne Stroustrup在內的幾位大師,這個事情正標志了C++模板系統的圖靈完備性被發現。那麼,讓我們也來向先輩致敬,來實現這個打印出一個素數序列的模板吧!