熟悉C++的程序員都知道,C++是一門多范式編程語言,支持面向過程、面向?qū)ο蟆⒎盒途幊桃约昂瘮?shù)式編程范式。然而提到C++模板元編程,在很多人心里這卻是C++里的黑魔法:它很難學(xué)習(xí),一旦進(jìn)入這個(gè)領(lǐng)域曾經(jīng)那些熟悉的東西(if,for...)都不再靈驗(yàn);它很強(qiáng)大,但現(xiàn)實(shí)中卻鮮見有人用它來解決實(shí)際問題,除過偶爾在一些編碼練習(xí)中被某些C++狂熱粉當(dāng)做奇淫巧技拿出來秀秀肌肉。
其實(shí)模板元編程是C++所支持的一種非常強(qiáng)大的計(jì)算能力,它是使用C++開發(fā)高質(zhì)量庫和框架所離不開的一項(xiàng)武器。
掌握C++模板元編程,至少可以在以下場合幫助到你:
- 實(shí)現(xiàn)高擴(kuò)展性,并且兼具高性能的庫
- 實(shí)現(xiàn)靈活且易于使用的框架
- 實(shí)現(xiàn)基于C++的內(nèi)部DSL(Domain Specific Language)
- 幫助更深入地理解并使用模板和泛型編程,更好地去使用C++ STL庫中的高級特性
如果你是一個(gè)C++的庫或框架的開發(fā)者,了解和掌握一些模板元編程的知識,可以讓你的作品更易于擴(kuò)展、擁有更易用的接口,甚至更高的運(yùn)行時(shí)效率。而即使你只使用C++設(shè)計(jì)和開發(fā)應(yīng)用程序,了解模板元編程也會(huì)幫助你更好的去使用STL庫的各種特性,幫助你的局部設(shè)計(jì)做的更加漂亮。
實(shí)際上C++模板元編程技術(shù)已經(jīng)滲透在我們?nèi)粘J褂玫母鞣N庫和框架中,例如我們最常使用的STL庫以及各種xUnit測試框架和mock框架。可以說,模板元編程是中級C++程序員邁向高級的必經(jīng)之路!
然而現(xiàn)實(shí)是,C++模板元編程的學(xué)習(xí)之旅卻并不平坦。
一方面,由于C++模板元編程的本質(zhì)是函數(shù)式編程,熟練掌握并使用函數(shù)式編程的程序員比較小眾,絕大多數(shù)程序員初次進(jìn)入這個(gè)領(lǐng)域時(shí)面對模式匹配、遞歸和不可變性時(shí)都會(huì)手足無措。另一方面,由于C++的模板元編程能力是被意外發(fā)現(xiàn)的,不像別的函數(shù)式編程語言經(jīng)過良好的設(shè)計(jì),所以C++的這種函數(shù)式計(jì)算能力天生存在著各種缺陷,直到C++11標(biāo)準(zhǔn)才開始逐漸完善起來。在之前很多重要特性都靠很繞的方式去迂回實(shí)現(xiàn),增加了學(xué)習(xí)的難度。
另外,市面上介紹模板元編程的書和資料也乏善可陳,以下的書相對還不錯(cuò),但對于模板元編程的介紹仍舊存在一些問題:
《C++ Templates Complete》
介紹C++模板知識最全面的一本書,涉及到了模板各個(gè)方面的基礎(chǔ)知識和應(yīng)用技巧。由于元編程并不是此書重點(diǎn),所以僅有短短一章列舉了一些利用模板元編程做數(shù)值計(jì)算的例子。現(xiàn)實(shí)中使用模板元編程單純做數(shù)值計(jì)算的場合并不多,模板元編程更大的價(jià)值在于做類型計(jì)算和代碼生成,書中卻涉及甚少。《Modern C++ Design》
介紹如何使用模板進(jìn)行C++高階設(shè)計(jì)的一本書。介紹了TypeLists
的概念,作為一種重要的編譯時(shí)數(shù)據(jù)結(jié)構(gòu),是元編程的基礎(chǔ)。但遺憾作者并沒有明確的提出元編程的概念,也沒有對C++編譯時(shí)的運(yùn)算特征進(jìn)行總結(jié)和提煉。最后由于此書基于的C++ 98標(biāo)準(zhǔn)對于模板以及編譯期類型計(jì)算支持上的欠缺,書中介紹的不少實(shí)現(xiàn)比較迂回復(fù)雜。《C++模板元編程》
正式介紹C++模板元編程的一本書,引入了元函數(shù)的概念。通過對模板計(jì)算的規(guī)范化,發(fā)揮了編譯期元函數(shù)可組合的優(yōu)勢。遺憾的是,此書只能算是boost中mpl庫的用戶手冊,基本上是在講mpl庫的用戶接口和使用方法,沒有涉及庫的實(shí)現(xiàn)細(xì)節(jié)。現(xiàn)實(shí)中我們采用元編程解決問題,一般不會(huì)引用boost這么重的庫,往往只會(huì)在某一局部借鑒類似的設(shè)計(jì)技巧。所以對于元編程來說,授之以漁的意義遠(yuǎn)大于授之以魚。另外由于boost mpl庫中用了大量C++預(yù)處理期代碼生成技術(shù),導(dǎo)致通過閱讀mpl源碼去掌握模板元編程的同學(xué)一上來就陷入到一堆宏中,對于學(xué)習(xí)元編程增加了非常多的干擾因素。
基于以上原因,我基于C++11標(biāo)準(zhǔn)實(shí)現(xiàn)了一個(gè)模板元編程的基礎(chǔ)庫:TLP (https://github.com/MagicBowen/tlp),然后再通過本手冊來為大家全面介紹C++模板元編程的基本知識和使用技巧。
TLP庫包含以下內(nèi)容:
- 基本的編譯期數(shù)據(jù)類型和算法;
- 基本的編譯期數(shù)據(jù)結(jié)構(gòu)
TypeList
,以及針對它的各種基本算法函數(shù):length、append、erase、replace、unique、belong、comb... - 基于
TypeList
的各種高階函數(shù),如 any,all,sort、transform、map,filter,fold... - 輔助模板元編程的常用函數(shù),如 __is_eq(),__if(),__print() ...
- 一些有用的編譯期Traits工具,如 IsBaseOf,IsConvertible,IsBuiltIn...
- 一些常用的元編程模式,如 “元函數(shù)轉(zhuǎn)發(fā)”等;
- 一個(gè)面向模板元編程的測試框架,用它描述的所有測試用例執(zhí)行在C++的編譯期,我們使用它來對TLP進(jìn)行測試;
除此之外,TLP庫中還包含了如下示例代碼,用來演示如何在現(xiàn)實(shí)場景中應(yīng)用好模板元編程和TLP庫:
- 示范了如何使用模板元編程做純編譯期計(jì)算,完成一個(gè)自動(dòng)數(shù)三角形的程序;
- 示范了如何使用模板元編程技術(shù)來實(shí)現(xiàn)代碼生成,自動(dòng)創(chuàng)建visitor設(shè)計(jì)模式;
- 示范了如何使用模板元編程技術(shù)來實(shí)現(xiàn)一個(gè)DSL,用于描述并生成有限狀態(tài)機(jī);
上面提到的TypeList
以及使用代碼生成來創(chuàng)建visitor設(shè)計(jì)模式
的原創(chuàng)者是《Modern C++ Design》的作者Andrei Alexandrescu。在TLP中我用C++11對TypeList
及其算法進(jìn)行了改寫,并進(jìn)行了高階函數(shù)的擴(kuò)展。得益于C++11標(biāo)準(zhǔn)對模板元編程的更好支持,新的實(shí)現(xiàn)比起原來的更加清晰和簡潔。
示例代碼中利用模板元編程創(chuàng)建有限狀態(tài)機(jī)DSL
的設(shè)計(jì)最初來自于《C++模板元編程》一書,為了讓其更好被理解,我對例子以及代碼進(jìn)行了較大的改編。
除了上面的例子,本手冊中還介紹了我自己開發(fā)的針對C++模塊和子系統(tǒng)FT(Functional Test)級別的測試框架dates,展示了它如何使用模板元編程來進(jìn)行類型萃取、類型選擇以及類型校驗(yàn),最終使得框架變得更易用、更高效以及更安全。這些技巧可以幫助到大家更好地使用模板元編程去解決現(xiàn)實(shí)問題。
最后,TLP庫自身的測試通過一個(gè)原創(chuàng)的C++模板元編程測試框架。該框架專門針對C++編譯期計(jì)算進(jìn)行測試,它的用法和常見的xUnit測試框架類似,但有趣的是使用該框架描述的所有測試用例的執(zhí)行發(fā)生在C++編譯期。本文會(huì)專門介紹該框架的一些實(shí)現(xiàn)細(xì)節(jié)。
C++模板元編程當(dāng)年被提出來的時(shí)候,函數(shù)式編程還沒有像今天這樣被更多的人所了解和接受,當(dāng)時(shí)的C++標(biāo)準(zhǔn)和編譯器對模板和編譯期計(jì)算的支持也存在著很多缺陷。然而到了今天,很多事情發(fā)生了變化!本文的讀者最好能夠有一些函數(shù)式編程的基礎(chǔ),了解C++模板的基本用法,熟悉一些C++11標(biāo)準(zhǔn)的內(nèi)容。當(dāng)然文中所有用的到模板技術(shù)、C++11標(biāo)準(zhǔn)和涉及到的函數(shù)式編程概念也都會(huì)專門介紹和說明。
如果你從來沒有接觸過C++模板元編程,那么最好從一開始就把它當(dāng)做一門全新的語言去學(xué)習(xí),從頭掌握C++中這種不一樣的計(jì)算模型和語法。這種新的語言和我們熟識的運(yùn)行期C++在語法和計(jì)算模型上都有較大的差異,但它的優(yōu)勢在于能和運(yùn)行期C++緊密無縫地結(jié)合在一起,無論是在提高程序可擴(kuò)展性還是提高程序運(yùn)行效率上,都能創(chuàng)造出非常不可思議的效果來。希望通過本手冊,可以讓更多的人掌握C++模板元編程這一設(shè)計(jì)利器,在適合的場合下以更有效、更酷的方式去解決問題。
文中出現(xiàn)的所有代碼片段,如果在注釋中給出了TLP庫中對應(yīng)的文件路徑,則都能在TLP庫中找到源碼。除此之外的其它代碼則是為了文章需要所構(gòu)造的臨時(shí)代碼。另外,為了減少干擾,本文中所示TLP庫中的代碼均沒有加命名空間,閱讀文章和TLP庫源碼時(shí)請注意區(qū)分。