故事要從一個看起來很是簡單的功能開始:git
請計算兩個數的和。
若是你對Python很熟悉,你必定會以爲:「哇!這太簡單了!」,而後寫出如下代碼:github
def Plus(lhs, rhs): return lhs + rhs
那麼,C語言又如何呢?你須要面對這樣的問題:算法
/* 這裏寫什麼?*/ Plus(/* 這裏寫什麼?*/ lhs, /* 這裏寫什麼?*/ rhs) { return lhs + rhs; }
也許你很快就能想到如下解法中的一些或所有:express
int Plus(int lhs, int rhs) { return lhs + rhs; }
顯然,這不是一個好的方案。由於這樣的Plus函數接口強行的要求兩個實參以及返回值的類型都必須是int,或是可以發生隱式類型轉換到int的類型。此時,若是實參並非int類型,其結果每每就是錯誤的。請看如下示例:編程
int main() { printf("%d\n", Plus(1, 2)); // 3,正確 printf("%d\n", Plus(1.999, 2.999)); // 仍然是3! }
int Plusi(int lhs, int rhs) { return lhs + rhs; } long Plusl(long lhs, long rhs) { return lhs + rhs; } double Plusd(double lhs, double rhs) { return lhs + rhs; } // ...
這種方案的缺點也很明顯:其使得代碼寫起來像「彙編語言」(movl,movq,...)。咱們須要針對不一樣的類型調用不一樣名稱的函數(是的,C語言也不支持函數重載),這太可怕了。數組
#define Plus(lhs, rhs) (lhs + rhs)
這種方案彷佛很不錯,甚至「代碼看上去和Python同樣」。但正如許許多多的書籍都討論過的那樣,宏,不只「拋棄」了類型,甚至「拋棄」了代碼。是的,宏不是C語言代碼,其只是交付於預處理器執行的「複製粘貼」的標記。一旦預處理完成,宏已然再也不存在。可想而知,在功能變得複雜後,宏的缺點將會愈來愈大:代碼晦澀,沒法調試,「莫名其妙」的報錯...數據結構
看到這裏,也許你會以爲:「哇!C語言真爛!竟然連這麼簡單的功能都沒法實現!」。但請想想,爲何會出現這些問題呢?讓咱們回到故事的起點:dom
請計算兩個數的和。
仔細分析這句話:「請計算...的和」,意味着「加法」語義,這在C語言中能夠經過「+」實現(也許你會聯想到彙編語言中的加法實現);而「兩個」,則意味着形參的數量是2(也許你會聯想到彙編語言中的ESS、ESP、EBP等寄存器);那麼,「數」,意味着什麼語義?C語言中,具備「數」這一語義的類型有十幾種:int、double、unsigned,等等,甚至char也具備「數」的語義。那麼,「加法」和「+」,「兩個」和「形參的數量是2」,以及「數」和int、double、unsigned等等之間的關係是什麼?編程語言
是抽象。ide
高級語言的目的,就是對比其更加低級的語言進行抽象,從而使得咱們可以實現更加高級的功能。抽象,是一種人類的高級思惟活動,是一種充滿着智慧的思惟活動。彙編語言抽象了機器語言,而C語言則進一步抽象了彙編語言:其將彙編語言中的各類加法指令,抽象成了一個簡單的加號;將各類寄存器操做,抽象成了形參和實參...抽象思惟是如此的廣泛與天然,以致於咱們每每甚至忽略了這種思惟的存在。
可是,C語言並無針對類型進行抽象的能力,C語言不知道,也沒有能力表達「int和double都是數字」這一語義。而這,直接致使了這個「看起來很是簡單的功能」難以完美的實現。
針對類型的抽象是如此重要,以致於編程語言世界出現了與C語言這樣的「靜態類型語言」徹底不同的「動態類型語言」。正如開頭所示,在Python這樣的動態類型語言中,咱們根本就不須要爲每一個變量提供類型,從而彷佛「從根本上解決了問題」。可是,「出來混,早晚要還的」,這種看似完美的動態類型語言,犧牲的倒是極大的運行時效率!咱們不由陷入了沉思:真的沒有既不損失效率,又能對類型進行抽象的方案了嗎?
正當咱們束手無策,甚至感到些許絕望之時,C++的模板,爲咱們照亮了前行的道路。
模板,即C++中用以實現泛型編程思想的語法組分。模板是什麼?一言以蔽之:類型也能夠是「變量」的東西。這樣的「東西」,在C++中有二:函數模板和類模板。
經過在普通的函數定義和類定義中前置template <...>,便可定義一個模板,讓咱們以上文中的Plus函數進行說明。請看如下示例:
此爲函數模板:
template <typename T> T Plus(T lhs, T rhs) { return lhs + rhs; } int main() { cout << Plus(1, 2) << endl; // 3,正確! cout << Plus(1.999, 2.999) << endl; // 4.998,一樣正確! }
此爲類模板:
template <typename T> struct Plus { T operator()(T lhs, T rhs) { return lhs + rhs; } }; int main() { cout << Plus<int>()(1, 2) << endl; // 3,正確! cout << Plus<double>()(1.999, 2.999) << endl; // 4.998,一樣正確! }
顯然,模板的出現,使得咱們垂手可得的就實現了類型抽象,而且沒有(像動態類型語言那樣)引入任何由於此種抽象帶來的額外代價。
請看如下示例:
template <typename T> struct Plus { T operator()(T lhs, T rhs) { return lhs + rhs; } }; int main() { cout << Plus<int>()(1, 2) << endl; cout << Plus<double>()(1.999, 2.999) << endl; }
上例中,typename T中的T,稱爲模板形參;而Plus<int>中的int,則稱爲模板實參。在這裏,模板實參是一個類型。
事實上,模板的形參與實參既能夠是類型,也能夠是值,甚至能夠是「模板的模板」;而且,模板形參也能夠具備默認值(就和函數形參同樣)。請看如下示例:
template <typename T, int N, template <typename U, typename = allocator<U>> class Container = vector> class MyArray { Container<T> __data[N]; }; int main() { MyArray<int, 3> _; }
上例中,咱們聲明瞭三個模板參數:
什麼叫「模板的模板參數」?這裏須要明確的是:模板、類型和值,是三個徹底不同的語法組分。模板可以「創造」類型,而類型可以「創造」值。請參考如下示例以進行辨析:
vector<int> v;
此例中,vector是一個模板,vector<int>是一個類型,而v是一個值。
因此,一個「模板的模板參數」,就是一個須要提供給其一個模板做爲實參的參數。對於上文中的聲明,Container是一個「模板的模板參數」,其須要接受一個模板做爲實參 。須要怎樣的模板呢?這個模板應具備兩個模板形參,且第二形參具備默認值allocator<U>;同時,Container具備默認值vector,這正是一個符合要求的模板。這樣,Container在類定義中,即可被看成一個模板使用(就像vector那樣)。
模板,表明了一種泛化的語義。顯然,既然有泛化語義,就應當有特化語義。特化,使得咱們能爲某些特定的類型專門提供一份特殊實現,以達到某些目的。
特化分爲全特化與偏特化。所謂全特化,即一個「披着空空如也的template <>的普通函數或類」,咱們仍是以上文中的Plus函數爲例:
// 無論T是什麼類型,都將使用此定義... template <typename T> T Plus(T lhs, T rhs) { return lhs + rhs; } // ...可是,當T爲int時,將使用此定義 template <> // 空空如也的template <> int Plus(int lhs, int rhs) { return lhs + rhs; } int main() { Plus(1., 2.); // 使用泛型版本 Plus(1, 2); // 使用特化版本 }
那麼,偏特化又是什麼呢?除了全特化之外的特化,都稱爲偏特化。這句話雖然簡短,但意味深長,讓咱們來仔細分析一下:首先,「除了全特化之外的...」,表明了template關鍵詞以後的「<>」不能爲空,不然就是全特化,這顯而易見;其次,「...的特化」,表明了偏特化也必須是一個特化。什麼叫「是一個特化」呢?只要特化版本比泛型版本更特殊,那麼此版本就是一個特化版本。請看如下示例:
// 泛化版本 template <typename T, typename U> struct _ {}; // 這個版本的特殊之處在於:僅當兩個類型同樣的時候,纔會且必定會使用此版本 template <typename T> struct _<T, T> {}; // 這個版本的特殊之處在於:僅當兩個類型都是指針的時候,纔會且必定會使用此版本 template <typename T, typename U> struct _<T *, U *> {}; // 這個版本「換湯不換藥」,沒有任何特別之處,因此不是一個特化,而是錯誤的重複定義 template <typename A, typename B> struct _<A, B> {};
因而可知,「更特殊」是一個十分寬泛的語義,這賦予了模板極大的表意能力,咱們將在下面的章節中不斷的見到特化所帶來的各類技巧。
函數模板不是函數,而是一個能夠生成函數的語法組分;同理,類模板也不是類,而是一個能夠生成類的語法組分。咱們稱經過函數模板生成函數,或經過類模板生成類的過程爲模板實例化。
模板實例化具備一個很是重要的特徵:惰性。這種惰性主要體如今類模板上。請看如下示例:
template <typename T> struct Test { void Plus(const T &val) { val + val; } void Minus(const T &val) { val - val; } }; int main() { Test<string>().Plus("abc"); Test<int>().Minus(0); }
上例中,Minus函數顯然是不適用於string類型的。也就是說,Test類對於string類型而言,並非「100%完美的」。當遇到這種狀況時,C++的作法十分寬鬆:不完美?沒關係,只要不調用那些「不完美的函數」就好了。在編譯器層面,編譯器只會實例化真的被使用的函數,並對其進行語法檢查,而根本不會在乎那些根本沒有被用到的函數。也就是說,在上例中,編譯器實際上只實例化出了兩個函數:string版本的Plus,以及int版本的Minus。
在這裏,「懶惰即美德」佔了上風。
在C++中,「::」表達「取得」語義。顯然,「::」既能夠取得一個值,也能夠取得一個類型。這在非模板場景下是沒有任何問題的,並不會引發接下來即將將要討論的「取得的是一個類型仍是一個值」的語義混淆,由於編譯器知道「::」左邊的語法組分的定義。但在模板中,若是「::」左邊的語法組分並非一個確切類型,而是一個模板參數的話,語義將再也不是肯定的。請看如下示例:
struct A { typedef int TypeOrValue; }; struct B { static constexpr int TypeOrValue = 0; }; template <typename T> struct C { T::TypeOrValue; // 這是什麼? };
上例中,若是T是A,則T::TypeOrValue是一個類型;而若是T是B,則T::TypeOrValue是一個數。咱們稱這種含有模板參數的,沒法當即肯定語義的名稱爲「依賴型名稱」。所謂「依賴」,意即此名稱的確切語義依賴於模板參數的實際類型。
對於依賴型名稱,C++規定:默認狀況下,編譯器應認爲依賴型名稱不是一個類型;若是須要編譯器將依賴型名稱視爲一個類型,則須要前置typename關鍵詞。請看如下示例以進行辨析:
T::TypeOrValue * N; // T::TypeOrValue是一個值,這是一個乘法表達式 typename T::TypeOrValue * N; // typename T::TypeOrValue是一個類型,聲明瞭一個這樣類型的指針
可變參數模板是C++11引入的一個極爲重要的語法。這裏對其進行簡要介紹。
可變參數模板表達了「參數數量,以及每一個參數的類型都未知且各不相同」這一語義。若是咱們但願實現一個簡單的print函數,其可以傳入任意數量,且類型互不相同的參數,並依次打印這些參數值,此時就須要使用可變參數模板。
可變參數模板的語法由如下組分構成:
接下來,咱們就基於可變參數模板,實現這一print函數。請看如下示例:
// 遞歸終點 void print() {} // 分解出一個val + 剩下的全部val // 至關於:void print(const T &val, const Types1 &Args1, const Types2 &Args2, const Types3 &Args3, ...) template <typename T, typename... Types> void print(const T &val, const Types &... Args) { // 每次打印一個val cout << val << endl; // 至關於:print(Args1, Args2, Args3, ...); // 遞歸地繼續分解... print(Args...); } int main() { print(1, 2., '3', "4"); }
上例中,咱們實現了一對重載的print函數。第一個print函數是一個空函數,其將在「Args...」是空的時候被調用,以做爲遞歸終點;而第二個print函數接受一個val以及餘下的全部val做爲參數,其將打印val,並使用餘下的全部val繼續遞歸調用本身。不難發現,第二版本的print函數具備不斷打印並分解Args的能力,直到Args被徹底分解。
「sizeof?這有什麼可討論的?」也許你會想。只要你學過C語言,那麼對此必不陌生。那麼爲何咱們還須要爲sizeof這一「平淡無奇」的語法單獨安排一節來討論呢?這是由於sizeof有兩個對於泛型編程而言極爲重要的特性:
上述第一點很好理解,由於sizeof所考察的是類型,而類型(固然也包含其所佔用的內存大小),必定是一個編譯期就知道的量(由於C++做爲一門靜態類型語言,任何的類型都毫不會延遲到運行時才知道,這是動態類型語言才具備的特性),故sizeof的結果是一個編譯期常量也就不足爲奇了。
上述第二點意味深長。利用此特性,咱們能夠實現出一些很是特殊的功能。請看下一節。
讓咱們以一個問題引出這一節的內容:
如何實現:斷定類型A是否可以基於隱式類型轉換轉爲B類型?
乍看之下,這是個十分棘手的問題。此時咱們應當思考的是:如何引導(請注意「引導」一詞的含義)編譯器,在A到B的隱式類型轉換可行時,走第一條路,不然,走第二條路?
請看如下示例:
template <typename A, typename B> class IsCastable { private: // 定義兩個內存大小不同的類型,做爲「布爾值」 typedef char __True; typedef struct { char _[2]; } __False; // 稻草人函數 static A __A(); // 只要A到B的隱式類型轉換可用,重載肯定的結果就是此函數... static __True __Test(B); // ...不然,重載肯定的結果纔是此函數(「...」參數的重載肯定優先級低於其餘一切可行的重載版本) static __False __Test(...); public: // 根據重載肯定的結果,就可以斷定出隱式類型轉換是否可以發生 static constexpr bool Value = sizeof(__Test(__A())) == sizeof(__True); };
上例比較複雜,咱們依次進行討論。
首先,咱們聲明瞭兩個大小不一樣的類型,做爲假想的「布爾值」。也許你會有疑問,這裏爲何不使用int或double之類的類型做爲False?這是因爲C語言並未規定「int、double必須比char大」,故爲了「強行知足標準」(你徹底能夠認爲這是某種「教條主義或形式主義」),這裏採用了「兩個char必定比一個char大一倍」這一簡單道理,定義了False。
而後,咱們聲明瞭一個所謂的「稻草人函數」,這個看似毫無心義的函數甚至沒有函數體(由於並不須要,且接下來的兩個函數也沒有函數體,與此函數同理)。這個函數惟一的目的就是「得到」一個A類型的值「給sizeof看」。因爲sizeof的不求值特性,此函數也就不須要(咱們也沒法提供)函數體了。那麼,爲何不直接使用形如「T()」這樣的寫法,而須要聲明一個「稻草人函數」呢?我想,不用我說你就已經明白緣由了:這是由於並非全部的T都具備默認構造函數,而若是T沒有默認構造函數,那麼「T()」就是錯誤的。
接下來是最關鍵的部分,咱們聲明瞭一對重載函數,這兩個函數的區別有二:
也就是說,若是咱們給這一對重載函數傳入一個A類型的值時,因爲「...」參數的重載肯定優先級低於其餘一切可行的重載版本,只要A到B的隱式類型轉換可以發生,重載肯定的結果就必定是調用第一個版本的函數,返回值爲__True;不然,只有當A到B的隱式類型轉換真的不可行時,編譯器纔會「被迫」選擇那個編譯器「最不喜歡的版本」,從而使得返回值爲__False。返回值的不一樣,就可以直接體如今sizeof的結果不一樣上。因此,只須要斷定sizeof(__Test(__A()))是多少,就可以達到咱們最終的目的了。下面請看使用示例:
int main() { cout << IsCastable<int, double>::Value << endl; // true cout << IsCastable<int, string>::Value << endl; // false }
能夠看出,輸出結果徹底符合咱們的預期。
SFINAE(Substitution Failure Is Not An Error,替換失敗並不是錯誤)是一個高級模板技巧。首先,讓咱們來分析這一拗口的詞語:「替換失敗並不是錯誤」。
什麼是「替換」?這裏的替換,實際上指的正是模板實例化;也就是說,當模板實例化失敗時,編譯器並不認爲這是一個錯誤。這句話看上去彷佛莫名其妙,也許你會有疑問:那怎麼樣才認爲是一個錯誤?咱們又爲何要討論一個「錯誤的東西」呢?讓咱們以一個問題引出這一技巧的意義:
如何斷定一個類型是不是一個類類型?
「哇!這個問題彷佛比上一個問題更難啊!」也許你會這麼想。不過有了上一個問題的鋪墊,這裏咱們依然要思考的是:一個類類型,有什麼獨一無二的東西是非類類型所沒有的?(這樣咱們彷佛就能讓編譯器在「喜歡和不喜歡」之間作出抉擇)
也許你將恍然大悟:類的成員指針。
請看如下示例:
template <typename T> class IsClass { private: // 定義兩個內存大小不同的類型,做爲「布爾值」 typedef char __True; typedef struct { char _[2]; } __False; // 僅當T是一個類類型時,「int T::*」纔是存在的,從而這個泛型函數的實例化纔是可行的 // 不然,就將觸發SFINAE template <typename U> static __True __Test(int U::*); // 僅當觸發SFINAE時,編譯器纔會「被迫」選擇這個版本 template <typename U> static __False __Test(...); public: // 根據重載肯定的結果,就可以斷定出T是否爲類類型 static constexpr bool Value = sizeof(__Test<T>(0)) == sizeof(__True); };
一樣,咱們首先定義了兩個內存大小必定不同的類型,做爲假想的「布爾值」。而後,咱們聲明瞭兩個重載模板,其分別以兩個「布爾值」做爲返回值。這裏的關鍵在於,重載模板的參數,一個是類成員指針,另外一個是「...」。顯然,當編譯器拿到一個T,並準備生成一個「T:😗」時,僅當T是一個類類型時,這一輩子成纔是正確的,合乎語法的;不然,這個函數簽名將根本沒法被生成出來,從而進一步的使得編譯器「被迫」選擇那個「最不喜歡的版本」進行調用(而不是認爲這個「根本沒法被生成出來」的模板是一個錯誤)。因此,經過sizeof對__Test的返回值大小進行斷定,就可以達到咱們最終的目的了。下面請看使用示例:
int main() { cout << IsClass<double>::Value << endl; // false cout << IsClass<string>::Value << endl; // true }
能夠看出,輸出結果徹底符合咱們的預期。
sizeof,做爲一個C語言的「入門級」語法,其「永不求值」的特性每每被咱們所忽略。本章中,咱們充分利用了sizeof的這種「永不求值」的特性,作了不少「表面工程」,僅僅是爲了「給sizeof看」;同理,SFINAE技術彷佛也只是在「找編譯器的麻煩,拿編譯器尋開心」。但正是這些「表面工程、找麻煩、尋開心」,讓咱們得以實現了一些很是難以想象的功能。
Traits,中文翻譯爲「特性」,Type Traits,即爲「類型的特性」。這是個十分奇怪的翻譯,故不少書籍對這個詞選擇不譯,也有書籍將其翻譯爲「類型萃取器」,十分生動形象。
Type Traits的定義較爲模糊,其大體表明瞭這樣的一系列技術:經過一個類型T,取得另外一個基於T進行加工後的類型,或對T基於某一標準進行分類,獲得分類結果。
本章中,咱們以幾個經典的Type Traits應用,來見識一番此技術的精妙。
第一個例子較爲簡單:咱們須要獲得T的指針類型,即:獲得「T *」。此時,只須要將「T *」經過typedef變爲Type Traits類的結果便可。請看如下示例:
template <typename T> struct AddStar { typedef T *Type; }; template <typename T> struct AddStar<T *> { typedef T *Type; }; int main() { cout << typeid(AddStar<int>::Type).name() << endl; // int * cout << typeid(AddStar<int *>::Type).name() << endl; // int * }
這段代碼十分簡單,但彷佛咱們寫了兩遍「如出一轍」的代碼?認真觀察和思考便可發現:特化版本是爲了防止一個已是指針的類型發生「升級」而存在的。若是T已是一個指針類型,則Type就是T自己,不然,Type纔是「T *」。
上一節,咱們實現了一個可以爲T「添加星號」的Traits,這一節,咱們將實現一個功能與之相反的Traits:爲T「去除星號」。
「簡單!」也許你會想,並很快給出瞭如下實現:
template <typename T> struct RemoveStar { typedef T Type; }; template <typename T> struct RemoveStar<T *> { typedef T Type; }; int main() { cout << typeid(RemoveStar<int>::Type).name() << endl; // int cout << typeid(RemoveStar<int *>::Type).name() << endl; // int }
彷佛完成了?不幸的是,這一實現並不完美。請看如下示例:
int main() { cout << typeid(RemoveStar<int **>::Type).name() << endl; // int *,哦不! }
能夠看到,咱們的上述實現只能去除一個星號,當傳入一個多級指針時,並不能獲得咱們想要的結果。
這該如何是好?咱們不由想到:若是可以實現一個「while循環」,就能去除全部的星號了。雖然模板沒有while循環,但咱們知道:遞歸正是循環的等價形式。請看如下示例:
// 遞歸終點,此時T真的不是指針了 template <typename T> struct RemoveStar { typedef T Type; }; // 當T是指針時,Type應該是T自己(已經去除了一個星號)繼續RemoveStar的結果 template <typename T> struct RemoveStar<T *> { typedef typename RemoveStar<T>::Type Type; };
上述實現中,當發現T選擇了特化版本(即T自己是指針時),就會遞歸地對T進行去星號,直到T再也不選擇特化版本,從而抵達遞歸終點爲止。這樣,就能在面對多級指針時,也可以獲得正確的Type。下面請看使用示例:
int main() { cout << typeid(RemoveStar<int **********>::Type).name() << endl; // int }
能夠看出,輸出結果徹底符合咱們的預期。
顯然,使用這樣的Traits是具備潛在的較大代價的。例如上例中,爲了去除一個十級指針的星號,編譯器居然須要實例化出11個類!但好在這一切均發生在編譯期,對運行效率不會產生任何影響。
讓咱們繼續討論前言中的Plus函數,以引出本節所要討論的話題。目前咱們給出的「最好實現」以下:
template <typename T> T Plus(T lhs, T rhs) { return lhs + rhs; } int main() { cout << Plus(1, 2) << endl; // 3,正確! }
可是,只要在上述代碼中添加一個「.」,就當即發生了問題:
int main() { cout << Plus(1, 2.) << endl; // 二義性錯誤!T應該是int仍是double? }
上例中,因爲Plus模板只使用了單一的一個模板參數,故要求兩個實參的類型必須一致,不然,編譯器就不知道T應該是什麼類型,從而引起二義性錯誤。但顯然,任何的兩種「數」之間都應該是能夠作加法的,因此不難想到,咱們應該使用兩個而不是一個模板參數,分別做爲lhs與rhs的類型,可是,咱們當即就遇到了新的問題。請看如下示例:
template <typename T1, typename T2> /* 這裏應該寫什麼?*/ Plus(T1 lhs, T2 rhs) { return lhs + rhs; }
應該寫T1?仍是T2?顯然都不對。咱們應該尋求一種方法,其可以獲取到T1與T2之間的「更強大類型」,並將此「更強大類型」做爲返回值。進一步的,咱們能夠以此爲基礎,實現出一個可以獲取到任意數量的類型之中的「最強大類型」的方法。
應該怎麼作呢?事實上,這個問題的解決方案,確實是難以想到的。請看如下示例:
template <typename A, typename B> class StrongerType { private: // 稻草人函數 static A __A(); static B __B(); public: // 3目運算符表達式的類型就是「更強大類型」 typedef decltype(true ? __A() : __B()) Type; }; int main() { cout << typeid(StrongerType<int, char>::Type).name() << endl; // int cout << typeid(StrongerType<int, double>::Type).name() << endl; // double }
上例中,咱們首先定義了兩個「稻草人函數」,用以分別「獲取」類型爲A或B的值「給decltype看」。而後,咱們使用了decltype探測三目運算符表達式的類型,不難發現,decltype也具備sizeof的「不對錶達式進行求值」的特性。因爲三目運算符表達式從理論上可能返回兩個值中的任意一個,故表達式的類型就是咱們所尋求的「更強大類型」。隨後的用例也證明了這一點。
有了獲取兩個類型之間的「更強大類型」的Traits之後,咱們不難想到:N個類型之中的「最強大類型」,就是N - 1個類型之中的「最強大類型」與第N個類型之間的「更強大類型」。請看如下示例:
// 原型 // 經過typename StrongerType<Types...>::Type獲取Types...中的「最強大類型」 template <typename... Types> class StrongerType; // 只有一個類型 template <typename T> class StrongerType<T> { // 我本身就是「最強大的」 typedef T Type; }; // 只有兩個類型 template <typename A, typename B> class StrongerType<A, B> { private: // 稻草人函數 static A __A(); static B __B(); public: // 3目運算符表達式的類型就是「更強大類型」 typedef decltype(true ? __A() : __B()) Type; }; // 不止兩個類型 template <typename T, typename... Types> class StrongerType<T, Types...> { public: // T和typename StrongerType<Types...>::Type之間的「更強大類型」就是「最強大類型」 typedef typename StrongerType<T, typename StrongerType<Types...>::Type>::Type Type; }; int main() { cout << typeid(StrongerType<char, int>::Type).name() << endl; // int cout << typeid(StrongerType<int, double>::Type).name() << endl; // double cout << typeid(StrongerType<char, int, double>::Type).name() << endl; // double }
經過遞歸,咱們使得全部的類型共同參與了「打擂臺」,這裏的「擂臺」,就是咱們已經實現了的StrongerType的雙類型版本,而「打擂臺的最後大贏家」,則正是咱們所尋求的「最強大類型」。
有了StrongerType這一Traits後,咱們就能夠實現上文中的雙類型版本的Plus函數了。請看如下示例:
// Plus函數的返回值應該是T1與T2之間的「更強大類型」 template <typename T1, typename T2> typename StrongerType<T1, T2>::Type Plus(T1 lhs, T2 rhs) { return lhs + rhs; } int main() { Plus(1, 2.); // 完美! }
至此,咱們「終於」實現了一個最完美的Plus函數。
本章所實現的三個小工具,都是STL的type_traits庫的一部分。值得一提的是咱們最後實現的獲取「最強大類型」的工具:這一工具所解決的問題,其實是一個很是經典的問題,其屢次出如今多部著做中。因爲decltype(以及可變參數模板)是C++11的產物,故不少較老的書籍對此問題給出了「無解」的結論,或只能給出一些較爲牽強的解決方案。
值也能成爲模板參數的一部分,而模板參數是編譯期常量,這兩者的結合使得經過模板進行(較複雜的)編譯期計算成爲了可能。因爲編譯器本就不是「計算器」,故標題中使用了「壓榨」一詞,以表達此技術的「高昂的編譯期代價」以及「較大的侷限性」的特色;同時,合理的利用編譯期計算技術,可以極大地提升程序的效率,故「壓榨」也有「壓榨性能」之意。
本章中,咱們以一小一大兩個示例,來討論編譯期計算這一巧妙技術的應用。
編譯期計算階乘是編譯期計算技術的經典案例,許多書籍對此均有討論(每每做爲「模板元編程」一章的首個案例)。那麼首先,讓咱們來看看一個普通的階乘函數的實現:
int Factorial(int N) { return N == 1 ? 1 : N * Factorial(N - 1); }
這個實現很簡單,這裏就不對其進行詳細討論了。下面,咱們來看看如何將這個函數「翻譯」爲一個編譯期就進行計算並獲得結果的「函數」。請看如下示例:
// 遞歸起點 template <int N> struct Factorial { static constexpr int Value = N * Factorial<N - 1>::Value; }; // 遞歸終點 template <> struct Factorial<1> { static constexpr int Value = 1; }; int main() { cout << Factorial<4>::Value; // 編譯期就能得到結果 }
觀察上述代碼,不難總結出咱們的「翻譯」規則:
上述四點「翻譯」規則幾乎就是編譯期計算的所有技巧了!接下來,就讓咱們以一個更復雜的例子來繼續討論這一技術的精彩之處:編譯期分數的實現。
分數,由分子和分母組成。有了上一節的鋪墊,咱們不難發現:分數正是一個能夠使用編譯期計算技術的極佳場合。因此首先,咱們須要實現一個編譯期分數類。編譯期分數類的實現很是簡單,咱們只須要經過一個「構造函數」將模板參數保留下來,做爲靜態數據成員便可。請看如下示例:
template <long long __Numerator, long long __Denominator> struct Fraction { // 「構造函數」 static constexpr long long Numerator = __Numerator; static constexpr long long Denominator = __Denominator; // 將編譯期分數轉爲編譯期浮點數 template <typename T = double> static constexpr T Eval() { return static_cast<T>(Numerator) / static_cast<T>(Denominator); } }; int main() { // 1/2 typedef Fraction<1, 2> OneTwo; // 0.5 cout << OneTwo::Eval<>(); }
由使用示例可見:編譯期分數的「實例化」只須要一個typedef便可;而且,咱們也能經過一個編譯期分數獲得一個編譯期浮點數。
讓咱們繼續討論下一個問題:如何實現約分和通分?
顯然,約分和通分須要「求得兩個數的最大公約數和最小公倍數」的算法。因此,咱們首先來看看這兩個算法的「普通」實現:
// 求得兩個數的最大公約數 long long GreatestCommonDivisor(long long lhs, long long rhs) { return rhs == 0 ? lhs : GreatestCommonDivisor(rhs, lhs % rhs); } // 求得兩個數的最小公倍數 long long LeastCommonMultiple(long long lhs, long long rhs) { return lhs * rhs / GreatestCommonDivisor(lhs, rhs); }
根據上一節的「翻譯規則」,咱們不難翻譯出如下代碼:
// 對應於「return rhs == 0 ? ... : GreatestCommonDivisor(rhs, lhs % rhs)」部分 template <long long LHS, long long RHS> struct __GreatestCommonDivisor { static constexpr long long __Value = __GreatestCommonDivisor<RHS, LHS % RHS>::__Value; }; // 對應於「return rhs == 0 ? lhs : ...」部分 template <long long LHS> struct __GreatestCommonDivisor<LHS, 0> { static constexpr long long __Value = LHS; }; // 對應於「return lhs * rhs / GreatestCommonDivisor(lhs, rhs)」部分 template <long long LHS, long long RHS> struct __LeastCommonMultiple { static constexpr long long __Value = LHS * RHS / __GreatestCommonDivisor<LHS, RHS>::__Value; };
有了上面的這兩個工具,咱們就可以實現出通分和約分了。首先,咱們能夠改進一開始的Fraction類,在「構造函數」中加入「自動約分」功能。請看如下示例:
template <long long __Numerator, long long __Denominator> struct Fraction { // 具備「自動約分」功能的「構造函數」 static constexpr long long Numerator = __Numerator / __GreatestCommonDivisor<__Numerator, __Denominator>::__Value; static constexpr long long Denominator = __Denominator / __GreatestCommonDivisor<__Numerator, __Denominator>::__Value; }; int main() { // 2/4 => 1/2 typedef Fraction<2, 4> OneTwo; }
能夠看出,咱們只需在「構造函數」中添加對分子、分母同時除以其最大公約數的運算,就可以實現「自動約分」了。
接下來,咱們來實現分數的四則運算功能。顯然,分數的四則運算的結果仍是一個分數,故咱們只須要經過using,將「四則運算模板」與「等價的結果分數模板」鏈接起來便可實現。請看如下示例:
// FractionAdd其實就是一個特殊的編譯期分數模板 template <typename LHS, typename RHS> using FractionAdd = Fraction< // 將通分後的分子相加 LHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__LValue + RHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__RValue, // 通分後的分母 __LeastCommonMultiple<LHS::Denominator, RHS::Denominator>::__Value // 自動約分 >; // FractionMinus其實也是一個特殊的編譯期分數模板 template <typename LHS, typename RHS> using FractionMinus = Fraction< // 將通分後的分子相減 LHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__LValue - RHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__RValue, // 通分後的分母 __LeastCommonMultiple<LHS::Denominator, RHS::Denominator>::__Value // 自動約分 >; // FractionMultiply其實也是一個特殊的編譯期分數模板 template <typename LHS, typename RHS> using FractionMultiply = Fraction< // 分子與分子相乘 LHS::Numerator * RHS::Numerator, // 分母與分母相乘 LHS::Denominator * RHS::Denominator // 自動約分 >; // FractionDivide其實也是一個特殊的編譯期分數模板 template <typename LHS, typename RHS> using FractionDivide = Fraction< // 分子與分母相乘 LHS::Numerator * RHS::Denominator, // 分母與分子相乘 LHS::Denominator * RHS::Numerator // 自動約分 >; int main() { // 1/2 typedef Fraction<1, 2> OneTwo; // 2/3 typedef Fraction<2, 3> TwoThree; // 2/3 + 1/2 => 7/6 typedef FractionAdd<TwoThree, OneTwo> TwoThreeAddOneTwo; // 2/3 - 1/2 => 1/6 typedef FractionMinus<TwoThree, OneTwo> TwoThreeMinusOneTwo; // 2/3 * 1/2 => 1/3 typedef FractionMultiply<TwoThree, OneTwo> TwoThreeMultiplyOneTwo; // 2/3 / 1/2 => 4/3 typedef FractionDivide<TwoThree, OneTwo> TwoThreeDivideOneTwo; }
因而可知,所謂的四則運算,實際上就是一個針對Fraction的using(模板不能使用typedef,只能使用using)罷了。
最後,咱們實現分數的比大小功能。這很是簡單:只須要先對分母通分,再對分子進行比大小便可。而比大小的結果,就是「比大小模板」的一個數據成員。請看如下示例:
// 這六個模板都進行「先通分,再比較」運算,惟一的區別就在於比較操做符的不一樣 // 「operator==」 template <typename LHS, typename RHS> struct FractionEqual { static constexpr bool Value = LHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__LValue == RHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__RValue; }; // 「operator!=」 template <typename LHS, typename RHS> struct FractionNotEqual { static constexpr bool Value = LHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__LValue != RHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__RValue; }; // 「operator<」 template <typename LHS, typename RHS> struct FractionLess { static constexpr bool Value = LHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__LValue < RHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__RValue; }; // 「operator<=」 template <typename LHS, typename RHS> struct FractionLessEqual { static constexpr bool Value = LHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__LValue <= RHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__RValue; }; // 「operator>」 template <typename LHS, typename RHS> struct FractionGreater { static constexpr bool Value = LHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__LValue > RHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__RValue; }; // 「operato>=」 template <typename LHS, typename RHS> struct FractionGreaterEqual { static constexpr bool Value = LHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__LValue >= RHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__RValue; }; int main() { // 1/2 typedef Fraction<1, 2> OneTwo; // 2/3 typedef Fraction<2, 3> TwoThree; // 1/2 == 2/3 => false cout << FractionEqual<OneTwo, TwoThree>::Value << endl; // 1/2 != 2/3 => true cout << FractionNotEqual<OneTwo, TwoThree>::Value << endl; // 1/2 < 2/3 => true cout << FractionLess<OneTwo, TwoThree>::Value << endl; // 1/2 <= 2/3 => true cout << FractionLessEqual<OneTwo, TwoThree>::Value << endl; // 1/2 > 2/3 => false cout << FractionGreater<OneTwo, TwoThree>::Value << endl; // 1/2 >= 2/3 => false cout << FractionGreaterEqual<OneTwo, TwoThree>::Value << endl; }
至此,編譯期分數的所有功能就都實現完畢了。不難發現,在編譯期分數的使用過程當中,咱們全程使用的都是typedef,並無真正的構造任何一個分數,一切計算都已經在編譯期完成了。
讀完本章,也許你會恍然大悟:「哦!原來模板也可以表達形參、if、while、return等語義!」,進而,也許你會有疑問:「那既然這樣,豈不是全部的計算函數都能換成編譯期計算了?」。
很惋惜,答案是否認的。
咱們經過對編譯期計算這一技術的優缺點進行總結,從而回答這個問題。編譯期計算的目的,是爲了徹底消除運行時代價,從而在高性能計算場合極大的提升效率;但此技術的缺點也是不少且很明顯的:首先,僅僅爲了進行一次編譯期計算,就有可能進行不少次的模板實例化(好比,爲了計算10的階乘,就要實例化出10個Factorial類),這是一種極大的潛在的編譯期代價;其次,並非任何類型的值都能做爲模板參數,如浮點數(雖然咱們能夠使用編譯期分數間接的規避這一限制)、以及任何的類類型值等均不能夠,這就使得編譯期計算的應用幾乎被限定在只須要使用整型和布爾類型的場合中;最後,「遞歸實例化」在全部的編譯器中都是有最大深度限制的(不過幸運的是,在現代編譯器中,容許的最大深度實際上是比較大的)。但即便如此,因爲編譯期計算技術使得咱們能夠進行「搶跑」,在程序還未開始運行時,就計算出各類複雜的結果,從而極大的提高程序的效率,故此技術固然也是瑕不掩瑜的。
本章旨在討論這樣的一個問題:
如何實現一個「多功能函數」,使得單一的「多功能函數」在面對不一樣類型的參數時,可以自動選擇針對當前類型的最佳方案或獨特功能?
本章亦將經過一小一大兩個案例,來討論這一問題。
STL中的advance函數,可用於將迭代器向前(後)推動N步。顯然,不一樣的迭代器類別對前進這一動做的支持是不同的:如前向迭代器,只能前進,不能後退;雙向迭代器,可雙向移動;而隨機訪問迭代器,不只能夠雙向移動,還能夠「大跨步地」移動。爲了討論方便,咱們首先用數字的加減模擬出這三種不一樣類別的「迭代器」。請看如下示例:
// 模擬的「前向迭代器」 template <typename T> class ForwardIterator { public: // 構造函數 ForwardIterator(const T &val); // operator* T &operator*() { return __val; } // 只能++ void operator++(int) { __val++; } private: // 數據成員 T __val; }; // 模擬的「雙向迭代器」 template <typename T> class BidirectionalIterator { public: // 構造函數 BidirectionalIterator(const T &val); // operator* T &operator*() { return __val; } // 能夠++和-- void operator++(int) { __val++; } void operator--(int) { __val--; } private: // 數據成員 T __val; }; // 模擬的「隨機訪問迭代器」 template <typename T> class RandomAccessIterator { public: // 構造函數 RandomAccessIterator(const T &val); // operator* T &operator*() { return __val; } // 不只能夠++和--,還能夠直接+=、-= void operator++(int) { __val++; } void operator--(int) { __val--; } void operator+=(int N) { __val += N; } void operator-=(int N) { __val -= N; } private: // 數據成員 T __val; }; template <typename T> ForwardIterator<T>::ForwardIterator(const T &val): __val(val) {} template <typename T> BidirectionalIterator<T>::BidirectionalIterator(const T &val): __val(val) {} template <typename T> RandomAccessIterator<T>::RandomAccessIterator(const T &val): __val(val) {}
此時,咱們就能夠實現出一個簡單的advance函數了。請看如下示例:
template <typename Iterator> void Advance(Iterator &iter, int N) { for (int _ = 0; _ < N; _++) iter++; } int main() { ForwardIterator<int> forwardIterator(0); BidirectionalIterator<int> bidirectionalIterator(0); RandomAccessIterator<int> randomAccessIterator(0); Advance(forwardIterator, 10); Advance(bidirectionalIterator, 10); Advance(randomAccessIterator, 10); cout << *forwardIterator << endl; // 10 cout << *bidirectionalIterator << endl; // 10 cout << *randomAccessIterator << endl; // 10 }
上述實現彷佛運行正常,但稍加分析不難發現,這一實現具備如下兩個主要缺點:
怎麼樣才能既提供單一接口,又可以讓接口有能力分辨出不一樣的迭代器類別呢?首先,咱們須要將「迭代器的類別」這一語義表達出來,能夠經過「強行編造」一些空類實現這一語義。請看如下示例:
// 「強行編造」的三種不一樣的迭代器類別 struct ForwardIteratorTag {}; struct BidirectionalIteratorTag {}; struct RandomAccessIteratorTag {};
而後,咱們能夠爲每一個迭代器添加一個typedef,從而表示出這個迭代器的類別。請看如下示例:
template <typename T> class ForwardIterator { public: // 前向迭代器的類別是ForwardIteratorTag typedef ForwardIteratorTag IteratorCategory; // ... }; template <typename T> class BidirectionalIterator { public: // 雙向迭代器的類別是BidirectionalIteratorTag typedef BidirectionalIteratorTag IteratorCategory; // ... }; template <typename T> class RandomAccessIterator { public: // 隨機訪問迭代器的類別是RandomAccessIteratorTag typedef RandomAccessIteratorTag IteratorCategory; // ... };
此時,當咱們拿到一個迭代器類型Iterator時,咱們就能夠經過typename Iterator::IteratorCategory來獲取到當前迭代器的類別了。
同時,咱們能夠以迭代器類別做爲Advance函數的第三參數,從而重載出多個不一樣版本的Advance函數。請看如下示例:
// 適用於前向迭代器的版本 template <typename Iterator> void __Advance(Iterator &iter, int N, ForwardIteratorTag) { for (int _ = 0; _ < N; _++) iter++; } // 適用於雙向迭代器的版本 template <typename Iterator> void __Advance(Iterator &iter, int N, BidirectionalIteratorTag) { if (N > 0) { for (int _ = 0; _ < N; _++) iter++; } else { for (int _ = 0; _ < -N; _++) iter--; } } // 適用於隨機訪問迭代器的版本 template <typename Iterator> void __Advance(Iterator &iter, int N, RandomAccessIteratorTag) { iter += N; }
此時,咱們已經擁有了兩組工具:
此時,只要咱們使用迭代器類別(做爲第三參數)去調用__Advance函數,編譯器就將根據重載肯定規則,選擇適用於當前迭代器類別的__Advance函數進行調用了。請看如下示例:
int main() { ForwardIterator<int> forwardIterator(0); BidirectionalIterator<int> bidirectionalIterator(0); RandomAccessIterator<int> randomAccessIterator(0); // 重載肯定至__Advance(Iterator &iter, int N, ForwardIteratorTag)版本 __Advance(forwardIterator, 10, typename ForwardIterator<int>::IteratorCategory()); // 重載肯定至__Advance(Iterator &iter, int N, BidirectionalIteratorTag)版本 __Advance(bidirectionalIterator, 10, typename BidirectionalIterator<int>::IteratorCategory()); // 重載肯定至__Advance(Iterator &iter, int N, RandomAccessIteratorTag)版本 __Advance(randomAccessIterator, 10, typename RandomAccessIterator<int>::IteratorCategory()); cout << *forwardIterator << endl; // 10 cout << *bidirectionalIterator << endl; // 10 cout << *randomAccessIterator << endl; // 10 }
這就結束了嗎?怎麼感受函數的調用這麼麻煩呢?顯然還未結束。
不難發現,在任何一個函數的實現代碼中,咱們都不只擁有參數值,還擁有每一個參數的類型。因此,咱們能夠再實現一個只有兩個參數的函數,並在其內部構造出「typename Iterator::IteratorCategory()」,並調用三個參數的__Advance函數。而這,就是基於重載肯定的編譯期分派技術。請看如下示例:
// 最終的「多功能函數」 template <typename Iterator> void Advance(Iterator &iter, int N) { // 根據typename Iterator::IteratorCategory()進行基於重載肯定的編譯期分派 __Advance(iter, N, typename Iterator::IteratorCategory()); } int main() { ForwardIterator<int> forwardIterator(0); BidirectionalIterator<int> bidirectionalIterator(0); RandomAccessIterator<int> randomAccessIterator(0); // 重載肯定至__Advance(Iterator &iter, int N, ForwardIteratorTag)版本 Advance(forwardIterator, 10); // 重載肯定至__Advance(Iterator &iter, int N, BidirectionalIteratorTag)版本 Advance(bidirectionalIterator, 10); // 重載肯定至__Advance(Iterator &iter, int N, RandomAccessIteratorTag)版本 Advance(randomAccessIterator, 10); cout << *forwardIterator << endl; // 10 cout << *bidirectionalIterator << endl; // 10 cout << *randomAccessIterator << endl; // 10 }
至此,咱們就完成了Advance這個「多功能函數」的實現。但最後,咱們還有一個重要問題須要解決:指針也是迭代器,那麼指針的迭代器類型(固然是隨機訪問迭代器)怎麼獲取?
也許不用我說,你就已經知道答案了,解決方案就是「加中間層可解決一切問題」定理。咱們能夠爲「獲取迭代器類型」這一操做添加一箇中間層,並在此中間層中,對指針類型進行特化。請看如下示例:
// 迭代器的迭代器類型 template <typename Iterator> struct IteratorTraits { typedef typename Iterator::IteratorCategory IteratorCategory; }; // 指針的迭代器類型 template <typename T> struct IteratorTraits<T *> { typedef RandomAccessIteratorTag IteratorCategory; };
同時,咱們還須要將上面的Advance函數中的「簡單粗暴的typename Iterator::IteratorCategory」替換爲咱們剛剛實現的IteratorTraits方法:
template <typename Iterator> void Advance(Iterator &iter, int N) { // 根據typename IteratorTraits<Iterator>::IteratorCategory()進行基於重載肯定的編譯期分派 __Advance(iter, N, typename IteratorTraits<Iterator>::IteratorCategory()); }
至此,Advance函數的實現也就所有完成了。
接下來,咱們以一個更爲複雜,也更爲神奇的功能,繼續討論編譯期分派這一技術。
本節將要實現的功能,其最終使用起來十分簡單:
print(X); // "X"能夠是任何值!
沒錯,這是一個能夠打印任何值的函數!經過上一節的鋪墊,咱們知道:做爲實現者,咱們不只能夠獲得X的值,還能「順便」獲得X的類型。因此,咱們就能夠在X的類型上大作文章,針對不一樣的類型實現出不一樣的打印函數,最後,經過這個print函數進行編譯期分派,從而實現出這一神奇的函數。
首先應該實現什麼呢?不難發現,X能夠是一個「能夠直接cout的值」、(支持迭代器的)容器、Pair、Tuple、Stack、Queue等類別。因此,咱們首先須要對X的這些不一樣的類別進行分類,經過建立不少空的Tag類便可實現此功能。請看如下示例:
// 默認類別 struct __CommonTag {}; // 線性容器類別 struct __SequenceContainerTag {}; // Map容器類別 struct __MapContainerTag {}; // Set容器類別 struct __SetContainerTag {}; // Pair類別 struct __PairTag {}; // Map中的Pair類別 struct __MapPairTag {}; // Tuple類別 struct __TupleTag {}; // Stack類別 struct __StackTag {}; // Queue類別 struct __QueueTag {};
而後,經過創建一系列的Traits,咱們就能夠將X的類型映射到這些Tag上:
// 若是T不是下面所說起的類型中的任何一種,那麼T的類別就是__CommonTag(即:「能夠直接cout的類型」) template <typename T> struct __CategoryTraits { typedef __CommonTag __Category; }; // array<T, N>的類別是__SequenceContainerTag template <typename T, size_t N> struct __CategoryTraits<array<T, N>> { typedef __SequenceContainerTag __Category; }; // deque<T>的類別是__SequenceContainerTag template <typename T> struct __CategoryTraits<deque<T>> { typedef __SequenceContainerTag __Category; }; // forward_list<T>的類別是__SequenceContainerTag template <typename T> struct __CategoryTraits<forward_list<T>> { typedef __SequenceContainerTag __Category; }; // list<T>的類別是__SequenceContainerTag template <typename T> struct __CategoryTraits<list<T>> { typedef __SequenceContainerTag __Category; }; // vector<T>的類別是__SequenceContainerTag template <typename T> struct __CategoryTraits<vector<T>> { typedef __SequenceContainerTag __Category; }; // map<K, V>的類別是__MapContainerTag template <typename K, typename V> struct __CategoryTraits<map<K, V>> { typedef __MapContainerTag __Category; }; // multimap<K, V>的類別是__MapContainerTag template <typename K, typename V> struct __CategoryTraits<multimap<K, V>> { typedef __MapContainerTag __Category; }; // unordered_map<K, V>的類別是__MapContainerTag template <typename K, typename V> struct __CategoryTraits<unordered_map<K, V>> { typedef __MapContainerTag __Category; }; // unordered_multimap<K, V>的類別是__MapContainerTag template <typename K, typename V> struct __CategoryTraits<unordered_multimap<K, V>> { typedef __MapContainerTag __Category; }; // set<T>的類別是__SetContainerTag template <typename T> struct __CategoryTraits<set<T>> { typedef __SetContainerTag __Category; }; // multiset<T>的類別是__SetContainerTag template <typename T> struct __CategoryTraits<multiset<T>> { typedef __SetContainerTag __Category; }; // unordered_set<T>的類別是__SetContainerTag template <typename T> struct __CategoryTraits<unordered_set<T>> { typedef __SetContainerTag __Category; }; // unordered_multiset<T>的類別是__SetContainerTag template <typename T> struct __CategoryTraits<unordered_multiset<T>> { typedef __SetContainerTag __Category; }; // pair<T1, T2>的類別是__PairTag template <typename T1, typename T2> struct __CategoryTraits<pair<T1, T2>> { typedef __PairTag __Category; }; // tuple<Types...>的類別是__TupleTag template <typename... Types> struct __CategoryTraits<tuple<Types...>> { typedef __TupleTag __Category; }; // stack<T>的類別是__StackTag template <typename T> struct __CategoryTraits<stack<T>> { typedef __StackTag __Category; }; // queue<T>的類別是__QueueTag template <typename T> struct __CategoryTraits<queue<T>> { typedef __QueueTag __Category; }; // priority_queue<T>的類別是__StackTag template <typename T> struct __CategoryTraits<priority_queue<T>> { typedef __StackTag __Category; };
接下來須要解決的問題是:咱們但願面對相似於一維數組這樣的「較小的值」和相似於高維數組這樣的「較大的值」時,可以採用或緊湊,或分散的不一樣的打印格式。具體什麼是「較小的值」呢?這裏,咱們認爲:若是X自己的類別就是__CommonTag,或X中的值(前提是X自己的類別不是__CommonTag)的類別是__CommonTag,則認爲X是一個「較小的值」,採起緊湊的打印格式,不然,就認爲X是一個「較大的值」,採起分散的打印格式。
那麼,咱們就又引出了一個問題:對於某些類型,如Pair和Tuple,其中的各個元素的類型是不同的,即:各個元素的類別也是不同的;同時,很顯然,當咱們面對多個類別時,只要其中有一個類別不是__CommonTag,那麼咱們就應當認爲這些類別的「最強大類別」不是__CommonTag。所以,咱們首先須要實現一個獲取「最強大類別」的Traits。請看如下示例:
// 原型 // 經過typename __CategoryPromotionTraits<Tags...>::__Category獲取「最強大類別」 template <typename... Tags> struct __CategoryPromotionTraits; // 若是有兩個任意的類別,則隨便選一個類別做爲「更強大類別」便可... template <typename Tag1, typename Tag2> struct __CategoryPromotionTraits<Tag1, Tag2> { typedef Tag1 __Category; }; // ...可是,若是右類別是__CommonTag,則「更強大類別」須要「敬而遠之」... template <typename Tag1> struct __CategoryPromotionTraits<Tag1, __CommonTag> { typedef Tag1 __Category; }; // ...同理,若是左類別是__CommonTag,則「更強大類別」也須要「敬而遠之」... template <typename Tag2> struct __CategoryPromotionTraits<__CommonTag, Tag2> { typedef Tag2 __Category; }; // ...只有當「你我都是普通人」時,「更強大類別」纔是__CommonTag template <> struct __CategoryPromotionTraits<__CommonTag, __CommonTag> { typedef __CommonTag __Category; }; // 面對不止兩個類別時,「最強大類別」應該是Tag1與Tags...的「最強大類別」之間的「更強大類別」 template <typename Tag1, typename... Tags> struct __CategoryPromotionTraits<Tag1, Tags...> { typedef typename __CategoryPromotionTraits< Tag1, typename __CategoryPromotionTraits<Tags...>::__Category >::__Category __Category; };
接下來,咱們就來實現可以獲取X中的元素的類別(即X的「子類別」)的Traits:
// 原型 // 經過typename __SubCategoryTraits<X的類別, X的類型>::__Category獲取X中的元素的類別 template <typename Tag, typename T> struct __SubCategoryTraits; // __CommonTag沒有子類別 template <typename T> struct __SubCategoryTraits<__CommonTag, T> { typedef void __Category; }; // __SequenceContainerTag的子類別就是T::value_type的類別 template <typename T> struct __SubCategoryTraits<__SequenceContainerTag, T> { typedef typename __CategoryTraits<typename T::value_type>::__Category __Category; }; // __MapContainerTag的子類別必定是__MapPairTag template <typename T> struct __SubCategoryTraits<__MapContainerTag, T> { typedef __MapPairTag __Category; }; // __SetContainerTag的子類別就是T::value_type的類別 template <typename T> struct __SubCategoryTraits<__SetContainerTag, T> { typedef typename __CategoryTraits<typename T::value_type>::__Category __Category; }; // __MapPairTag的子類別是T::first_type的類別與T::second_type的類別之間的「更強大類別」 template <typename T> struct __SubCategoryTraits<__MapPairTag, T> { typedef typename __CategoryPromotionTraits< typename __CategoryTraits<typename T::first_type>::__Category, typename __CategoryTraits<typename T::second_type>::__Category >::__Category __Category; }; // 和__MapPairTag同樣 // __PairTag的子類別也是T::first_type的類別與T::second_type的類別之間的「更強大類別」 template <typename T> struct __SubCategoryTraits<__PairTag, T> { typedef typename __CategoryPromotionTraits< typename __CategoryTraits<typename T::first_type>::__Category, typename __CategoryTraits<typename T::second_type>::__Category >::__Category __Category; }; // __TupleTag的子類別是各個Types的類別之中的「最強大類別」 template <typename... Types> struct __SubCategoryTraits<__TupleTag, tuple<Types...>> { typedef typename __CategoryPromotionTraits< typename __CategoryTraits<Types>::__Category... >::__Category __Category; }; // __StackTag的子類別就是T::value_type的類別 template <typename T> struct __SubCategoryTraits<__StackTag, T> { typedef typename __CategoryTraits<typename T::value_type>::__Category __Category; }; // __QueueTag的子類別就是T::value_type的類別 template <typename T> struct __SubCategoryTraits<__QueueTag, T> { typedef typename __CategoryTraits<typename T::value_type>::__Category __Category; };
有了__CategoryTraits和__SubCategoryTraits這兩個工具,咱們的準備工做也就基本上完成了。接下來是最後的一些簡單的準備工做:
// 縮進空白字符 const char __SPACE = ' '; // 單次縮進長度 const int __INDENTATION_LEN = 4; // 元素之間的分隔符 const string __VALUE_SPLICE = ", "; // 行末分隔符 const char __LINE_END = ','; // 線性容器類別的左右定界符 const char __SEQUENCE_CONTAINER_BEGIN = '['; const char __SEQUENCE_CONTAINER_END = ']'; // Map容器類別的左右定界符 const char __MAP_CONTAINER_BEGIN = '{'; const char __MAP_CONTAINER_END = '}'; // Set容器類別的左右定界符 const char __SET_CONTAINER_BEGIN = '{'; const char __SET_CONTAINER_END = '}'; // Pair類別的左右定界符 const char __PAIR_BEGIN = '('; const char __PAIR_END = ')'; // Map中的Pair類別的左右定界符 const char __MAP_PAIR_BEGIN = '('; const char __MAP_PAIR_END = ')'; // Map中的Pair類別的鍵值對分隔符 const string __MAP_PAIR_SPLICE = ": "; // Map中的Pair類別的鍵值對行末分隔符 const char __MAP_PAIR_LINE_END = ':'; // Tuple容器類別的左右定界符 const char __TUPLE_BEGIN = '('; const char __TUPLE_END = ')'; // Stack容器類別的左右定界符 const char __STACK_BEGIN = '['; const char __STACK_END = ']'; // Queue容器類別的左右定界符 const char __QUEUE_BEGIN = '['; const char __QUEUE_END = ']';
template <typename T> T __reverseStack(T oriStack) // 必須使用值傳遞 { T reverseStack; while (!oriStack.empty()) { reverseStack.push(oriStack.top()); oriStack.pop(); } return reverseStack; }
終於能夠開始着手實現最重要的print函數了!這裏咱們經過實現不少個__PrintData類的特化來實現針對不一樣類別及其子類別組合的編譯期分派。那麼,__PrintData類須要哪些模板參數呢?X的類別、X的子類別、X的類型,顯然都是須要的,此外,咱們還須要在模板參數中維護一個整數,用於使每一個模板都可以知道「我在第幾層?」,以實現打印時的縮進控制。請看如下示例:
// 原型 // 經過__PrintData<SelfTag, SubTag, T, N>::__Print(val)打印val template <typename SelfTag, typename SubTag, typename T, int N> struct __PrintData; // 打印__CommonTag類別的值(__CommonTag的子類別必定是void),採用緊湊的打印格式 template <typename T, int N> struct __PrintData<__CommonTag, void, T, N> { static void __Print(const T &val); }; // 打印__SequenceContainerTag類別 + 任意子類別組合的值,採用寬鬆的打印格式... template <typename SubTag, typename T, int N> struct __PrintData<__SequenceContainerTag, SubTag, T, N> { static void __Print(const T &val); }; // ...可是,若是子類別是__CommonTag,則採用緊湊的打印格式 template <typename T, int N> struct __PrintData<__SequenceContainerTag, __CommonTag, T, N> { static void __Print(const T &val); }; // 打印__MapContainerTag類別 + __MapPairTag子類別組合的值,採用寬鬆的打印格式 template <typename T, int N> struct __PrintData<__MapContainerTag, __MapPairTag, T, N> { static void __Print(const T &val); }; // 打印__SetContainerTag類別 + 任意子類別組合的值,採用寬鬆的打印格式... template <typename SubTag, typename T, int N> struct __PrintData<__SetContainerTag, SubTag, T, N> { static void __Print(const T &val); }; // ...可是,若是子類別是__CommonTag,則採用緊湊的打印格式 template <typename T, int N> struct __PrintData<__SetContainerTag, __CommonTag, T, N> { static void __Print(const T &val); }; // 打印__PairTag類別 + 任意子類別組合的值,採用寬鬆的打印格式... template <typename SubTag, typename T, int N> struct __PrintData<__PairTag, SubTag, T, N> { static void __Print(const T &val); }; // ...可是,若是子類別是__CommonTag,則採用緊湊的打印格式 template <typename T, int N> struct __PrintData<__PairTag, __CommonTag, T, N> { static void __Print(const T &val); }; // 打印__MapPairTag類別 + 任意子類別組合的值,採用寬鬆的打印格式... template <typename SubTag, typename T, int N> struct __PrintData<__MapPairTag, SubTag, T, N> { static void __Print(const T &val); }; // ...可是,若是子類別是__CommonTag,則採用緊湊的打印格式 template <typename T, int N> struct __PrintData<__MapPairTag, __CommonTag, T, N> { static void __Print(const T &val); }; // 打印__TupleTag類別 + 任意子類別組合的值,採用寬鬆的打印格式... template <typename SubTag, typename T, int N> struct __PrintData<__TupleTag, SubTag, T, N> { static void __Print(const T &val); }; // ...可是,若是子類別是__CommonTag,則採用緊湊的打印格式 template <typename T, int N> struct __PrintData<__TupleTag, __CommonTag, T, N> { static void __Print(const T &val); }; // 打印__StackTag類別 + 任意子類別組合的值,採用寬鬆的打印格式... template <typename SubTag, typename T, int N> struct __PrintData<__StackTag, SubTag, T, N> { static void __Print(const T &val); }; // ...可是,若是子類別是__CommonTag,則採用緊湊的打印格式 template <typename T, int N> struct __PrintData<__StackTag, __CommonTag, T, N> { static void __Print(const T &val); }; // 打印__QueueTag + 任意子類別組合的值,採用寬鬆的打印格式... template <typename SubTag, typename T, int N> struct __PrintData<__QueueTag, SubTag, T, N> { static void __Print(T val); }; // ...可是,若是子類別是__CommonTag,則採用緊湊的打印格式 template <typename T, int N> struct __PrintData<__QueueTag, __CommonTag, T, N> { static void __Print(T val); };
如下是除了Tuple之外的(Tuple的打印函數的實現咱們將在稍後討論)各個打印函數的實現:
// 打印__CommonTag類別的值(__CommonTag的子類別必定是void),採用緊湊的打印格式 template <typename T, int N> void __PrintData<__CommonTag, void, T, N>::__Print(const T &val) { // 直接打印縮進與val cout << string(N * __INDENTATION_LEN, __SPACE) << val; } // 打印__SequenceContainerTag類別 + 任意子類別組合的值,採用寬鬆的打印格式... template <typename SubTag, typename T, int N> void __PrintData<__SequenceContainerTag, SubTag, T, N>::__Print(const T &val) { // 打印縮進與線性容器類別的左定界符,而後換行 cout << string(N * __INDENTATION_LEN, __SPACE) << __SEQUENCE_CONTAINER_BEGIN << endl; for (auto &subVal: val) { // 縮進層數+1,使用線性容器中的元素的__PrintData版本,依次打印線性容器中的每一個元素 __PrintData< SubTag, typename __SubCategoryTraits<SubTag, typename T::value_type>::__Category, typename T::value_type, N + 1 >::__Print(subVal); // 打印行末分隔符後換行 cout << __LINE_END << endl; } // 打印縮進與線性容器類別的右定界符 cout << string(N * __INDENTATION_LEN, __SPACE) << __SEQUENCE_CONTAINER_END; } // ...可是,若是子類別是__CommonTag,則採用緊湊的打印格式 template <typename T, int N> void __PrintData<__SequenceContainerTag, __CommonTag, T, N>::__Print(const T &val) { // 打印縮進與線性容器類別的左定界符 cout << string(N * __INDENTATION_LEN, __SPACE) << __SEQUENCE_CONTAINER_BEGIN; // 不換行,依次打印線性容器中的每一個元素,以及元素之間的分隔符 if (!val.empty()) { cout << val.front(); for (auto iter = next(val.begin()); iter != val.end(); iter++) { cout << __VALUE_SPLICE << *iter; } } // 打印線性容器類別的右定界符 cout << __SEQUENCE_CONTAINER_END; } // 打印__MapContainerTag類別 + __MapPairTag子類別組合的值,採用寬鬆的打印格式 template <typename T, int N> void __PrintData<__MapContainerTag, __MapPairTag, T, N>::__Print(const T &val) { // 打印縮進與Map容器類別的左定界符,而後換行 cout << string(N * __INDENTATION_LEN, __SPACE) << __MAP_CONTAINER_BEGIN << endl; for (auto &subVal: val) { // 縮進層數+1,直接使用__MapPairTag類別以及Map容器的子類別生成__PrintData,用於打印Map中的Pair __PrintData< __MapPairTag, typename __SubCategoryTraits<__MapPairTag, typename T::value_type>::__Category, typename T::value_type, N + 1 >::__Print(subVal); // 打印行末分隔符後換行 cout << __LINE_END << endl; } // 打印縮進與Map容器類別的右定界符 cout << string(N * __INDENTATION_LEN, __SPACE) << __MAP_CONTAINER_END; } // 打印__SetContainerTag類別 + 任意子類別組合的值,採用寬鬆的打印格式... template <typename SubTag, typename T, int N> void __PrintData<__SetContainerTag, SubTag, T, N>::__Print(const T &val) { // 打印縮進與Set容器類別的左定界符,而後換行 cout << string(N * __INDENTATION_LEN, __SPACE) << __SET_CONTAINER_BEGIN << endl; for (auto &subVal: val) { // 縮進層數+1,使用Set容器中的元素的__PrintData版本,依次打印Set容器中的每一個元素 __PrintData< SubTag, typename __SubCategoryTraits<SubTag, typename T::value_type>::__Category, typename T::value_type, N + 1 >::__Print(subVal); // 打印行末分隔符後換行 cout << __LINE_END << endl; } // 打印縮進與Set容器類別的右定界符 cout << string(N * __INDENTATION_LEN, __SPACE) << __SET_CONTAINER_END; } // ...可是,若是子類別是__CommonTag,則採用緊湊的打印格式 template <typename T, int N> void __PrintData<__SetContainerTag, __CommonTag, T, N>::__Print(const T &val) { // 打印縮進與Set容器類別的左定界符 cout << string(N * __INDENTATION_LEN, __SPACE) << __SET_CONTAINER_BEGIN; // 不換行,依次打印Set容器中的每一個元素,以及元素之間的分隔符 if (!val.empty()) { cout << *val.begin(); for (auto iter = next(val.begin()); iter != val.end(); iter++) { cout << __VALUE_SPLICE << *iter; } } // 打印Set容器類別的右定界符 cout << __SET_CONTAINER_END; } // 打印__PairTag類別 + 任意子類別組合的值,採用寬鬆的打印格式... template <typename SubTag, typename T, int N> void __PrintData<__PairTag, SubTag, T, N>::__Print(const T &val) { // 打印縮進與Pair類別的左定界符,而後換行 cout << string(N * __INDENTATION_LEN, __SPACE) << __PAIR_BEGIN << endl; // 縮進層數+1,使用val.first的__PrintData版本打印val.first __PrintData< typename __CategoryTraits<typename T::first_type>::__Category, typename __SubCategoryTraits< typename __CategoryTraits<typename T::first_type>::__Category, typename T::first_type >::__Category, typename T::first_type, N + 1 >::__Print(val.first); // 打印行末分隔符後換行 cout << __LINE_END << endl; // 縮進層數+1,使用val.second的__PrintData版本打印val.second __PrintData< typename __CategoryTraits<typename T::second_type>::__Category, typename __SubCategoryTraits< typename __CategoryTraits<typename T::second_type>::__Category, typename T::second_type >::__Category, typename T::second_type, N + 1 >::__Print(val.second); // 打印行末分隔符後換行,再打印縮進與Pair類別的右定界符 cout << __LINE_END << endl << string(N * __INDENTATION_LEN, __SPACE) << __PAIR_END; } // ...可是,若是子類別是__CommonTag,則採用緊湊的打印格式 template <typename T, int N> void __PrintData<__PairTag, __CommonTag, T, N>::__Print(const T &val) { // 直接依次打印縮進、Pair類別的左定界符、val.first、元素之間的分隔符、 // val.second以及Pair類別的右定界符 cout << string(N * __INDENTATION_LEN, __SPACE) << __PAIR_BEGIN << val.first << __VALUE_SPLICE << val.second << __PAIR_END; } // 打印__MapPairTag類別 + 任意子類別組合的值,採用寬鬆的打印格式... // 此版本的實現與__PrintData<__PairTag, SubTag, T, N>版本高度類似 // 惟一的區別在於定界符的選取不一樣 template <typename SubTag, typename T, int N> void __PrintData<__MapPairTag, SubTag, T, N>::__Print(const T &val) { // 打印縮進與Map中的Pair類別的左定界符,而後換行 cout << string(N * __INDENTATION_LEN, __SPACE) << __MAP_PAIR_BEGIN << endl; // 縮進層數+1,使用val.first的__PrintData版本打印val.first __PrintData< typename __CategoryTraits<typename T::first_type>::__Category, typename __SubCategoryTraits< typename __CategoryTraits<typename T::first_type>::__Category, typename T::first_type >::__Category, typename T::first_type, N + 1 >::__Print(val.first); // 打印Map中的Pair類別的鍵值對行末分隔符後換行 cout << __MAP_PAIR_LINE_END << endl; // 縮進層數+1,使用val.second的__PrintData版本打印val.second __PrintData< typename __CategoryTraits<typename T::second_type>::__Category, typename __SubCategoryTraits< typename __CategoryTraits<typename T::second_type>::__Category, typename T::second_type >::__Category, typename T::second_type, N + 1 >::__Print(val.second); // 打印行末分隔符後換行,再打印縮進與Map中的Pair類別的右定界符 cout << __LINE_END << endl << string(N * __INDENTATION_LEN, __SPACE) << __MAP_PAIR_END; } // ...可是,若是子類別是__CommonTag,則採用緊湊的打印格式 // 此版本的實現與__PrintData<__PairTag, __CommonTag, T, N>版本高度類似 // 惟一的區別在於定界符的選取不一樣 template <typename T, int N> void __PrintData<__MapPairTag, __CommonTag, T, N>::__Print(const T &val) { // 直接依次打印縮進、Map中的Pair類別的左定界符、val.first、 // Map中的Pair類別的鍵值對分隔符、val.second以及Map中的Pair類別的右定界符 cout << string(N * __INDENTATION_LEN, __SPACE) << __MAP_PAIR_BEGIN << val.first << __MAP_PAIR_SPLICE << val.second << __MAP_PAIR_END; } // 打印__StackTag類別 + 任意子類別組合的值,採用寬鬆的打印格式... template <typename SubTag, typename T, int N> void __PrintData<__StackTag, SubTag, T, N>::__Print(const T &val) { // 獲得一個反序的Stack T reverseVal = __reverseStack(val); // 打印縮進與Stack容器類別的左定界符,而後換行 cout << string(N * __INDENTATION_LEN, __SPACE) << __STACK_BEGIN << endl; while (!reverseVal.empty()) { // 縮進層數+1,使用Stack容器中的元素的__PrintData版本,依次打印Stack容器中的每一個元素 __PrintData< SubTag, typename __SubCategoryTraits<SubTag, typename T::value_type>::__Category, typename T::value_type, N + 1 >::__Print(reverseVal.top()); // 打印行末分隔符後換行 cout << __LINE_END << endl; // 出棧 reverseVal.pop(); } // 打印縮進與Stack容器類別的右定界符 cout << string(N * __INDENTATION_LEN, __SPACE) << __STACK_END; } // ...可是,若是子類別是__CommonTag,則採用緊湊的打印格式 template <typename T, int N> void __PrintData<__StackTag, __CommonTag, T, N>::__Print(const T &val) { // 獲得一個反序的Stack T reverseVal = __reverseStack(val); // 打印縮進與Stack容器類別的左定界符 cout << string(N * __INDENTATION_LEN, __SPACE) << __STACK_BEGIN; // 不換行,依次打印Stack容器中的每一個元素,以及元素之間的分隔符 if (!reverseVal.empty()) { cout << reverseVal.top(); reverseVal.pop(); while (!reverseVal.empty()) { cout << __VALUE_SPLICE << reverseVal.top(); reverseVal.pop(); } } // 打印Stack容器類別的右定界符 cout << __STACK_END; } // 打印__QueueTag類別 + 任意子類別組合的值,採用寬鬆的打印格式... template <typename SubTag, typename T, int N> void __PrintData<__QueueTag, SubTag, T, N>::__Print(T val) { // 打印縮進與Queue容器類別的左定界符,而後換行 cout << string(N * __INDENTATION_LEN, __SPACE) << __QUEUE_BEGIN << endl; while (!val.empty()) { // 縮進層數+1,使用Queue容器中的元素的__PrintData版本,依次打印Queue容器中的每一個元素 __PrintData< SubTag, typename __SubCategoryTraits<SubTag, typename T::value_type>::__Category, typename T::value_type, N + 1 >::__Print(val.front()); // 打印行末分隔符後換行 cout << __LINE_END << endl; // 出隊列 val.pop(); } // 打印縮進與Queue容器類別的右定界符 cout << string(N * __INDENTATION_LEN, __SPACE) << __QUEUE_END; } // ...可是,若是子類別是__CommonTag,則採用緊湊的打印格式 template <typename T, int N> void __PrintData<__QueueTag, __CommonTag, T, N>::__Print(T val) { // 打印縮進與Queue容器類別的左定界符 cout << string(N * __INDENTATION_LEN, __SPACE) << __QUEUE_BEGIN; // 不換行,依次打印Queue容器中的每一個元素,以及元素之間的分隔符 if (!val.empty()) { cout << val.front(); val.pop(); while (!val.empty()) { cout << __VALUE_SPLICE << val.front(); val.pop(); } } // 打印Queue容器類別的右定界符 cout << __QUEUE_END; }
怎麼打印Tuple?雖然Tuple不能經過真正的for循環進行遍歷,但咱們能夠使用編譯期的「for循環」對Tuple進行「遍歷」。請看如下示例:
// 以緊湊(單行)的打印格式打印Tuple // 從「索引值」0,向「最大索引值」TopIdx進行「迭代」 template <typename T, int Idx, int TopIdx> struct __PrintTupleOneLine { static void __Print(const T &val); }; // 當Idx == TopIdx時,「迭代」結束 template <typename T, int TopIdx> struct __PrintTupleOneLine<T, TopIdx, TopIdx> { static void __Print(const T &val) {} }; // 以寬鬆(多行)的打印格式打印Tuple // 從「索引值」0,向「最大索引值」TopIdx進行「迭代」 template <typename T, int N, int Idx, int TopIdx> struct __PrintTupleMultiLine { static void __Print(const T &val); }; // 當Idx == TopIdx時,「迭代」結束 template <typename T, int N, int TopIdx> struct __PrintTupleMultiLine<T, N, TopIdx, TopIdx> { static void __Print(const T &val) {} }; template <typename T, int Idx, int TopIdx> void __PrintTupleOneLine<T, Idx, TopIdx>::__Print(const T &val) { // 打印「val[Idx]」 cout << get<Idx>(val); // 若是「val[Idx]」不是Tuple的最後一個元素,則打印元素之間的分隔符 if (Idx + 1 < TopIdx) { cout << __VALUE_SPLICE; } // 繼續打印「val[Idx + 1]」... __PrintTupleOneLine<T, Idx + 1, TopIdx>::__Print(val); } template <typename T, int N, int Idx, int TopIdx> void __PrintTupleMultiLine<T, N, Idx, TopIdx>::__Print(const T &val) { // 縮進層數+1,使用「val[Idx]」的__PrintData版本打印「val[Idx]」 __PrintData< typename __CategoryTraits<typename tuple_element<Idx, T>::type>::__Category, typename __SubCategoryTraits< typename __CategoryTraits<typename tuple_element<Idx, T>::type>::__Category, typename tuple_element<Idx, T>::type >::__Category, typename tuple_element<Idx, T>::type, N + 1 >::__Print(get<Idx>(val)); // 打印行末分隔符後換行 cout << __LINE_END << endl; // 繼續打印「val[Idx + 1]」... __PrintTupleMultiLine<T, N, Idx + 1, TopIdx>::__Print(val); }
此時,咱們就能夠提供用於打印Tuple的__PrintData的定義了:
// 打印__TupleTag類別 + 任意子類別組合的值,採用寬鬆的打印格式... template <typename SubTag, typename T, int N> void __PrintData<__TupleTag, SubTag, T, N>::__Print(const T &val) { // 打印縮進與Tuple容器類別的左定界符,而後換行 cout << string(N * __INDENTATION_LEN, __SPACE) << __TUPLE_BEGIN << endl; // 調用多行版本的Tuple打印函數,打印val __PrintTupleMultiLine<T, N, 0, tuple_size<T>::value>::__Print(val); // 打印Tuple容器類別的右定界符 cout << string(N * __INDENTATION_LEN, __SPACE) << __TUPLE_END; } // ...可是,若是子類別是__CommonTag,則採用緊湊的打印格式 template <typename T, int N> void __PrintData<__TupleTag, __CommonTag, T, N>::__Print(const T &val) { // 打印縮進與Tuple容器類別的左定界符 cout << string(N * __INDENTATION_LEN, __SPACE) << __TUPLE_BEGIN; // 調用單行版本的Tuple打印函數,打印val __PrintTupleOneLine<T, 0, tuple_size<T>::value>::__Print(val); // 打印Tuple容器類別的右定界符 cout << __TUPLE_END; }
至此,咱們已經實現了print函數所須要的一切底層組件。如今咱們須要作的,就是匯聚全部的這些底層組件,最終實現print函數。請看如下示例:
template <typename T> void print(const T &val) { __PrintData< // T的類別 typename __CategoryTraits<T>::__Category, // T的子類別 typename __SubCategoryTraits< typename __CategoryTraits<T>::__Category, T >::__Category, // val的類型 T, // 縮進層數從0開始 0 >::__Print(val); cout << endl; }
讓咱們當即來試試這個print函數的效果:
int main() { // 普通值 int sampleInt = 123; double *samplePtr = nullptr; string sampleStr = "abc"; print(sampleInt); // 123 print(samplePtr); // 0 print(sampleStr); // abc // 線性容器 array<int, 3> sampleArray {1, 2, 3}; vector<string> sampleVector {"abc", "def", "ghi"}; list<deque<forward_list<string>>> sampleComplexContainer {{{"abc", "def"}, {"ghi", "jkl"}}, {{"mno", "pqr"}, {"stu", "vwx"}}}; print(sampleArray); // [1, 2, 3] print(sampleVector); // [abc, def, ghi] /* [ [ [abc, def], [ghi, jkl], ], [ [mno, pqr], [stu, vwx], ], ] */ print(sampleComplexContainer); // Map容器 map<int, string> sampleMap {{1, "abc"}, {2, "def"}, {3, "ghi"}}; multimap<int, vector<string>> sampleComplexMap {{1, {"abc", "def"}}, {2, {"ghi", "jkl"}}, {3, {"mno", "pqu"}}}; /* { (1: abc), (2: def), (3: ghi), } */ print(sampleMap); /* { ( 1: [abc, def], ), ( 2: [ghi, jkl], ), ( 3: [mno, pqu], ), } */ print(sampleComplexMap); // Set容器 set<int> sampleSet {1, 2, 3}; multiset<vector<bool>> sampleComplexSet {{true, false}, {false, true}, {true, false, false, true}}; print(sampleSet); // {1, 2, 3} /* { [0, 1], [1, 0], [1, 0, 0, 1], } */ print(sampleComplexSet); // Pair pair<int, string> samplePair {1, "abc"}; pair<int, vector<string>> sampleComplexPair {1, {"abc", "def", "ghi"}}; print(samplePair); // (1, abc) /* ( 1, [abc, def, ghi], ) */ print(sampleComplexPair); // Tuple容器 tuple<int, double, char, string> sampleTuple {1, 2., 'a', "abc"}; tuple<int, double, char, string, vector<string>> sampleComplexTuple {1, 2., 'a', "abc", {"abc", "def", "ghi"}}; print(sampleTuple); // (1, 2, a, abc) /* ( 1, 2, a, abc, [abc, def, ghi], ) */ print(sampleComplexTuple); // Stack容器 stack<int> sampleStack; sampleStack.push(1); sampleStack.push(2); sampleStack.push(3); stack<vector<string>> sampleComplexStack; sampleComplexStack.push({"abc", "def"}); sampleComplexStack.push({"ghi", "jkl"}); sampleComplexStack.push({"mno", "pqr"}); /* 棧底 --------> 棧頂 [1, 2, 3] */ print(sampleStack); /* 棧底 [ | [abc, def], | [ghi, jkl], | [mno, pqr], | ] v 棧頂 */ print(sampleComplexStack); // Queue容器 queue<int> sampleQueue; sampleQueue.push(1); sampleQueue.push(2); sampleQueue.push(3); priority_queue<vector<string>> sampleComplexPriorityQueue; sampleComplexPriorityQueue.push({"abc", "def"}); sampleComplexPriorityQueue.push({"ghi", "jkl"}); sampleComplexPriorityQueue.push({"mno", "pqr"}); /* 隊列首 <-------- 隊列尾 [1, 2, 3] */ print(sampleQueue); /* 隊列首 [ ^ [mno, pqr], | [ghi, jkl], | [abc, def], | ] | 隊列尾 */ print(sampleComplexPriorityQueue); }
至此,print函數的實現也就所有完成了。
本章,咱們首先經過一個簡單的STL advance函數,討論了編譯期分派技術。這一函數的實現過程可以帶給咱們兩點思考:
緊接着,咱們實現了一個代碼更爲複雜的print函數。觀其輸出結果,不由讓咱們感慨:一個小小的T,在通過咱們的「大作文章」以後,竟可以表現出如此豐富的多樣性!這,就是編譯期分派的強大威力所在!
Tuple是一種很是特殊且高級的數據結構,其可以容納和取用數量、類型都不定的一組值,你也能夠將Tuple理解爲某種「匿名結構體」。乍看之下,「數量、類型都不定」和模板中「什麼都是已經肯定的編譯期常量」從語法上就是徹底相悖的,和容器的「全部元素的類型必須相同」的原則也是徹底相悖的,彷佛,Tuple是一種「突破極限」的容器。可事實真的是如此嗎?
首先,請看下面這段「平淡無奇」的代碼:
template <typename T1, typename T2> struct __RecursionPair { public: // 數據成員 T1 __first; T2 __second; // 構造函數 __RecursionPair(); __RecursionPair(const T1 &first, const T2 &second); }; template <typename T1, typename T2> __RecursionPair<T1, T2>::__RecursionPair() = default; template <typename T1, typename T2> __RecursionPair<T1, T2>::__RecursionPair(const T1 &first, const T2 &second): __first(first), __second(second) {} // 針對只有一個值的Pair的特化 template <typename T1> struct __RecursionPair<T1, void> { public: // 數據成員 T1 __first; // 構造函數 __RecursionPair(); explicit __RecursionPair(const T1 &first); }; template <typename T1> __RecursionPair<T1, void>::__RecursionPair() = default; template <typename T1> __RecursionPair<T1, void>::__RecursionPair(const T1 &first): __first(first) {} int main() { __RecursionPair<int, double> _(1, 2.); __RecursionPair<int, void> __(1); }
「這不就是STL的Pair嗎?」,你必定會有這樣的疑問。沒錯,這確實就是STL的Pair,但請你繼續看:
__RecursionPair<int, __RecursionPair<double, __RecursionPair<char, string>>> multiPair; // 還有這種操做???
沒錯!就是有這樣的操做。此時,也許你已經意識到了,只要「無限堆疊」這樣的Pair,理論上就能實現一個任意數量+任意類型的容器了。咱們稱這樣的Pair爲「可遞歸Pair」。
故事結束了嗎?不,這才只是個開始。咱們能夠當即發現,這種「無限堆疊」產生的「千層餅」,是一個很是反人類的東西,不只沒有「索引值」,甚至爲了取得第10個值,居然須要連着寫9遍「.second」!這也太反人類了!請不要着急,接着往下看。
接下來,咱們須要解決彷佛很棘手的一個問題:如何根據「索引值」獲取可遞歸Pair的某個位置的類型呢?要知道,可遞歸Pair裏面但是根本沒有「索引值」這一律唸啊。
讓咱們先試着邁出第一步:獲取__RecursionPair<T1, void>的T1。請看如下示例:
// 原型 // 經過typename __RecursionPairType<T, N>::__ValueType獲取「T[N]」的類型 template <int N, typename T> struct __RecursionPairType; // 獲取__RecursionPair<T1, void>的第0個類型(至關於「T[0]」的類型) template <typename T1> struct __RecursionPairType<0, __RecursionPair<T1, void>> { // __RecursionPair<T1, void>的第0個類型顯然就是T1 typedef T1 __ValueType; };
彷佛很順利對不對?讓咱們繼續:獲取__RecursionPair<T1, T2>的T1和T2。請看如下示例:
// 獲取__RecursionPair<T1, T2>的第0個類型(至關於「T[0]」的類型) template <typename T1, typename T2> struct __RecursionPairType<0, __RecursionPair<T1, T2>> { // __RecursionPair<T1, T2>的第0個類型顯然就是T1 typedef T1 __ValueType; }; // 獲取__RecursionPair<T1, T2>的第1個類型(至關於「T[1]」的類型) template <typename T1, typename T2> struct __RecursionPairType<1, __RecursionPair<T1, T2>> { // __RecursionPair<T1, T2>的第1個類型顯然就是T2 typedef T2 __ValueType; };
接下來,咱們就要面對真正的難題了,它就是:
__RecursionPair<T1, __RecursionPair<T2, T3>>
仔細分析這一類型不難發現:T1和T2必定不會繼續是一個__RecursionPair類型(由於咱們人爲地「默認」了可遞歸Pair只有second能夠進行遞歸,實際上first也能夠進行遞歸,可是這樣的代碼看上去比較「彆扭」)。因此,咱們當即能夠給出如下實現:
// 獲取__RecursionPair<T1, __RecursionPair<T2, T3>>的第0個類型(至關於「T[0]」的類型) template <typename T1, typename T2, typename T3> struct __RecursionPairType<0, __RecursionPair<T1, __RecursionPair<T2, T3>>> { // 由於T1必定不會繼續是一個__RecursionPair類型 // 因此__RecursionPair<T1, __RecursionPair<T2, T3>>的第0個類型應該就是T1 typedef T1 __ValueType; }; // 獲取__RecursionPair<T1, __RecursionPair<T2, T3>>的第1個類型(至關於「T[1]」的類型) template <typename T1, typename T2, typename T3> struct __RecursionPairType<1, __RecursionPair<T1, __RecursionPair<T2, T3>>> { // 由於T2必定不會繼續是一個__RecursionPair類型 // 因此__RecursionPair<T1, __RecursionPair<T2, T3>>的第1個類型應該就是T2 typedef T2 __ValueType; };
那麼,若是N大於1,要怎麼辦呢?此時,雖然咱們本身已經無能爲力(由於咱們並無能力「拆分」T3),可是咱們能夠「寄但願於」遞歸。請看如下示例:
// 獲取__RecursionPair<T1, __RecursionPair<T2, T3>>的第N(N > 1)個類型(至關於「T[N]」的類型) template <int N, typename T1, typename T2, typename T3> struct __RecursionPairType<N, __RecursionPair<T1, __RecursionPair<T2, T3>>> { // 若是N大於1,那麼「T[N]」的類型應該是__RecursionPair<T2, T3>的第N - 1個類型 typedef typename __RecursionPairType<N - 1, __RecursionPair<T2, T3>>::__ValueType __ValueType; };
至此,咱們就完整實現了根據「索引值」獲取可遞歸Pair的某個位置的類型這一工具。讓咱們來看看效果:
int main() { typedef __RecursionPair<int, __RecursionPair<double, __RecursionPair<char, string>>> Type; cout << typeid(__RecursionPairType<3, Type>::__ValueType).name(); // string }
能夠看出,輸出結果徹底符合咱們的預期。
看到這裏,也許你會以爲上述實現中的第4個和第5個特化(即__RecursionPairType<0, __RecursionPair<T1, __RecursionPair<T2, T3>>>和__RecursionPairType<1, __RecursionPair<T1, __RecursionPair<T2, T3>>>版本)彷佛是多餘的?你能夠去掉這些特化,而後編譯試試看。
有了上文中__RecursionPairType的鋪墊,根據「索引值」獲取可遞歸Pair的某個位置的值這一功能彷佛也能夠「依葫蘆畫瓢」進行實現了。一樣,讓咱們先邁出第一步:
// 原型 // 經過__RecursionPairValue<N, T>::__Get(pairObj)獲取「pairObj[N]」的值 template <int N, typename T> struct __RecursionPairValue; // 獲取__RecursionPair<T1, void>的「pairObj[0]」的值 template <typename T1> struct __RecursionPairValue<0, __RecursionPair<T1, void>> { // __Get函數的參數類型顯然就是__RecursionPair<T1, void> typedef __RecursionPair<T1, void> __PairType; // __Get函數的返回值類型顯然就是T1 typedef T1 __ValueType; // 實際的返回值顯然就是pairObj.__first static __ValueType &__Get(__PairType &pairObj) { return pairObj.__first; } static const __ValueType &__Get(const __PairType &pairObj) { return pairObj.__first; } };
讓咱們繼續。接下來實現__RecursionPair<T1, T2>的取值:
// 獲取__RecursionPair<T1, T2>的「pairObj[0]」的值 template <typename T1, typename T2> struct __RecursionPairValue<0, __RecursionPair<T1, T2>> { // __Get函數的參數類型顯然就是__RecursionPair<T1, T2> typedef __RecursionPair<T1, T2> __PairType; // __Get函數的返回值類型顯然就是T1 typedef T1 __ValueType; // 實際的返回值顯然就是pairObj.__first static __ValueType &__Get(__PairType &pairObj) { return pairObj.__first; } static const __ValueType &__Get(const __PairType &pairObj) { return pairObj.__first; } }; // 獲取__RecursionPair<T1, T2>的「pairObj[1]」的值 template <typename T1, typename T2> struct __RecursionPairValue<1, __RecursionPair<T1, T2>> { // __Get函數的參數類型顯然就是__RecursionPair<T1, T2> typedef __RecursionPair<T1, T2> __PairType; // __Get函數的返回值類型顯然就是T2 typedef T2 __ValueType; // 實際的返回值顯然就是pairObj.__second static __ValueType &__Get(__PairType &pairObj) { return pairObj.__second; } static const __ValueType &__Get(const __PairType &pairObj) { return pairObj.__second; } };
讓咱們繼續。接下來實現__RecursionPair<T1, __RecursionPair<T2, T3>>的取值:
// 獲取__RecursionPair<T1, __RecursionPair<T2, T3>>的「pairObj[0]」的值 template <typename T1, typename T2, typename T3> struct __RecursionPairValue<0, __RecursionPair<T1, __RecursionPair<T2, T3>>> { // __Get函數的參數類型顯然就是__RecursionPair<T1, __RecursionPair<T2, T3>> typedef __RecursionPair<T1, __RecursionPair<T2, T3>> __PairType; // __Get函數的返回值類型顯然就是T1 typedef T1 __ValueType; // 實際的返回值顯然就是pairObj.__first static __ValueType &__Get(__PairType &pairObj) { return pairObj.__first; } static const __ValueType &__Get(const __PairType &pairObj) { return pairObj.__first; } }; // 獲取__RecursionPair<T1, __RecursionPair<T2, T3>>的「pairObj[1]」的值 template <typename T1, typename T2, typename T3> struct __RecursionPairValue<1, __RecursionPair<T1, __RecursionPair<T2, T3>>> { // __Get函數的參數類型顯然就是__RecursionPair<T1, __RecursionPair<T2, T3>> typedef __RecursionPair<T1, __RecursionPair<T2, T3>> __PairType; // __Get函數的返回值類型顯然就是T2 typedef T2 __ValueType; // 實際的返回值顯然就是pairObj.__second.__first static __ValueType &__Get(__PairType &pairObj) { return pairObj.__second.__first; } static const __ValueType &__Get(const __PairType &pairObj) { return pairObj.__second.__first; } };
那麼,若是N大於1,要怎麼辦呢?咱們須要解決兩個問題:
第一個問題的解決方案不言而喻:咱們已經實現了能夠獲取到可遞歸Pair的任意位置的類型的工具,這固然能夠在這裏爲咱們所用;對於第二個問題,咱們一樣能夠基於遞歸,對pairObj.second進行「拆分」,直至N降至1爲止。請看如下示例:
// 獲取__RecursionPair<T1, __RecursionPair<T2, T3>>的「pairObj[N]」的值 template <int N, typename T1, typename T2, typename T3> struct __RecursionPairValue<N, __RecursionPair<T1, __RecursionPair<T2, T3>>> { // __Get函數的參數類型顯然就是__RecursionPair<T1, __RecursionPair<T2, T3>> typedef __RecursionPair<T1, __RecursionPair<T2, T3>> __PairType; // __Get函數的返回值類型須要依賴咱們前面已經實現的__RecursionPairType獲取 typedef typename __RecursionPairType<N, __PairType>::__ValueType __ValueType; // 咱們並無能力「拆分」pairObj.__second.__second,可是咱們能夠「寄但願於」遞歸 static __ValueType &__Get(__PairType &pairObj) { return __RecursionPairValue<N - 1, __RecursionPair<T2, T3>>::__Get(pairObj.__second); } // 同上 static const __ValueType &__Get(const __PairType &pairObj) { return __RecursionPairValue<N - 1, __RecursionPair<T2, T3>>::__Get(pairObj.__second); } };
至此,咱們就完整實現了根據「索引值」獲取可遞歸Pair的某個位置的值這一工具。讓咱們來看看效果:
int main() { __RecursionPair<int, __RecursionPair<double, __RecursionPair<char, string>>> testPair; __RecursionPairValue<3, decltype(testPair)>::__Get(testPair) = "abc"; cout << __RecursionPairValue<3, decltype(testPair)>::__Get(testPair); // abc }
一樣,若是你以爲上述實現中的第4個和第5個特化(即__RecursionPairValue<0, __RecursionPair<T1, __RecursionPair<T2, T3>>>和__RecursionPairValue<1, __RecursionPair<T1, __RecursionPair<T2, T3>>>版本)是多餘的,你能夠去掉這些特化,而後編譯試試看。
本節將會是整個Tuple的實現中最爲精彩的部分!
咱們雖然已經實現了針對可遞歸Pair的取類型和取值工具,但咱們仍是沒有實現出一個「扁平的」真正的Tuple(沒錯,終於又看到Tuple這個詞了)。接下來,咱們就開始着手考慮,如何將可遞歸Pair這張「千層餅」擀平,變成一張「單層餅」Tuple。
如何實現「擀平」這一操做呢?稍加思考不難發現,可遞歸Pair和Tuple之間彷佛存在着這樣的一一對應關係:
如何描述這種「是一個」的語義?哦!是繼承!
請看如下示例:
// 原型 // 經過Tuple<Types...>構造一個Tuple template <typename... Types> struct Tuple; // 含有一個元素的Tuple,就是一個__RecursionPair<T1, void> template <typename T1> struct Tuple<T1>: __RecursionPair<T1, void> { // 我是一個怎樣的__RecursionPair?固然是繼承的那個! typedef __RecursionPair<T1, void> __PairType; // 構造函數(待實現) Tuple(); Tuple(const T1 &first); }; // 含有兩個元素的Tuple,就是一個__RecursionPair<T1, T2> template <typename T1, typename T2> struct Tuple<T1, T2>: __RecursionPair<T1, T2> { // 我是一個怎樣的__RecursionPair?固然也是繼承的那個! typedef __RecursionPair<T1, T2> __PairType; // 構造函數(待實現) Tuple(); Tuple(const T1 &first, const T2 &second); }; // 默認構造函數 template <typename T1> Tuple<T1>::Tuple() = default; // 只須要調用Tuple的「可遞歸Pair形態」(即父類)的構造函數便可 template <typename T1> Tuple<T1>::Tuple(const T1 &first): __PairType(first) {} // 默認構造函數 template <typename T1, typename T2> Tuple<T1, T2>::Tuple() = default; // 一樣,只須要調用Tuple的「可遞歸Pair形態」(即父類)的構造函數便可 template <typename T1, typename T2> Tuple<T1, T2>::Tuple(const T1 &first, const T2 &second): __PairType(first, second) {}
那麼,含有不止兩個元素的Tuple,是哪一個可遞歸Pair呢?若是你已經注意到了上面的兩個Tuple實現中的「看似無用」的typedef,那麼問題就能迎刃而解。這些typedef,保存了當前Tuple所對應的「可遞歸Pair形態」。從可遞歸Pair的角度去思考,不難找到如下規律:
找到了這一規律,代碼實現也就垂手可得了。請看如下示例:
// Tuple<T1, Types...>的「可遞歸Pair形態」是:將T1,與typename Tuple<Types...>::__PairType //(即Tuple<Types...>的「可遞歸Pair形態」)放入一個__RecursionPair中 template <typename T1, typename... Types> struct Tuple<T1, Types...>: __RecursionPair<T1, typename Tuple<Types...>::__PairType> { // 我是一個怎樣的__RecursionPair?一樣也是繼承的那個! typedef __RecursionPair<T1, typename Tuple<Types...>::__PairType> __PairType; // 構造函數(待實現) Tuple(); Tuple(const T1 &first, const Types &... Args); };
那麼,這樣的一個含有多個元素的Tuple,其構造函數又該如何實現呢?經過上文的討論,咱們不難發現:無論是什麼樣的Tuple(從只含有一個元素的Tuple到含有不少個元素的Tuple),其父類都是一個可遞歸Pair,而可遞歸Pair也是Pair,其構造函數永遠只須要兩個值(無論是多麼複雜的可遞歸Pair)。因此,咱們仍然能夠經過直接調用父類的構造函數來對任意的Tuple進行構造。咱們須要哪兩個值來調用複雜的可遞歸Pair的構造函數呢?讓咱們繼續進行「找規律」:
咱們再一次經過找規律的方法獲得告終論!接下來就能夠進行代碼實現了。請看如下示例:
// 默認構造函數 template <typename T1, typename... Types> Tuple<T1, Types...>::Tuple() = default; // 只須要調用Tuple的「可遞歸Pair形態」(即父類)的構造函數便可 // 構造__PairType的兩個參數分別來自於first與構造Tuple<Types...>(Args...)所獲得的一個__RecursionPair template <typename T1, typename... Types> Tuple<T1, Types...>::Tuple(const T1 &first, const Types &... Args): __PairType(first, Tuple<Types...>(Args...)) {}
至此,Tuple的實現中最重要的部分:Tuple的構造函數,也就所有實現完畢了。讓咱們當即來試用一下。請看如下示例:
int main() { Tuple<int, double, char, string> sampleTuple(1, 2., '3', "4"); cout << __RecursionPairValue<0, decltype(sampleTuple)::__PairType>::__Get(sampleTuple) << endl; // 1 cout << __RecursionPairValue<1, decltype(sampleTuple)::__PairType>::__Get(sampleTuple) << endl; // 2 cout << __RecursionPairValue<2, decltype(sampleTuple)::__PairType>::__Get(sampleTuple) << endl; // 3 cout << __RecursionPairValue<3, decltype(sampleTuple)::__PairType>::__Get(sampleTuple) << endl; // 4 }
因而可知,Tuple的構造函數工做正常(雖然咱們暫時還只能經過「可遞歸Pair時代」的工具獲取到其內部的值)。
最後,就是一些簡單的周邊功能的實現了。
首先是MakeTuple快捷函數,此函數只須要使用一個可變參數模板封裝Tuple的構造函數便可。請看如下示例:
template <typename... Types> inline Tuple<Types...> MakeTuple(const Types &... Args) { return Tuple<Types...>(Args...); } int main() { auto sampleTuple = MakeTuple(1, 2., '3'); }
而後是根據「索引值」獲取Tuple的某個位置的類型的類,實現時只須要將所有操做直接委託給咱們已經實現的__RecursionPairType便可。請看如下示例:
// 原型 // 經過typename TupleType<N, T>::Type獲取「T[N]」的類型 template <int N, typename T> struct TupleType; // 僅當T是一個Tuple時,此類纔有意義 template <int N, typename... Types> struct TupleType<N, Tuple<Types...>> { // 使用__RecursionPairType做用於Tuple<Types...>的「可遞歸Pair形態」上,就能獲取「Tuple<Types...>[N]」的類型 typedef typename __RecursionPairType<N, typename Tuple<Types...>::__PairType>::__ValueType Type; }; int main() { Tuple<int, double, char, string> sampleTuple(1, 2., '3', "4"); cout << typeid(TupleType<0, decltype(sampleTuple)>::Type).name() << endl; // int cout << typeid(TupleType<1, decltype(sampleTuple)>::Type).name() << endl; // double cout << typeid(TupleType<2, decltype(sampleTuple)>::Type).name() << endl; // char cout << typeid(TupleType<3, decltype(sampleTuple)>::Type).name() << endl; // string }
而後是根據「索引值」獲取Tuple的某個位置的值的函數,實現時只須要將所有操做直接委託給咱們已經實現的TupleType以及__RecursionPairValue便可。請看如下示例:
// 函數的返回值就是typename TupleType<N, Tuple<Types...>>::Type template <int N, typename... Types> inline typename TupleType<N, Tuple<Types...>>::Type &Get(Tuple<Types...> &tupleObj) { // 使用__RecursionPairValue做用於Tuple<Types...>的「可遞歸Pair形態」上,就能獲取「tupleObj[N]」的值 return __RecursionPairValue<N, typename Tuple<Types...>::__PairType>::__Get(tupleObj); } // 同上 template <int N, typename... Types> inline const typename TupleType<N, Tuple<Types...>>::Type &Get(const Tuple<Types...> &tupleObj) { return __RecursionPairValue<N, typename Tuple<Types...>::__PairType>::__Get(tupleObj); } int main() { Tuple<int, double, char, string> sampleTuple(1, 2., '3', "4"); cout << Get<0>(sampleTuple) << endl; // 1 cout << Get<1>(sampleTuple) << endl; // 2 cout << Get<2>(sampleTuple) << endl; // 3 cout << Get<3>(sampleTuple) << endl; // 4 }
最後是獲取Tuple的長度的類,直接使用sizeof...(Types)便可。請看如下示例:
// 原型 template <typename T> struct TupleSize; // 僅當T是一個Tuple時,此類纔有意義 template <typename... Types> struct TupleSize<Tuple<Types...>> { // Tuple的長度顯然就是Tuple的可變模板參數的數量 static constexpr int Size = sizeof...(Types); }; int main() { cout << TupleSize<Tuple<int, double, char, string>>::Size << endl; // 4 }
至此,Tuple的實現也就所有完成了。
Tuple,做爲一個看起來已然「突破極限」的高級容器,其背後的核心居然只是一個「平淡無奇」的Pair,這不得不使人驚訝於基於模板的高階抽象的威力。在Tuple的實現過程當中,咱們充分利用了模板偏特化,用以描繪出各類不一樣「形態」的可遞歸Pair;咱們也使用了繼承,用以描繪出Tuple與可遞歸Pair的一一對應關係。在這裏,模板與繼承,這兩個「不一樣世界的產物」,被巧妙的結合在了一塊兒,最終爲咱們帶來了一場十分精彩的二重奏!
表達式模板?什麼?你沒據說過?那就對了!經過本章的討論,你就會了解到:模板是如何在用戶無感知的前提下,將高性能計算引入咱們的程序中的。
讓咱們從一個看似很簡單的問題開始:
如何實現向量的加法運算?
若是使用STL的array表示向量,不難作出如下實現:
template <typename T, size_t N> array<T, N> operator+(const array<T, N> &lhs, const array<T, N> &rhs) { array<T, N> resArray; for (int idx = 0; idx < N; idx++) resArray[idx] = lhs[idx] + rhs[idx]; return resArray; } int main() { array<int, 3> lhs {1, 2, 3}, rhs {4, 5, 6}, res; res = lhs + rhs; for (auto val: res) cout << val << endl; // 5 7 9 }
這個實現有什麼問題呢?請看如下示例:
lhs + rhs + lhs + rhs + lhs + rhs + lhs + rhs + lhs + rhs; // 哦!大量的冗餘計算!
在上面這個「10連加」表達式中,operator+函數一共被調用了9次,這也就意味着:函數體內的resArray臨時變量被建立、return了9次(假設沒有NRV優化),for循環也被執行了9次,而這還只是一次「10連加」所形成的結果。可想而知,在計算量變得愈來愈大時,這是多麼大的時間耗費!此時,咱們不難想到,上述的「10連加」表達式實際上可以被優化爲以下實現:
// 實際上只須要一次函數調用 template <typename T, size_t N> array<T, N> operator+(const array<T, N> &lhs, const array<T, N> &rhs) { array<T, N> resArray; // 實際上也只須要一次循環 for (int idx = 0; idx < N; idx++) { // 只須要在循環體內執行「10連加」便可 resArray[idx] = lhs[idx] + rhs[idx] + lhs[idx] + rhs[idx] + lhs[idx] + rhs[idx] + lhs[idx] + rhs[idx] + lhs[idx] + rhs[idx]; } return resArray; }
可問題是,編譯器就算有能力優化成這樣的實現,其也不能優化成這樣。這是因爲C++的表達式語義本就是「積極主動的」,當編譯器看到lhs + rhs...時,其就必須遵照C++的語義規定,當即計算此加法,而「暫且不顧」後續表達式。
看來,編譯器優化是完全不可能幫得上忙了。這讓咱們陷入了困境之中。
既然編譯器幫不上忙,那咱們是否能經過某種技術,「繞過」編譯器的這種主動計算呢?若是你的想象力足夠豐富,也許你會有這樣的想法:
可否將表達式看做某種「字符串」,這樣,加法就至關於「字符串的拼接」呢?而當咱們真的須要表達式的結果時,咱們能夠實現一個對「表達式字符串」進行求值的函數來進行求值。
這是一個天馬行空的想法,但基於模板,這個想法是真的能夠實現的!這就是本章將要討論的表達式模板技術。
首先,讓咱們實現一個Array類,用於存放一個向量。請看如下示例:
template <typename T, int N> class __Array { public: // 構造函數 __Array(); explicit __Array(const T &val); __Array(initializer_list<T> initializerList); // operator[] T &operator[](int idx) { return __data[idx]; } const T &operator[](int idx) const { return __data[idx]; } private: // 一個C語言數組,用於存放向量 T __data[N]; }; template <typename T, int N> __Array<T, N>::__Array() = default; template <typename T, int N> __Array<T, N>::__Array(const T &val) { for (int idx = 0; idx < N; idx++) { __data[idx] = val; } } template <typename T, int N> __Array<T, N>::__Array(initializer_list<T> initializerList) { int idx = 0; for (auto &val: initializerList) { __data[idx++] = val; } } int main() { __Array<int, 3> lhs {1, 2, 3}; for (int idx = 0; idx < 3; idx++) cout << lhs[idx] << endl; // 1 2 3 }
咱們爲這個Array實現了默認構造函數,Fill構造函數,initializer_list構造函數,以及operator[]重載。看上去平淡無奇,不是嗎?
接下來,咱們就來實現上文中的「表達式字符串」(固然,咱們不是真的去實現一個特殊的字符串)。一個「表達式字符串」,如「lhs + rhs」,是由哪幾部分組成的呢?顯然,其是由lhs、「+」以及rhs組成,其中,lhs與rhs表明的是某個值,而「+」表明的是一個動做。若是咱們使用兩個變量分別存放lhs與rhs,並使用一個函數表達「+」這一動做,咱們就可以實現出一個「表達式字符串」了。而將這些內容封裝進一個模板中,咱們也就獲得了一個「表達式模板」。請看如下示例:
// 加法表達式模板 template <typename T, typename LExpr, typename RExpr> class __Plus { public: // 構造函數 __Plus(const LExpr &lhs, const RExpr &rhs); // 當對這個表達式模板進行[...]運算的時候,就能獲得這個表達式模板在某個「索引值」位置上的加法計算的結果 // 也就是說,表達式模板也是某種從外觀上看和向量別無二致的東西 T operator[](int idx) const { return __lhs[idx] + __rhs[idx]; } private: // 用於保存LExpr與RExpr的引用的數據成員 const LExpr &__lhs; const RExpr &__rhs; }; template <typename T, typename LExpr, typename RExpr> __Plus<T, LExpr, RExpr>::__Plus(const LExpr &lhs, const RExpr &rhs): __lhs(lhs), __rhs(rhs) {}
__Plus的模板參數包含加法的返回值類型T,以及左右值類型LExpr和RExpr;在__Plus中,咱們聲明瞭兩個分別指向LExpr和RExpr的引用;而在構造函數中,lhs、rhs被分別綁定至類中的兩個引用上,此時,咱們並無執行任何加法運算。那麼,何時才執行加法運算呢?從代碼中不難看出,直到operator[]時,纔會真正計算加法。
這是一種利用模板實現的「惰性計算」技術,當加法語義出現時,咱們並無真的執行加法,而只是執行了一次成本很低的「記錄」操做,咱們記錄了執行一次加法所須要的所有信息:左值、加這個動做、以及右值。僅當真正須要加法的結果時,__Plus纔會「在咱們強硬的驅使下」計算加法。而且,就算是在這種「強硬的驅使下」,__Plus每次也只會計算一個位置的加法。這就使得__Plus可以最大程度的規避無心義的加法計算(設想咱們進行了一次十萬維向量的加法,但咱們只須要知道第五萬維這一個位置的加法結果)。
此外,__Plus在設計上刻意的模仿了__Array的操做,這就使得__Plus也可以像一個__Array那樣具備「索引值」。這樣作的意義是什麼呢?僅僅是爲了方便、美觀嗎?咱們將在下一節中揭曉答案。
接下來,讓咱們試着使用一下__Plus類,體驗一下這種「惰性計算」技術。請看如下示例:
int main() { __Array<int, 3> lhs {1, 2, 3}, rhs {4, 5, 6}; // 保存了lhs + rhs這個表達式,但不對其進行計算 __Plus<int, __Array<int, 3>, __Array<int, 3>> res(lhs, rhs); for (int idx = 0; idx < 3; idx++) { // 這裏才計算加法 cout << res[idx] << endl; } }
看到這裏,也許你會恍然大悟:「哦!這個__Plus和上一章的可遞歸Pair同樣,也是能夠遞歸的!」請看如下示例:
int main() { __Array<int, 3> lhs {1, 2, 3}, rhs {4, 5, 6}; // 保存了lhs + rhs + lhs這個表達式,但不對其進行計算 // 但是這也太反人類了吧! __Plus<int, __Array<int, 3>, __Array<int, 3>> tmp(lhs, rhs); __Plus<int, __Plus<int, __Array<int, 3>, __Array<int, 3>>, __Array<int, 3>> res(tmp, lhs); for (int idx = 0; idx < 3; idx++) { // 這裏才計算加法 cout << res[idx] << endl; } }
咱們經過整整兩行的「超長」代碼,「終於」實現了lhs + rhs + lhs的惰性加法。顯然,這樣的實現是很是「反人類」的,有什麼辦法能對其進行簡化,甚至讓用戶無感知呢?稍加思索就可以發現,只要使用運算符重載,咱們就能把全部這些都隱藏於幕後,只留下lhs + rhs + lhs自己。請看如下示例:
template </* 這裏寫什麼?*/> /* 這裏寫什麼?*/ operator+(const /* 這裏寫什麼?*/ &lhs, const /* 這裏寫什麼?*/ &rhs) { return /* 這裏寫什麼?*/; } int main() { /* 這裏寫什麼?*/ lhs {1, 2, 3}, rhs {4, 5, 6}, res; // 最終的效果,太棒了! res = lhs + rhs + lhs; for (int idx = 0; idx < 3; idx++) { cout << res[idx] << endl; } }
對於operator+,其須要同時知足「__Array + __Array」、「__Array + __Plus」、「__Plus + __Array」、「__Plus + __Plus」等等的「排列組合」(而且,請不要忘了:除了「加」,還有「減乘除」呢!)。這就使得咱們難以肯定lhs與rhs的類型。難道真的要爲每種狀況都寫一個operator+重載嗎?請接着往下看。
如何規避這種「排列組合」呢?讓咱們開拓一下思惟,不難發現:單獨的一個__Array是一個表達式,而__Plus(任意兩個表達式相加的結果)也是一個表達式,而且他們的共性即在於,都是能夠基於operator[]進行表達式求值的。至此,解決方案水落石出:咱們能夠在__Array和__Plus之上再增長一個抽象層,表達「表達式」語義,而__Array和__Plus在此抽象層中並沒有區別,都是一個能夠進行operator[]運算的「表達式」。此時你應該可以明白:爲何__Plus要「刻意」模仿__Array的operator[]了。請看如下示例:
template <typename T, int N, typename Expr> class __Expression { public: // 適用於__Array的構造函數 __Expression(); explicit __Expression(const T &val); __Expression(initializer_list<T> initializerList); // 適用於__Plus的構造函數 __Expression(const Expr &expr); // operator[]直接委託給__expr執行 T &operator[](int idx) { return __expr[idx]; } T operator[](int idx) const { return __expr[idx]; } // operator= template <typename RExpr> __Expression &operator=(const __Expression<T, N, RExpr> &rhs); private: // __expr多是一個__Array,也多是一個__Plus Expr __expr; }; template <typename T, int N, typename Expr> __Expression<T, N, Expr>::__Expression() = default; template <typename T, int N, typename Expr> __Expression<T, N, Expr>::__Expression(const T &val): __expr(val) {} template <typename T, int N, typename Expr> __Expression<T, N, Expr>::__Expression(initializer_list<T> initializerList): __expr(initializerList) {} template <typename T, int N, typename Expr> __Expression<T, N, Expr>::__Expression(const Expr &expr): __expr(expr) {} // operator=直接委託給__expr執行 // 直到operator=發生時,rhs纔會真正被計算 template <typename T, int N, typename Expr> template <typename RhsExpr> __Expression<T, N, Expr> &__Expression<T, N, Expr>::operator=( const __Expression<T, N, RhsExpr> &rhs) { for (int idx = 0; idx < N; idx++) { // 計算rhs[idx]的值,並賦值給左值 __expr[idx] = rhs[idx]; } return *this; }
讓咱們來分析這一實現:既然咱們須要將__Array和__Plus都抽象爲一個表達式,那麼咱們就能夠增長一個模板參數Expr,用以標明這個__Expression究竟是什麼(是__Array仍是__Plus)。因爲Expr既能夠是__Array又能夠是__Plus,咱們就須要實現多個構造函數,使得這兩種類型的值均可以在__Expression中構造。因此,咱們實現了三個和__Array的三個構造函數功能一致的構造函數,以及能夠使用一個Expr做爲參數的構造函數。而後,咱們將operator[]和operator=都直接委託給__expr執行。
顯然,當用戶在使用的時候,__Expression的Expr模板參數必須是__Array,因此咱們能夠聲明一個固定了Expr模板參數的模板,做爲面向用戶的Array接口。請看如下示例:
// 最終面向用戶的Array接口 template <typename T, int N> using Array = __Expression<T, N, __Array<T, N>>;
同時,做爲實現者,咱們也能夠創造一些更復雜的Expr模板參數。因此,就讓咱們來實現上一節中未能實現的operator+吧。請看如下示例:
// __Expression<T, N, LExpr> + __Expression<T, N, RExpr>的結果是一個新的__Expression // 其第1、二模板參數不變,第三模板參數是LExpr + RExpr的結果,即__Plus<T, LExpr, RExpr> template <typename T, int N, typename LExpr, typename RExpr> inline __Expression<T, N, __Plus<T, LExpr, RExpr>> operator+( const __Expression<T, N, LExpr> &lhs, const __Expression<T, N, RExpr> &rhs) { // 用lhs與rhs的__expr,構造出一個__Plus // 再用這個__Plus,構造出一個新的__Expression(使用的是__Expression的第四構造函數) return __Expression<T, N, __Plus<T, LExpr, RExpr>>( __Plus<T, LExpr, RExpr>(lhs.__expr, rhs.__expr)); }
看上去很複雜的樣子?讓咱們來分析一下。首先,因爲咱們並不知道lhs和rhs的Expr分別是什麼(兩者均可能是__Array,若是這是一個「新的」Array;或__Plus,若是這已是一個表達式),因此咱們須要兩個模板參數LExpr與RExpr,以分別表明lhs和rhs的Expr類型;但同時咱們知道,只有相同類型的Array之間能夠進行運算,因此咱們只須要一套T與N便可。因此,兩個形參分別是const Array<T, N, LExpr> &lhs與const Array<T, N, RExpr> &rhs。
返回值是什麼呢?不難發現,當__Expression<T, N, LExpr>與__Expression<T, N, RExpr>相加後,結果仍然是一個新的__Expression,而真正須要相加的實際上是LExpr與RExpr,且相加的結果是__Plus<T, LExpr, RExpr>(事實上,相加的結果也能夠就是__Plus<T, __Expression<T, N, LExpr>, __Expression<T, N, RExpr>>,你必定不難想出其中緣由。但很明顯,這樣的代碼也實在是太長,太反人類了),故返回值的類型就是一個Expr模板參數爲__Plus<T, LExpr, RExpr>的__Expression,即__Expression<T, N, __Plus<T, LExpr, RExpr>>,而實際的返回值須要先使用lhs與rhs的__expr,構造出一個__Plus,再用這個__Plus,構造出一個新的__Expression。這裏使用的是__Expression的第四構造函數。
如今,讓咱們試着使用一下咱們剛剛實現的Array(請注意:若是你如今就實際編譯如下這段代碼,請去除__Expression的__expr的private限定,或爲operator+函數進行友元受權)。請看如下示例:
int main() { // lhs,rhs,res的實際類型都是__Expression<int, 3, __Array<int, 3>> Array<int, 3> lhs {1, 2, 3}, rhs {4, 5, 6}, res; // 「什麼都不作」(不會進行實際的運算) lhs + rhs + lhs; // lhs + rhs...的類型是__Expression<int, 3, __Plus<__Array<int, 3>, __Array<int, 3>>> // ... + lhs的類型是__Expression<int, 3, __Plus<__Plus<__Array<int, 3>, __Array<int, 3>>, __Array<int, 3>>> // 直到operator=發生時,纔會進行實際的運算 res = lhs + rhs + lhs; for (int idx = 0; idx < 3; idx++) { // 此時,res[idx]就是一次指針運算 cout << res[idx] << endl; } }
觀察上述代碼不難發現,咱們僅僅才作了兩次加法,__Expression的類型就已經「長的無法看」了。可是事實上,咱們根本就不用關心__Expression的類型到底是什麼,而只須要牢記如下兩點便可:
因而可知,__Expression一方面爲用戶提供了對於表達式模板的存在無感知的Array類,另外一方面又絲絕不丟失其做爲表達式模板的功能,實在是一個優秀的「雙管齊下」類;另外一方面,因爲咱們的__Array與__Plus均實現了統一的operator[]接口,這就使得__Expression可以「自適應地」最終實現對其自身的求值。以上種種,都可以爲咱們展示出「抽象」這一思想的精彩之處。
在數學中,一個向量不只能夠和另外一個向量相加,還能夠和一個標量(即一個T類型的值)相加。本節咱們就來實現這一功能。
如何讓標量也加入咱們的「__Expression你們族」中呢?沒錯,關鍵就在於咱們在上幾節已經「老生常談」的operator[]。雖然標量根本就沒有operator[]這一律念,咱們也能夠「強行的」爲其添加這一律念,以使其適配__Expression的須要。請看如下示例:
// 爲標量提供的封裝類,從而使得標量也可以適配__Expression template <typename T> class __Scalar { public: // 構造函數 __Scalar(T val); // 強行爲標量提供一個「莫名其妙的」operator[] // 無論索引值是多少(事實上咱們根本就無視了這個索引值),都返回val T operator[](int) const { return __val; } private: // 使用一個T類型的數據成員存放構造函數中的val T __val; }; template <typename T> __Scalar<T>::__Scalar(T val): __val(val) {}
此時,__Expression的Expr模板參數就不只能夠是__Array或__Plus,還能夠是__Scalar了。
讓咱們繼續,實現適用於__Expression與__Scalar之間的加法的運算符重載。請看如下示例:
// __Expression<T, N, LExpr> + T // 其結果爲__Plus<T, LExpr, __Scalar<T>> template <typename T, int N, typename LExpr> inline __Expression<T, N, __Plus<T, LExpr, __Scalar<T>>> operator+( const __Expression<T, N, LExpr> &lhs, const T &rhs) { // 先使用rhs構造出一個__Scalar<T> // 再使用lhs的__expr和__Scalar<T>構造出一個__Plus // 最後使用一個__Plus構造出一個新的__Expression return __Expression<T, N, __Plus<T, LExpr, __Scalar<T>>>( __Plus<T, LExpr, __Scalar<T>>(lhs.__expr, __Scalar<T>(rhs))); } // T + __Expression<T, N, RExpr> // 其結果爲:__Plus<T, __Scalar<T>, RExpr> template <typename T, int N, typename RExpr> inline __Expression<T, N, __Plus<T, __Scalar<T>, RExpr>> operator+( const T &lhs, const __Expression<T, N, RExpr> &rhs) { // 先使用lhs構造出一個__Scalar<T> // 再使用__Scalar<T>和rhs的__expr構造出一個__Plus // 最後使用一個__Plus構造出一個新的__Expression return __Expression<T, N, __Plus<T, __Scalar<T>, RExpr>>( __Plus<T, __Scalar<T>, RExpr>(__Scalar<T>(lhs), rhs.__expr)); }
在咱們試用這一功能前,其實還有一件事是沒有完成的。請設想:當咱們寫下「lhs + 1」這一表達式時,這裏的「1」顯然是一個臨時量,而若是使用咱們如今所實現的__Plus,那麼這個臨時量「1」將被存入一個引用中。這將當即致使「懸掛引用」的發生!因此,咱們須要實現一個簡單的Traits類,在面對一個__Scalar時,將__Plus中的數據成員類型,從引用類型自動切換至值類型。這一Traits的實現很是簡單,請看如下示例:
// 無論T是是什麼,都萃取出一個const T &類型... template <typename T> struct __ScalarTypeTraits { typedef const T &__Type; }; // ...可是,若是T是一個__Scalar<T>類型,則萃取出一個__Scalar<T>類型 template <typename T> struct __ScalarTypeTraits<__Scalar<T>> { typedef __Scalar<T> __Type; };
有了這個Traits,咱們就能夠使用這個Traits改進咱們的__Plus類了。請看如下示例:
template <typename T, typename LExpr, typename RExpr> class __Plus { // ... private: // 原實現: // const LExpr &__lhs; // const RExpr &__rhs; // 改進後的實現: // 在LExpr(或RExpr)爲__Scalar<LExpr>(或__Scalar<RExpr>)時 // __Type將從引用類型自動切換至值類型 typename __ScalarTypeTraits<LExpr>::__Type __lhs; typename __ScalarTypeTraits<RExpr>::__Type __rhs; };
至此,咱們就能夠將標量也加入到表達式模板中了(請注意:若是你如今就實際編譯如下這段代碼,請去除__Expression的__expr的private限定,或爲operator+函數進行友元受權)。請看如下示例:
int main() { Array<int, 3> lhs {1, 2, 3}, rhs {4, 5, 6}, res; // 加入標量 res = lhs + rhs + lhs + 1; // 7 10 13 for (int idx = 0; idx < 3; idx++) { cout << res[idx] << endl; } }
本節中,咱們經過一個對標量的簡單的封裝類,使得標量也可以加入到表達式模板中;同時,爲了不標量臨時量所引起的「懸掛引用」問題,咱們又實現了一個簡單的Traits類,用於在面對標量時自動將表達式模板中的引用類型切換爲值類型。
至此,表達式模板的所有技術就都討論完畢了。下一節,咱們將最終給出表達式模板的完整實現。
因爲本章的代碼較爲分散,且咱們仍有不少重複性的代碼沒有於上文中給出。故本節中,咱們將給出表達式模板的完整實現。主要包含如下幾點新增內容:
請看如下示例:
// 「夢開始的地方」:__Array類 template <typename T, int N> class __Array { public: // 構造函數 __Array(); explicit __Array(const T &val); __Array(initializer_list<T> initializerList); // operator[] T &operator[](int idx) { return __data[idx]; } const T &operator[](int idx) const { return __data[idx]; } private: // 數據成員 T __data[N]; }; template <typename T, int N> __Array<T, N>::__Array() = default; template <typename T, int N> __Array<T, N>::__Array(const T &val) { for (int idx = 0; idx < N; idx++) { __data[idx] = val; } } template <typename T, int N> __Array<T, N>::__Array(initializer_list<T> initializerList) { int idx = 0; for (auto &val: initializerList) { __data[idx++] = val; } } // 標量適配器 template <typename T> class __Scalar { public: // 構造函數 __Scalar(T val); // operator[] T operator[](int) const { return __val; } private: // 數據成員 T __val; }; template <typename T> __Scalar<T>::__Scalar(T val): __val(val) {} // 標量值類型萃取器 template <typename T> struct __ScalarTypeTraits { typedef const T &__Type; }; template <typename T> struct __ScalarTypeTraits<__Scalar<T>> { typedef __Scalar<T> __Type; }; // 加法表達式模板 template <typename T, typename LExpr, typename RExpr> class __Plus { public: // 構造函數 __Plus(const LExpr &lhs, const RExpr &rhs); // operator[] T operator[](int idx) const { return __lhs[idx] + __rhs[idx]; } private: // 數據成員 typename __ScalarTypeTraits<LExpr>::__Type __lhs; typename __ScalarTypeTraits<RExpr>::__Type __rhs; }; // 減法表達式模板 template <typename T, typename LExpr, typename RExpr> class __Minus { public: // 構造函數 __Minus(const LExpr &lhs, const RExpr &rhs); // operator[] T operator[](int idx) const { return __lhs[idx] - __rhs[idx]; } private: // 數據成員 typename __ScalarTypeTraits<LExpr>::__Type __lhs; typename __ScalarTypeTraits<RExpr>::__Type __rhs; }; // 乘法表達式模板 template <typename T, typename LExpr, typename RExpr> class __Multiplies { public: // 構造函數 __Multiplies(const LExpr &lhs, const RExpr &rhs); // operator[] T operator[](int idx) const { return __lhs[idx] * __rhs[idx]; } private: // 數據成員 typename __ScalarTypeTraits<LExpr>::__Type __lhs; typename __ScalarTypeTraits<RExpr>::__Type __rhs; }; // 除法表達式模板 template <typename T, typename LExpr, typename RExpr> class __Divides { public: // 構造函數 __Divides(const LExpr &lhs, const RExpr &rhs); // operator[] T operator[](int idx) const { return __lhs[idx] / __rhs[idx]; } private: // 數據成員 typename __ScalarTypeTraits<LExpr>::__Type __lhs; typename __ScalarTypeTraits<RExpr>::__Type __rhs; }; // 取模表達式模板 template <typename T, typename LExpr, typename RExpr> class __Modulus { public: // 構造函數 __Modulus(const LExpr &lhs, const RExpr &rhs); // operator[] T operator[](int idx) const { return __lhs[idx] % __rhs[idx]; } private: // 數據成員 typename __ScalarTypeTraits<LExpr>::__Type __lhs; typename __ScalarTypeTraits<RExpr>::__Type __rhs; }; template <typename T, typename LExpr, typename RExpr> __Plus<T, LExpr, RExpr>::__Plus(const LExpr &lhs, const RExpr &rhs): __lhs(lhs), __rhs(rhs) {} template <typename T, typename LExpr, typename RExpr> __Minus<T, LExpr, RExpr>::__Minus(const LExpr &lhs, const RExpr &rhs): __lhs(lhs), __rhs(rhs) {} template <typename T, typename LExpr, typename RExpr> __Multiplies<T, LExpr, RExpr>::__Multiplies(const LExpr &lhs, const RExpr &rhs): __lhs(lhs), __rhs(rhs) {} template <typename T, typename LExpr, typename RExpr> __Divides<T, LExpr, RExpr>::__Divides(const LExpr &lhs, const RExpr &rhs): __lhs(lhs), __rhs(rhs) {} template <typename T, typename LExpr, typename RExpr> __Modulus<T, LExpr, RExpr>::__Modulus(const LExpr &lhs, const RExpr &rhs): __lhs(lhs), __rhs(rhs) {} // __Expression表達式類 template <typename T, int N, typename Expr> class __Expression { public: // 構造函數 __Expression(); explicit __Expression(const T &val); __Expression(initializer_list<T> initializerList); __Expression(const Expr &expr); // operator[] T &operator[](int idx) { return __expr[idx]; } T operator[](int idx) const { return __expr[idx]; } // operator= template <typename RExpr> __Expression &operator=(const __Expression<T, N, RExpr> &rhs); private: // 數據成員 Expr __expr; // 如下均爲友元受權 // operator+ template <typename T_, int N_, typename LExpr, typename RExpr> friend inline __Expression<T_, N_, __Plus<T_, LExpr, RExpr>> operator+( const __Expression<T_, N_, LExpr> &lhs, const __Expression<T_, N_, RExpr> &rhs); template <typename T_, int N_, typename LExpr> friend inline __Expression<T_, N_, __Plus<T_, LExpr, __Scalar<T_>>> operator+( const __Expression<T_, N_, LExpr> &lhs, const T_ &rhs); template <typename T_, int N_, typename RExpr> friend inline __Expression<T_, N_, __Plus<T_, __Scalar<T_>, RExpr>> operator+( const T_ &lhs, const __Expression<T_, N_, RExpr> &rhs); // operator- template <typename T_, int N_, typename LExpr, typename RExpr> friend inline __Expression<T_, N_, __Minus<T_, LExpr, RExpr>> operator-( const __Expression<T_, N_, LExpr> &lhs, const __Expression<T_, N_, RExpr> &rhs); template <typename T_, int N_, typename LExpr> friend inline __Expression<T_, N_, __Minus<T_, LExpr, __Scalar<T_>>> operator-( const __Expression<T_, N_, LExpr> &lhs, const T_ &rhs); template <typename T_, int N_, typename RExpr> friend inline __Expression<T_, N_, __Minus<T_, __Scalar<T_>, RExpr>> operator-( const T_ &lhs, const __Expression<T_, N_, RExpr> &rhs); // operator* template <typename T_, int N_, typename LExpr, typename RExpr> friend inline __Expression<T_, N_, __Multiplies<T_, LExpr, RExpr>> operator*( const __Expression<T_, N_, LExpr> &lhs, const __Expression<T_, N_, RExpr> &rhs); template <typename T_, int N_, typename LExpr> friend inline __Expression<T_, N_, __Multiplies<T_, LExpr, __Scalar<T_>>> operator*( const __Expression<T_, N_, LExpr> &lhs, const T_ &rhs); template <typename T_, int N_, typename RExpr> friend inline __Expression<T_, N_, __Multiplies<T_, __Scalar<T_>, RExpr>> operator*( const T_ &lhs, const __Expression<T_, N_, RExpr> &rhs); // operator/ template <typename T_, int N_, typename LExpr, typename RExpr> friend inline __Expression<T_, N_, __Divides<T_, LExpr, RExpr>> operator/( const __Expression<T_, N_, LExpr> &lhs, const __Expression<T_, N_, RExpr> &rhs); template <typename T_, int N_, typename LExpr> friend inline __Expression<T_, N_, __Divides<T_, LExpr, __Scalar<T_>>> operator/( const __Expression<T_, N_, LExpr> &lhs, const T_ &rhs); template <typename T_, int N_, typename RExpr> friend inline __Expression<T_, N_, __Divides<T_, __Scalar<T_>, RExpr>> operator/( const T_ &lhs, const __Expression<T_, N_, RExpr> &rhs); // operator% template <typename T_, int N_, typename LExpr, typename RExpr> friend inline __Expression<T_, N_, __Modulus<T_, LExpr, RExpr>> operator%( const __Expression<T_, N_, LExpr> &lhs, const __Expression<T_, N_, RExpr> &rhs); template <typename T_, int N_, typename LExpr> friend inline __Expression<T_, N_, __Modulus<T_, LExpr, __Scalar<T_>>> operator%( const __Expression<T_, N_, LExpr> &lhs, const T_ &rhs); template <typename T_, int N_, typename RExpr> friend inline __Expression<T_, N_, __Modulus<T_, __Scalar<T_>, RExpr>> operator%( const T_ &lhs, const __Expression<T_, N_, RExpr> &rhs); }; template <typename T, int N, typename Expr> __Expression<T, N, Expr>::__Expression() = default; template <typename T, int N, typename Expr> __Expression<T, N, Expr>::__Expression(const T &val): __expr(val) {} template <typename T, int N, typename Expr> __Expression<T, N, Expr>::__Expression(initializer_list<T> initializerList): __expr(initializerList) {} template <typename T, int N, typename Expr> __Expression<T, N, Expr>::__Expression(const Expr &expr): __expr(expr) {} template <typename T, int N, typename Expr> template <typename RhsExpr> __Expression<T, N, Expr> &__Expression<T, N, Expr>::operator=( const __Expression<T, N, RhsExpr> &rhs) { for (int idx = 0; idx < N; idx++) { __expr[idx] = rhs[idx]; } return *this; } // 運算符重載 // __Expression + __Expression template <typename T, int N, typename LExpr, typename RExpr> inline __Expression<T, N, __Plus<T, LExpr, RExpr>> operator+( const __Expression<T, N, LExpr> &lhs, const __Expression<T, N, RExpr> &rhs) { return __Expression<T, N, __Plus<T, LExpr, RExpr>>( __Plus<T, LExpr, RExpr>(lhs.__expr, rhs.__expr)); } // __Expression + __Scalar template <typename T, int N, typename LExpr> inline __Expression<T, N, __Plus<T, LExpr, __Scalar<T>>> operator+( const __Expression<T, N, LExpr> &lhs, const T &rhs) { return __Expression<T, N, __Plus<T, LExpr, __Scalar<T>>>( __Plus<T, LExpr, __Scalar<T>>(lhs.__expr, __Scalar<T>(rhs))); } // __Scalar + __Expression template <typename T, int N, typename RExpr> inline __Expression<T, N, __Plus<T, __Scalar<T>, RExpr>> operator+( const T &lhs, const __Expression<T, N, RExpr> &rhs) { return __Expression<T, N, __Plus<T, __Scalar<T>, RExpr>>( __Plus<T, __Scalar<T>, RExpr>(__Scalar<T>(lhs), rhs.__expr)); } // __Expression - __Expression template <typename T, int N, typename LExpr, typename RExpr> inline __Expression<T, N, __Minus<T, LExpr, RExpr>> operator-( const __Expression<T, N, LExpr> &lhs, const __Expression<T, N, RExpr> &rhs) { return __Expression<T, N, __Minus<T, LExpr, RExpr>>( __Minus<T, LExpr, RExpr>(lhs.__expr, rhs.__expr)); } // __Expression - __Scalar template <typename T, int N, typename LExpr> inline __Expression<T, N, __Minus<T, LExpr, __Scalar<T>>> operator-( const __Expression<T, N, LExpr> &lhs, const T &rhs) { return __Expression<T, N, __Minus<T, LExpr, __Scalar<T>>>( __Minus<T, LExpr, __Scalar<T>>(lhs.__expr, __Scalar<T>(rhs))); } // __Scalar - __Expression template <typename T, int N, typename RExpr> inline __Expression<T, N, __Minus<T, __Scalar<T>, RExpr>> operator-( const T &lhs, const __Expression<T, N, RExpr> &rhs) { return __Expression<T, N, __Minus<T, __Scalar<T>, RExpr>>( __Minus<T, __Scalar<T>, RExpr>(__Scalar<T>(lhs), rhs.__expr)); } // __Expression * __Expression template <typename T, int N, typename LExpr, typename RExpr> inline __Expression<T, N, __Multiplies<T, LExpr, RExpr>> operator*( const __Expression<T, N, LExpr> &lhs, const __Expression<T, N, RExpr> &rhs) { return __Expression<T, N, __Multiplies<T, LExpr, RExpr>>( __Multiplies<T, LExpr, RExpr>(lhs.__expr, rhs.__expr)); } // __Expression * __Scalar template <typename T, int N, typename LExpr> inline __Expression<T, N, __Multiplies<T, LExpr, __Scalar<T>>> operator*( const __Expression<T, N, LExpr> &lhs, const T &rhs) { return __Expression<T, N, __Multiplies<T, LExpr, __Scalar<T>>>( __Multiplies<T, LExpr, __Scalar<T>>(lhs.__expr, __Scalar<T>(rhs))); } // __Scalar * __Expression template <typename T, int N, typename RExpr> inline __Expression<T, N, __Multiplies<T, __Scalar<T>, RExpr>> operator*( const T &lhs, const __Expression<T, N, RExpr> &rhs) { return __Expression<T, N, __Multiplies<T, __Scalar<T>, RExpr>>( __Multiplies<T, __Scalar<T>, RExpr>(__Scalar<T>(lhs), rhs.__expr)); } // __Expression / __Expression template <typename T, int N, typename LExpr, typename RExpr> inline __Expression<T, N, __Divides<T, LExpr, RExpr>> operator/( const __Expression<T, N, LExpr> &lhs, const __Expression<T, N, RExpr> &rhs) { return __Expression<T, N, __Divides<T, LExpr, RExpr>>( __Divides<T, LExpr, RExpr>(lhs.__expr, rhs.__expr)); } // __Expression / __Scalar template <typename T, int N, typename LExpr> inline __Expression<T, N, __Divides<T, LExpr, __Scalar<T>>> operator/( const __Expression<T, N, LExpr> &lhs, const T &rhs) { return __Expression<T, N, __Divides<T, LExpr, __Scalar<T>>>( __Divides<T, LExpr, __Scalar<T>>(lhs.__expr, __Scalar<T>(rhs))); } // __Scalar / __Expression template <typename T, int N, typename RExpr> inline __Expression<T, N, __Divides<T, __Scalar<T>, RExpr>> operator/( const T &lhs, const __Expression<T, N, RExpr> &rhs) { return __Expression<T, N, __Divides<T, __Scalar<T>, RExpr>>( __Divides<T, __Scalar<T>, RExpr>(__Scalar<T>(lhs), rhs.__expr)); } // __Expression % __Expression template <typename T, int N, typename LExpr, typename RExpr> inline __Expression<T, N, __Modulus<T, LExpr, RExpr>> operator%( const __Expression<T, N, LExpr> &lhs, const __Expression<T, N, RExpr> &rhs) { return __Expression<T, N, __Modulus<T, LExpr, RExpr>>( __Modulus<T, LExpr, RExpr>(lhs.__expr, rhs.__expr)); } // __Expression % __Scalar template <typename T, int N, typename LExpr> inline __Expression<T, N, __Modulus<T, LExpr, __Scalar<T>>> operator%( const __Expression<T, N, LExpr> &lhs, const T &rhs) { return __Expression<T, N, __Modulus<T, LExpr, __Scalar<T>>>( __Modulus<T, LExpr, __Scalar<T>>(lhs.__expr, __Scalar<T>(rhs))); } // __Scalar % __Expression template <typename T, int N, typename RExpr> inline __Expression<T, N, __Modulus<T, __Scalar<T>, RExpr>> operator%( const T &lhs, const __Expression<T, N, RExpr> &rhs) { return __Expression<T, N, __Modulus<T, __Scalar<T>, RExpr>>( __Modulus<T, __Scalar<T>, RExpr>(__Scalar<T>(lhs), rhs.__expr)); } // 適用於__Expression的operator<<重載 template <typename T, int N, typename Expr> ostream &operator<<(ostream &os, const __Expression<T, N, Expr> &expressionObj) { os << '['; if (N) { os << expressionObj[0]; for (int idx = 1; idx < N; idx++) { os << ", " << expressionObj[idx]; } } os << ']'; return os; } // 最終供用戶使用的Array類 template <typename T, int N> using Array = __Expression<T, N, __Array<T, N>>; int main() { // 默認構造函數 Array<int, 3> sampleArrayA; // [?, ?, ?] // Fill構造函數 Array<int, 3> sampleArrayB(0); // [0, 0, 0] // initializer_list構造函數 Array<int, 3> sampleArrayC {1, 2, 3}; // [1, 2, 3] // 拷貝構造函數 Array<int, 3> sampleArrayD(sampleArrayC); // [1, 2, 3] // 四則運算 Array<int, 3> arrayA {1, 2, 3}, arrayB {4, 5, 6}, resArray; // operator<< cout << arrayA << endl; // [1, 2, 3] cout << arrayB << endl; // [4, 5, 6] cout << resArray << endl; // [?, ?, ?] // 惰性計算 arrayA + arrayB; // 什麼都不作! cout << arrayA + arrayB << endl; // [5, 7, 9] // operator+ resArray = 2 + arrayA + arrayB + 2; cout << resArray << endl; // [9, 11, 13] // operator- resArray = 2 - arrayA - arrayB - 2; cout << resArray << endl; // [-5, -7, -9] // operator* resArray = 2 * arrayA * arrayB * 2; cout << resArray << endl; // [16, 40, 72] // operator/ resArray = 200 / arrayB / arrayA / 2; cout << resArray << endl; // [25, 10, 5] // operator% resArray = 17 % arrayA % arrayB % 5; cout << resArray << endl; // [0, 1, 2] }
至此,表達式模板的實現也就所有完成了。
表達式模板,做爲一種服務於高性能計算場合的模板技術,被普遍應用於各類線性代數庫中(如著名的Eigen庫)。表達式模板的精彩之處在於:其充分利用了多級模板抽象所帶來的更大的抽象能力,將表達式模板中產生的重重複雜類型徹底隱藏於代碼實現中,使得用戶既可以像書寫普通表達式那樣進行公式的編碼,亦可以享受到表達式模板所帶來的極佳效率。模板在高性能計算領域的這一應用,既爲模板技術再添精彩一筆,也爲咱們的故事畫上了句號...
模板,最先於上世紀90年代被引入至C++,此後的多年內,模板技術迅速發展,促使了大量與之相關的程序設計技術的出現與成熟,並直接致使了STL的出現。在模板出現的幾年後,一份「經過報錯信息計算質數」的程序代碼完全刷新了人們對於模板的認知,這直接致使了「模板元編程」這一律唸的出現(本文做者原想以此份代碼的解讀做爲後記前的最後一章,但這份代碼已年代久遠,已經不能在做者的GCC編譯器上獲得理想的效果了,故抱憾做罷)。C++98標準的確立,催生了包括《C++ Templates: The Complete Guide》、《Modern C++ Design》等大量優秀著做的產生。正如《Modern C++ Design》一書的中文譯序中所言,這些書籍所講述的技術,使得咱們再也不認爲模板只是一位「戴上了新帽子」的舊朋友。閱讀這些書籍,必定能讓你對模板這一技術具備更深刻,更全面的認知。
模板是黑魔法嗎?相似的問題還有不少(例如:Python的元類是黑魔法嗎?)。若是你是一個狂熱的模板愛好者,你必定會回答:不!模板是頗有用的工具!而若是你對模板不是很感興趣,或僅僅是由於在學習模板的過程當中感到吃力,也許你會對模板的實用性存疑。人對某一個學術領域,某一項技術的認知,必將隨着學識、心態、技術自己的興衰等因素的變化而不斷的發生着變化。因此這個問題地答案,也就等着讀者你本身去不斷的回答了。
注:本文中的部分程序已完整實現於本文做者的Github上,列舉以下: