前面我們介紹了C++模板元編程的基礎知識。我們將模板元編程的計算對象統一到類型上,引入了元函數的概念。元函數是模板元編程的基礎構件,它支持默認參數,支持高階函數,支持柯里化,遵守不可變性,具有惰性特征。此外我們還介紹了在模板元編程中做計算控制的模式匹配和遞歸的相關技巧。
在示例代碼中,我們完成了幾個模板元編程的基礎元函數:IntType,BoolType,Value,Print,IsEqual,IfThenElse等等,并且對它們用宏進行了封裝,分別是__int()
,__bool()
,__value()
,__print()
,__is_eq()
,__if()
。后文在使用的時候,對于某一用宏封裝過的元函數,會提到“元函數Value”,可能也會提成“元函數__value()
”,請注意它們是相同的。
在前面的介紹中,我們一直將C++模板元編程看做是一門獨立的圖靈完備的純函數式語言。雖然C++模板元編程和我們熟識的運行期C++無論在語法還是計算模型上都有較大的差異,但他們卻能最緊密無縫地集成在一起。有了模板元編程,我們就可以把C++看成是一門兩階段語言。
第一階段發生在C++編譯期前段,這時可以看做是C++模板元編程的天下。此時,C++相當于一門純函數式的解釋型語言,編譯器在此時充當了解釋器的角色,直接面向C++源代碼進行解釋執行。我們知道對于代碼最全的元信息就存在于源代碼自身中,所以解釋型語言所謂反射或者自省的能力都非常的強,這也是C++模板元編程擁有強大能力的原因之一。框架和庫的開發往往離不開語言自身擁有的反射能力。C++模板元編程帶來的這種反射能力,和運行期C++的RTTI技術本質并不相同,它更加的強大且不會帶來運行時開銷。STL中的type_traits庫,利用模板元編程技術定義了非常多的編譯期反射工具,可以直接供大家使用。
第一階段C++模板元編程的另一特殊性在于它的計算對象:類型和常量,它們是構成運行期C++的基本元素。因此模板元編程可以看做是運行期C++的代碼生成器。當第一階段結束后,C++編譯器恢復我們熟識的角色,針對第一階段的結果代碼進行編譯,產生可以運行的C++程序。正是模板元編程這種可以充當運行期C++代碼生成器的能力,使得它成為構造內部DSL的強大工具。
C++在編譯期之前還有一個預處理階段。預處理期可以利用宏完成各種代碼生成,Boost中還專門有一個關于預處理的工具庫preprocessor,用于在預處理期進行數值運算及代碼生成,甚至還定義了預處理期的數據結構和算法。雖然預處理技術也是一項非常有用的工具,但由于其原理僅是文本替換,并不做真正的運算,所以理論上并非是圖靈完備的,因此我們在上圖中并未將其列入。
由于C++的模板元編程能力是在C++語言引入模板特性后被意外發現的,所以不像別的經過預先良好設計的函數式語言那樣語法優美,功能完備。通過前面的例子確實也看到它的很多寫法相比Haskell要繁瑣的多,而且功能上也要差很多。在C++11出現之前,標準上對模板元編程的支持還存在一些大的缺陷,而且不同編譯器對模板元編程的支持也不太統一。另外我們也看到,模板元編程操作IO的能力非常的差,這導致了模板元編程的問題定位變得困難。
得益于近些年C++標準以及主流編譯器對C++編譯期計算支持得越來越完備,使得模板元編程相比以前要更加方便和完善。雖然如此,由于歷史原因,它仍舊無法和一門真正經過良好設計的語言相比。但由于它內置于C++,使得它和運行期C++的結合上有著天然無法替代的優勢,這也是我們要學習它的原因。不要相信類似 “python + 傳統C++” 的說法,否則你就基本喪失了構造靈活高效的C++程序庫和框架的能力。想要成為一名專業的C++程序員,熟悉模板元編程是必須的。