0 論抽象——前言
故事要從一個看起來非常簡單的功能開始:
請計算兩個數的和,
如果你對Python很熟悉,你一定會覺得:“哇!這太簡單了!”,然后寫出以下代碼:
def Plus(lhs, rhs):
return lhs + rhs
那么,C語言又如何呢?你需要面對這樣的問題:
/* 這里寫什么?*/ Plus(/* 這里寫什么?*/ lhs, /* 這里寫什么?*/ rhs)
{
return lhs + rhs;
}
也許你很快就能想到以下解法中的一些或全部:
- 硬編碼為某個特定型別:
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語言真爛!居然連這么簡單的功能都無法實作!”,但請想一想,為什么會出現這些問題呢?讓我們回到故事的起點:
請計算兩個數的和,
仔細分析這句話:“請計算...的和”,意味著“加法”語意,這在C語言中可以通過“+”實作(也許你會聯想到匯編語言中的加法實作);而“兩個”,則意味著形參的數量是2(也許你會聯想到匯編語言中的ESS、ESP、EBP等暫存器);那么,“數”,意味著什么語意?C語言中,具有“數”這一語意的型別有十幾種:int、double、unsigned,等等,甚至char也具有“數”的語意,那么,“加法”和“+”,“兩個”和“形參的數量是2”,以及“數”和int、double、unsigned等等之間的關系是什么?
是抽象,
高級語言的目的,就是對比其更加低級的語言進行抽象,從而使得我們能夠實作更加高級的功能,抽象,是一種人類的高級思維活動,是一種充滿著智慧的思維活動,匯編語言抽象了機器語言,而C語言則進一步抽象了匯編語言:其將匯編語言中的各種加法指令,抽象成了一個簡單的加號;將各種暫存器操作,抽象成了形參和實參...抽象思維是如此的普遍與自然,以至于我們往往甚至忽略了這種思維的存在,
但是,C語言并沒有針對型別進行抽象的能力,C語言不知道,也沒有能力表達“int和double都是數字”這一語意,而這,直接導致了這個“看起來非常簡單的功能”難以完美的實作,
針對型別的抽象是如此重要,以至于編程語言世界出現了與C語言這樣的“靜態型別語言”完全不一樣的“動態型別語言”,正如開頭所示,在Python這樣的動態型別語言中,我們根本就不需要為每個變數提供型別,從而似乎“從根本上解決了問題”,但是,“出來混,遲早要還的”,這種看似完美的動態型別語言,犧牲的卻是極大的運行時效率!我們不禁陷入了沉思:真的沒有既不損失效率,又能對型別進行抽象的方案了嗎?
正當我們一籌莫展,甚至感到些許絕望之時,C++的模板,為我們照亮了前行的道路,
1 新手村——模板基礎
1.1 函式模板與類模板
模板,即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,同樣正確!
}
顯然,模板的出現,使得我們輕而易舉的就實作了型別抽象,并且沒有(像動態型別語言那樣)引入任何因為此種抽象帶來的額外代價,
1.2 模板形參、模板實參與默認值
請看以下示例:
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> _;
}
上例中,我們宣告了三個模板引數:
- typename T:一個普通的型別引數
- int N:一個整型引數
- template <typename U, typename = allocator<U>> class Container = vector:一個“模板的模板引數”
什么叫“模板的模板引數”?這里需要明確的是:模板、型別和值,是三個完全不一樣的語法組分,模板能夠“創造”型別,而型別能夠“創造”值,請參考以下示例以進行辨析:
vector<int> v;
此例中,vector是一個模板,vector<int>是一個型別,而v是一個值,
所以,一個“模板的模板引數”,就是一個需要提供給其一個模板作為實參的引數,對于上文中的宣告,Container是一個“模板的模板引數”,其需要接受一個模板作為實參 ,需要怎樣的模板呢?這個模板應具有兩個模板形參,且第二形參具有默認值allocator<U>;同時,Container具有默認值vector,這正是一個符合要求的模板,這樣,Container在類定義中,便可被當作一個模板使用(就像vector那樣),
1.3 特化與偏特化
模板,代表了一種泛化的語意,顯然,既然有泛化語意,就應當有特化語意,特化,使得我們能為某些特定的型別專門提供一份特殊實作,以達到某些目的,
特化分為全特化與偏特化,所謂全特化,即一個“披著空空如也的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> {};
由此可見,“更特殊”是一個十分寬泛的語意,這賦予了模板極大的表意能力,我們將在下面的章節中不斷的見到特化所帶來的各種技巧,
1.4 惰性實體化
函式模板不是函式,而是一個可以生成函式的語法組分;同理,類模板也不是類,而是一個可以生成類的語法組分,我們稱通過函式模板生成函式,或通過類模板生成類的程序為模板實體化,
模板實體化具有一個非常重要的特征:惰性,這種惰性主要體現在類模板上,請看以下示例:
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,
在這里,“懶惰即美德”占了上風,
1.5 依賴型名稱
在C++中,“::”表達“取得”語意,顯然,“::”既可以取得一個值,也可以取得一個型別,這在非模板場景下是沒有任何問題的,并不會引起接下來即將將要討論的“取得的是一個型別還是一個值”的語意混淆,因為編譯器知道“::”左邊的語法組分的定義,但在模板中,如果“::”左邊的語法組分并不是一個確切型別,而是一個模板引數的話,語意將不再是確定的,請看以下示例:
struct A { typedef int TypeOrValue; };
struct B { static constexpr int TypeOrValue = https://www.cnblogs.com/yingyulou/p/0; };
template
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是一個型別,宣告了一個這樣型別的指標
1.6 可變引數模板
可變引數模板是C++11引入的一個極為重要的語法,這里對其進行簡要介紹,
可變引數模板表達了“引數數量,以及每個引數的型別都未知且各不相同”這一語意,如果我們希望實作一個簡單的print函式,其能夠傳入任意數量,且型別互不相同的引數,并依次列印這些引數值,此時就需要使用可變引數模板,
可變引數模板的語法由以下組分構成:
- typename...:宣告一個可變引數模板形參
- sizeof...:獲取引數包內引數的數量
- Pattern...:以某一模式展開引數包
接下來,我們就基于可變引數模板,實作這一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被完全分解,
2 平淡無奇卻暗藏玄機的語法——sizeof與SFINAE
2.1 sizeof
“sizeof?這有什么可討論的?”也許你會想,只要你學過C語言,那么對此必不陌生,那么為什么我們還需要為sizeof這一“平淡無奇”的語法單獨安排一節來討論呢?這是因為sizeof有兩個對于泛型編程而言極為重要的特性:
- sizeof的求值結果是編譯期常量(從而可以作為模板實參使用)
- 在任何情況下,sizeof都不會引發對其引數的求值或類似行為(如函式呼叫,甚至函式定義!等),因為并不需要
上述第一點很好理解,因為sizeof所考察的是型別,而型別(當然也包含其所占用的記憶體大小),一定是一個編譯期就知道的量(因為C++作為一門靜態型別語言,任何的型別都絕不會延遲到運行時才知道,這是動態型別語言才具有的特性),故sizeof的結果是一個編譯期常量也就不足為奇了,
上述第二點意味深長,利用此特性,我們可以實作出一些非常特殊的功能,請看下一節,
2.2 稻草人函式
讓我們以一個問題引出這一節的內容:
如何實作:判定型別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 = https://www.cnblogs.com/yingyulou/p/sizeof(__Test(__A())) == sizeof(__True);
};
上例比較復雜,我們依次進行討論,
首先,我們宣告了兩個大小不同的型別,作為假想的“布林值”,也許你會有疑問,這里為什么不使用int或double之類的型別作為False?這是由于C語言并未規定“int、double必須比char大”,故為了“強行滿足標準”(你完全可以認為這是某種“教條主義或形式主義”),這里采用了“兩個char一定比一個char大一倍”這一簡單道理,定義了False,
然后,我們宣告了一個所謂的“稻草人函式”,這個看似毫無意義的函式甚至沒有函式體(因為并不需要,且接下來的兩個函式也沒有函式體,與此函式同理),這個函式唯一的目的就是“獲得”一個A型別的值“給sizeof看”,由于sizeof的不求值特性,此函式也就不需要(我們也無法提供)函式體了,那么,為什么不直接使用形如“T()”這樣的寫法,而需要宣告一個“稻草人函式”呢?我想,不用我說你就已經明白原因了:這是因為并不是所有的T都具有默認建構式,而如果T沒有默認建構式,那么“T()”就是錯誤的,
接下來是最關鍵的部分,我們宣告了一對多載函式,這兩個函式的區別有二:
- 回傳值不同,一個是sizeof的結果為1的值,而另一個是sizeof的結果為2的值
- 形參不同,一個是B,一個是“...”
也就是說,如果我們給這一對多載函式傳入一個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
}
可以看出,輸出結果完全符合我們的預期,
2.3 SFINAE
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 = https://www.cnblogs.com/yingyulou/p/sizeof(__Test(0)) == sizeof(__True);
};
同樣,我們首先定義了兩個記憶體大小一定不一樣的型別,作為假想的“布林值”,然后,我們宣告了兩個多載模板,其分別以兩個“布林值”作為回傳值,這里的關鍵在于,多載模板的引數,一個是類成員指標,另一個是“...”,顯然,當編譯器拿到一個T,并準備生成一個“T:??”時,僅當T是一個型別別時,這一生成才是正確的,合乎語法的;否則,這個函式簽名將根本無法被生成出來,從而進一步的使得編譯器“被迫”選擇那個“最不喜歡的版本”進行呼叫(而不是認為這個“根本無法被生成出來”的模板是一個錯誤),所以,通過sizeof對__Test的回傳值大小進行判定,就能夠達到我們最終的目的了,下面請看使用示例:
int main()
{
cout << IsClass<double>::Value << endl; // false
cout << IsClass<string>::Value << endl; // true
}
可以看出,輸出結果完全符合我們的預期,
2.4 本章后記
sizeof,作為一個C語言的“入門級”語法,其“永不求值”的特性往往被我們所忽略,本章中,我們充分利用了sizeof的這種“永不求值”的特性,做了很多“表面工程”,僅僅是為了“給sizeof看”;同理,SFINAE技術似乎也只是在“找編譯器的麻煩,拿編譯器尋開心”,但正是這些“表面工程、找麻煩、尋開心”,讓我們得以實作了一些非常不可思議的功能,
3 型別萃取器——Type Traits
Traits,中文翻譯為“特性”,Type Traits,即為“型別的特性”,這是個十分奇怪的翻譯,故很多書籍對這個詞選擇不譯,也有書籍將其翻譯為“型別萃取器”,十分生動形象,
Type Traits的定義較為模糊,其大致代表了這樣的一系列技術:通過一個型別T,取得另一個基于T進行加工后的型別,或對T基于某一標準進行分類,得到分類結果,
本章中,我們以幾個經典的Type Traits應用,來見識一番此技術的精妙,
3.1 為T“添加星號”
第一個例子較為簡單:我們需要得到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 *”,
3.2 為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個類!但好在這一切均發生在編譯期,對運行效率不會產生任何影響,
3.3 尋找“最強大型別”
讓我們繼續討論前言中的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函式,
3.4 本章后記
本章所實作的三個小工具,都是STL的type_traits庫的一部分,值得一提的是我們最后實作的獲取“最強大型別”的工具:這一工具所解決的問題,實際上是一個非常經典的問題,其多次出現在多部著作中,由于decltype(以及可變引數模板)是C++11的產物,故很多較老的書籍對此問題給出了“無解”的結論,或只能給出一些較為牽強的解決方案,
4 “壓榨”編譯器——編譯期計算
值也能成為模板引數的一部分,而模板引數是編譯期常量,這二者的結合使得通過模板進行(較復雜的)編譯期計算成為了可能,由于編譯器本就不是“計算器”,故標題中使用了“壓榨”一詞,以表達此技術的“高昂的編譯期代價”以及“較大的局限性”的特點;同時,合理的利用編譯期計算技術,能夠極大地提高程式的效率,故“壓榨”也有“壓榨性能”之意,
本章中,我們以一小一大兩個示例,來討論編譯期計算這一巧妙技術的應用,
4.1 編譯期計算階乘
編譯期計算階乘是編譯期計算技術的經典案例,許多書籍對此均有討論(往往作為“模板元編程”一章的首個案例),那么首先,讓我們來看看一個普通的階乘函式的實作:
int Factorial(int N)
{
return N == 1 ? 1 : N * Factorial(N - 1);
}
這個實作很簡單,這里就不對其進行詳細討論了,下面,我們來看看如何將這個函式“翻譯”為一個編譯期就進行計算并得到結果的“函式”,請看以下示例:
// 遞回起點
template <int N>
struct Factorial
{
static constexpr int Value = https://www.cnblogs.com/yingyulou/p/N * Factorial::Value;
};
// 遞回終點
template <>
struct Factorial<1>
{
static constexpr int Value = 1;
};
int main()
{
cout << Factorial<4>::Value; // 編譯期就能獲得結果
}
觀察上述代碼,不難總結出我們的“翻譯”規則:
- 形參N(運行時值)變為了模板引數N(編譯期值)
- “N == 1”這樣的“if陳述句”變為了模板特化
- 遞回變為了創造一個新的模板(Factorial<N - 1>),這也意味著回圈也可以通過此種方式實作
- “return”變為了一個static constexpr變數
上述四點“翻譯”規則幾乎就是編譯期計算的全部技巧了!接下來,就讓我們以一個更復雜的例子來繼續討論這一技術的精彩之處:編譯期分數的實作,
4.2 編譯期分數
分數,由分子和分母組成,有了上一節的鋪墊,我們不難發現:分數正是一個可以使用編譯期計算技術的極佳場合,所以首先,我們需要實作一個編譯期分數類,編譯期分數類的實作非常簡單,我們只需要通過一個“建構式”將模板引數保留下來,作為靜態資料成員即可,請看以下示例:
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 = https://www.cnblogs.com/yingyulou/p/__GreatestCommonDivisor::__Value;
};
// 對應于“return rhs == 0 ? lhs : ...”部分
template
struct __GreatestCommonDivisor
{
static constexpr long long __Value = LHS;
};
// 對應于“return lhs * rhs / GreatestCommonDivisor(lhs, rhs)”部分
template
struct __LeastCommonMultiple
{
static constexpr long long __Value = LHS * RHS /
__GreatestCommonDivisor::__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 =
https://www.cnblogs.com/yingyulou/p/LHS::Numerator * __CommonPoints::__LValue ==
RHS::Numerator * __CommonPoints::__RValue;
};
// “operator!=”
template
struct FractionNotEqual
{
static constexpr bool Value =
LHS::Numerator * __CommonPoints::__LValue !=
RHS::Numerator * __CommonPoints::__RValue;
};
// “operator<”
template
struct FractionLess
{
static constexpr bool Value =
LHS::Numerator * __CommonPoints::__LValue <
RHS::Numerator * __CommonPoints::__RValue;
};
// “operator<=”
template
struct FractionLessEqual
{
static constexpr bool Value =
LHS::Numerator * __CommonPoints::__LValue <=
RHS::Numerator * __CommonPoints::__RValue;
};
// “operator>”
template
struct FractionGreater
{
static constexpr bool Value =
LHS::Numerator * __CommonPoints::__LValue >
RHS::Numerator * __CommonPoints::__RValue;
};
// “operato>=”
template
struct FractionGreaterEqual
{
static constexpr bool Value =
LHS::Numerator * __CommonPoints::__LValue >=
RHS::Numerator * __CommonPoints::__RValue;
};
int main()
{
// 1/2
typedef Fraction<1, 2> OneTwo;
// 2/3
typedef Fraction<2, 3> TwoThree;
// 1/2 == 2/3 => false
cout << FractionEqual::Value << endl;
// 1/2 != 2/3 => true
cout << FractionNotEqual::Value << endl;
// 1/2 < 2/3 => true
cout << FractionLess::Value << endl;
// 1/2 <= 2/3 => true
cout << FractionLessEqual::Value << endl;
// 1/2 > 2/3 => false
cout << FractionGreater::Value << endl;
// 1/2 >= 2/3 => false
cout << FractionGreaterEqual::Value << endl;
}
至此,編譯期分數的全部功能就都實作完畢了,不難發現,在編譯期分數的使用程序中,我們全程使用的都是typedef,并沒有真正的構造任何一個分數,一切計算都已經在編譯期完成了,
4.3 本章后記
讀完本章,也許你會恍然大悟:“哦!原來模板也能夠表達形參、if、while、return等語意!”,進而,也許你會有疑問:“那既然這樣,豈不是所有的計算函式都能換成編譯期計算了?”,
很可惜,答案是否定的,
我們通過對編譯期計算這一技術的優缺點進行總結,從而回答這個問題,編譯期計算的目的,是為了完全消除運行時代價,從而在高性能計算場合極大的提高效率;但此技術的缺點也是很多且很明顯的:首先,僅僅為了進行一次編譯期計算,就有可能進行很多次的模板實體化(比如,為了計算10的階乘,就要實體化出10個Factorial類),這是一種極大的潛在的編譯期代價;其次,并不是任何型別的值都能作為模板引數,如浮點數(雖然我們可以使用編譯期分數間接的規避這一限制)、以及任何的型別別值等均不可以,這就使得編譯期計算的應用幾乎被限定在只需要使用整型和布爾型別的場合中;最后,“遞回實體化”在所有的編譯器中都是有最大深度限制的(不過幸運的是,在現代編譯器中,允許的最大深度其實是比較大的),但即使如此,由于編譯期計算技術使得我們可以進行“搶跑”,在程式還未開始運行時,就計算出各種復雜的結果,從而極大的提升程式的效率,故此技術當然也是瑕不掩瑜的,
5 神奇的“多功能”函式——編譯期分派
本章旨在討論這樣的一個問題:
如何實作一個“多功能函式”,使得單一的“多功能函式”在面對不同型別的引數時,能夠自動選擇針對當前型別的最佳方案或獨特功能?
本章亦將通過一小一大兩個案例,來討論這一問題,
5.1 迭代器的advance函式
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
}
上述實作似乎運行正常,但稍加分析不難發現,這一實作具有以下兩個主要缺點:
- N必須為正數,即使對于(允許N為負數的)雙向迭代器和隨機訪問迭代器而言也是一樣
- 隨機訪問迭代器并不需要像此實作這樣“步進”,從而造成了效率的浪費
怎么樣才能既提供單一介面,又能夠讓介面有能力分辨出不同的迭代器類別呢?首先,我們需要將“迭代器的類別”這一語意表達出來,可以通過“強行編造”一些空類實作這一語意,請看以下示例:
// “強行編造”的三種不同的迭代器類別
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;
}
此時,我們已經擁有了兩組工具:
- 可以通過typename Iterator::IteratorCategory來獲取到當前迭代器的類別
- 實作了3個__Advance函式,分別適用于三個迭代器類別
此時,只要我們使用迭代器類別(作為第三引數)去呼叫__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函式的實作也就全部完成了,
接下來,我們以一個更為復雜,也更為神奇的功能,繼續討論編譯期分派這一技術,
5.2 “萬能的”列印函式
本節將要實作的功能,其最終使用起來十分簡單:
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 = ']';
- 對于Stack<int>,如果我們將1,2,3依次入堆疊,則取出時只能按3,2,1的順序出堆疊,這將造成很奇怪的列印效果,所以我們需要一個簡單的函式,用于將Stack反序,請看以下示例:
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函式的實作也就全部完成了,
5.3 本章后記
本章,我們首先通過一個簡單的STL advance函式,討論了編譯期分派技術,這一函式的實作程序能夠帶給我們兩點思考:
- 為什么我們能在用戶無感知的情況下實作分派呢?不難發現:你,作為一個函式的實作者,有一樣東西是用戶所不具有的,那就是型別,當用戶使用一個值來呼叫函式時,你不僅能得到這個值,還能“順便”得到這個值的型別,而正是因為有了這多出來的型別,我們就能在型別上大做文章,實作出很多“神奇”的介面
- 事實上,并沒有真正的“多功能函式”,但我們可以通過“添加中間層可解決一切問題”這一“經典定理”,使得單一的介面函式,根據傳入的實參型別的不同,“多功能的”呼叫多個底層的實作函式,從而達到“多功能函式”的效果
緊接著,我們實作了一個代碼更為復雜的print函式,觀其輸出結果,不禁讓我們感慨:一個小小的T,在經過我們的“大做文章”之后,竟能夠表現出如此豐富的多樣性!這,就是編譯期分派的強大威力所在!
6 “突破極限”的容器——Tuple
Tuple是一種非常特殊且高級的資料結構,其能夠容納和取用數量、型別都不定的一組值,你也可以將Tuple理解為某種“匿名結構體”,乍看之下,“數量、型別都不定”和模板中“什么都是已經確定的編譯期常量”從語法上就是完全相悖的,和容器的“所有元素的型別必須相同”的原則也是完全相悖的,似乎,Tuple是一種“突破極限”的容器,可事實真的是如此嗎?
6.1 可遞回Pair
首先,請看下面這段“平淡無奇”的代碼:
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”!這也太反人類了!請不要著急,接著往下看,
6.2 為可遞回Pair的型別添加“索引值”
接下來,我們需要解決似乎很棘手的一個問題:如何根據“索引值”獲取可遞回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>>>版本)似乎是多余的?你可以去掉這些特化,然后編譯試試看,
6.3 為可遞回Pair的值添加“索引值”
有了上文中__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,要怎么辦呢?我們需要解決兩個問題:
- Get函式的回傳值型別是什么?
- 怎么得到“pairObj[N]”的值?
第一個問題的解決方案不言而喻:我們已經實作了可以獲取到可遞回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>>>版本)是多余的,你可以去掉這些特化,然后編譯試試看,
6.4 將“千層餅”搟成“單層餅”
本節將會是整個Tuple的實作中最為精彩的部分!
我們雖然已經實作了針對可遞回Pair的取型別和取值工具,但我們還是沒有實作出一個“扁平的”真正的Tuple(沒錯,終于又看到Tuple這個詞了),接下來,我們就開始著手考慮,如何將可遞回Pair這張“千層餅”搟平,變成一張“單層餅”Tuple,
如何實作“搟平”這一操作呢?稍加思考不難發現,可遞回Pair和Tuple之間似乎存在著這樣的一一對應關系:
- 含有一個元素的Tuple,就是一個__RecursionPair<T1, void>
- 含有兩個元素的Tuple,就是一個__RecursionPair<T1, T2>
- 含有三個元素的Tuple,就是一個__RecursionPair<T1, __RecursionPair<T2, T3>>
- 含有四個元素的Tuple,就是一個__RecursionPair<T1, __RecursionPair<T2, __RecursionPair<T3, T4>>>
- ...
如何描述這種“是一個”的語意?哦!是繼承!
請看以下示例:
// 原型
// 通過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, T2, T3>的“可遞回Pair形態”是__RecursionPair<T1, typename Tuple<T2, T3>::__PairType>,即:將T1,與Tuple<T2, T3>的“可遞回Pair形態”放入一個__RecursionPair中,最終得到的結果是__RecursionPair<T1, __RecursionPair<T2, T3>>
- Tuple<T1, T2, T3, T4>的“可遞回Pair形態”是__RecursionPair<T1, typename Tuple<T2, T3, T4>::__PairType>,即:將T1,與Tuple<T2, T3, T4>的“可遞回Pair形態”放入一個__RecursionPair中(Tuple<T2, T3, T4>的“可遞回Pair形態”就是上面已經得到的Tuple<T1, T2, T3>的“可遞回Pair形態”),最終得到的結果是__RecursionPair<T1, __RecursionPair<T2, __RecursionPair<T3, T4>>>
- ...
- Tuple<T1, Types...>的“可遞回Pair形態”是__RecursionPair<T1, typename Tuple<Types...>::__PairType>,即:將T1,與Tuple<Types...>的“可遞回Pair形態”放入一個__RecursionPair中,最終得到的結果是__RecursionPair<T1, __RecursionPair<T2, __RecursionPair<T3, ..., __RecursionPair<T(N - 1), T(N)>>>...>
找到了這一規律,代碼實作也就輕而易舉了,請看以下示例:
// 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的建構式呢?讓我們繼續進行“找規律”:
- 為了構造一個Tuple<T1, T2, T3>(Arg1, Arg2, Arg3),我們需要構造一個typename Tuple<T1, T2, T3>::__PairType,即一個__RecursionPair<T1, __RecursionPair<T2, T3>>,其中,T1來自于Arg1,而__RecursionPair<T2, T3>可以通過構造一個Tuple<T2, T3>(Arg2, Arg3)得到(因為一個Tuple<T2, T3>就是一個__RecursionPair<T2, T3>)
- 為了構造一個Tuple<T1, T2, T3, T4>(Arg1, Arg2, Arg3, Arg4),我們需要構造一個typename Tuple<T1, T2, T3, T4>::__PairType,即一個__RecursionPair<T1, __RecursionPair<T2, __RecursionPair<T3, T4>>>,其中,T1同樣來自于Arg1,而__RecursionPair<T2, __RecursionPair<T3, T4>>可以通過構造一個Tuple<T2, T3, T4>(Arg2, Arg3, Arg4)得到(因為一個Tuple<T2, T3, T4>就是一個__RecursionPair<T2, __RecursionPair<T3, T4>>)
- ...
- 為了構造一個Tuple<T1, Types...>(first, Args...),我們需要構造一個typename Tuple<T1, Types...>::__PairType,即一個__RecursionPair<T1, typename Tuple<Types...>::__PairType>,其中,T1來自于first,而typename Tuple<Types...>::__PairType可以通過構造一個Tuple<Types...>(Args...)得到(因為一個Tuple<Types...>就是一個typename Tuple<Types...>::__PairType)
我們再一次通過找規律的方法得到了結論!接下來就可以進行代碼實作了,請看以下示例:
// 默認建構式
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時代”的工具獲取到其內部的值),
6.5 Tuple的其他功能的實作
最后,就是一些簡單的周邊功能的實作了,
首先是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的實作也就全部完成了,
6.6 本章后記
Tuple,作為一個看起來已然“突破極限”的高級容器,其背后的核心竟然只是一個“平淡無奇”的Pair,這不得不令人驚訝于基于模板的高階抽象的威力,在Tuple的實作程序中,我們充分利用了模板偏特化,用以描繪出各種不同“形態”的可遞回Pair;我們也使用了繼承,用以描繪出Tuple與可遞回Pair的一一對應關系,在這里,模板與繼承,這兩個“不同世界的產物”,被巧妙的結合在了一起,最終為我們帶來了一場十分精彩的二重奏!
7 模板與高性能計算的極佳配合——運算式模板
運算式模板?什么?你沒聽說過?那就對了!通過本章的討論,你就會了解到:模板是如何在用戶無感知的前提下,將高性能計算引入我們的程式中的,
7.1 運算式的困境
讓我們從一個看似很簡單的問題開始:
如何實作向量的加法運算?
如果使用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++的語意規定,立即計算此加法,而“暫且不顧”后續運算式,
看來,編譯器優化是徹底不可能幫得上忙了,這讓我們陷入了困境之中,
7.2 一個天馬行空的想法
既然編譯器幫不上忙,那我們是否能通過某種技術,“繞過”編譯器的這種主動計算呢?如果你的想象力足夠豐富,也許你會有這樣的想法:
能否將運算式看作某種“字串”,這樣,加法就相當于“字串的拼接”呢?而當我們真的需要運算式的結果時,我們可以實作一個對“運算式字串”進行求值的函式來進行求值,
這是一個天馬行空的想法,但基于模板,這個想法是真的可以實作的!這就是本章將要討論的運算式模板技術,
7.3 向量類的實作
首先,讓我們實作一個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[]多載,看上去平淡無奇,不是嗎?
7.4 “運算式字串”的實作
接下來,我們就來實作上文中的“運算式字串”(當然,我們不是真的去實作一個特殊的字串),一個“運算式字串”,如“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+多載嗎?請接著往下看,
7.5 再加一層抽象
如何規避這種“排列組合”呢?讓我們開拓一下思維,不難發現:單獨的一個__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
// 其第一、二模板引數不變,第三模板引數是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的型別究竟是什么,而只需要牢記以下兩點即可:
- 不管進行多少次計算,或一次計算都還沒有進行(即一個Array),我們所操作的都是一個(可能具有很復雜型別的)__Expression
- 對__Expression進行operator[],就相當于對__Expression中的__expr進行operator[];而__expr無非只有兩種情況:其要么是一個__Array,此時,operator[]就相當于一次陣列取值;要么是一個__Plus,此時,operator[]就相當于遞回地呼叫lhs[idx] + rhs[idx],直到lhs與rhs都不再是一個__Plus為止
由此可見,__Expression一方面為用戶提供了對于運算式模板的存在無感知的Array類,另一方面又絲毫不丟失其作為運算式模板的功能,實在是一個優秀的“左右開弓”類;另一方面,由于我們的__Array與__Plus均實作了統一的operator[]介面,這就使得__Expression能夠“自適應地”最終實作對其自身的求值,以上種種,都能夠為我們展現出“抽象”這一思想的精彩之處,
7.6 讓標量也加入進來
在數學中,一個向量不僅可以和另一個向量相加,還可以和一個標量(即一個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類,用于在面對標量時自動將運算式模板中的參考型別切換為值型別,
至此,運算式模板的全部技術就都討論完畢了,下一節,我們將最終給出運算式模板的完整實作,
7.7 完整的實作
由于本章的代碼較為分散,且我們仍有很多重復性的代碼沒有于上文中給出,故本節中,我們將給出運算式模板的完整實作,主要包含以下幾點新增內容:
- 我們不僅需要實作operator+,還要實作operator-、operator*、operator/和operator%,這些實作都是operator+的簡單重復
- 我們需要為所有的operator函式添加友元授權
- 我們需要為__Expression實作operator<<多載(實作方案與operator=一致)
請看以下示例:
// “夢開始的地方”:__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]
}
至此,運算式模板的實作也就全部完成了,
7.8 本章后記
運算式模板,作為一種服務于高性能計算場合的模板技術,被廣泛應用于各種線性代數庫中(如著名的Eigen庫),運算式模板的精彩之處在于:其充分利用了多級模板抽象所帶來的更大的抽象能力,將運算式模板中產生的重重復雜型別完全隱藏于代碼實作中,使得用戶既能夠像書寫普通運算式那樣進行公式的編碼,亦能夠享受到運算式模板所帶來的極佳效率,模板在高性能計算領域的這一應用,既為模板技術再添精彩一筆,也為我們的故事畫上了句號...
8 模板是黑魔法嗎?——后記
模板,最早于上世紀90年代被引入至C++,此后的多年內,模板技術迅速發展,促使了大量與之相關的程式設計技術的出現與成熟,并直接導致了STL的出現,在模板出現的幾年后,一份“通過報錯資訊計算質數”的程式代碼徹底重繪了人們對于模板的認知,這直接導致了“模板元編程”這一概念的出現(本文作者原想以此份代碼的解讀作為后記前的最后一章,但這份代碼已年代久遠,已經不能在作者的GCC編譯器上得到理想的效果了,故抱憾作罷),C++98標準的確立,催生了包括《C++ Templates: The Complete Guide》、《Modern C++ Design》等大量優秀著作的產生,正如《Modern C++ Design》一書的中文譯序中所言,這些書籍所講述的技術,使得我們不再認為模板只是一位“戴上了新帽子”的舊朋友,閱讀這些書籍,一定能讓你對模板這一技術具有更深入,更全面的認知,
模板是黑魔法嗎?類似的問題還有很多(例如:Python的元類是黑魔法嗎?),如果你是一個狂熱的模板愛好者,你一定會回答:不!模板是很有用的工具!而如果你對模板不是很感興趣,或僅僅是因為在學習模板的程序中感到吃力,也許你會對模板的實用性存疑,人對某一個學術領域,某一項技術的認知,必將隨著學識、心態、技術本身的興衰等因素的變化而不斷的發生著變化,所以這個問題地答案,也就等著讀者你自己去不斷的回答了,
注:本文中的部分程式已完整實作于本文作者的Github上,列舉如下:
- 編譯期分數:https://github.com/yingyulou/STLToyRoom/tree/main/Fraction
- print函式:https://github.com/yingyulou/pprint
- Tuple:https://github.com/yingyulou/STLToyRoom/tree/main/Tuple
- 運算式模板:https://github.com/yingyulou/ExprTmpl
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/260848.html
標籤:C++
上一篇:這是一篇小短文
下一篇:JPG學習筆記4(附完整代碼)
