欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
C模板沉思錄(上)

   https://m.toutiao.com/is/JuK5kQU/ 




0 論抽象——前言

故事要從一個(gè)看起來(lái)非常簡(jiǎn)單的功能開(kāi)始:

請計算兩個(gè)數的和。

如果你對Python很熟悉,你一定會(huì )覺(jué)得:“哇!這太簡(jiǎn)單了!”,然后寫(xiě)出以下代碼:

def Plus(lhs, rhs):    return lhs + rhs

那么,C語(yǔ)言又如何呢?你需要面對這樣的問(wèn)題:

/* 這里寫(xiě)什么?*/ Plus(/* 這里寫(xiě)什么?*/ lhs, /* 這里寫(xiě)什么?*/ rhs){    return lhs + rhs;}

也許你很快就能想到以下解法中的一些或全部:

  1. 硬編碼為某個(gè)特定類(lèi)型:
int Plus(int lhs, int rhs){    return lhs + rhs;}

顯然,這不是一個(gè)好的方案。因為這樣的Plus函數接口強行的要求兩個(gè)實(shí)參以及返回值的類(lèi)型都必須是int,或是能夠發(fā)生隱式類(lèi)型轉換到int的類(lèi)型。此時(shí),如果實(shí)參并不是int類(lèi)型,其結果往往就是錯誤的。請看以下示例:

int main(){    printf('%d\n', Plus(12));          // 3,正確    printf('%d\n', Plus(1.9992.999));  // 仍然是3!}
  1. 針對不同類(lèi)型,定義多個(gè)函數
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;}// ...

這種方案的缺點(diǎn)也很明顯:其使得代碼寫(xiě)起來(lái)像“匯編語(yǔ)言”(movl,movq,...)。我們需要針對不同的類(lèi)型調用不同名稱(chēng)的函數(是的,C語(yǔ)言也不支持函數重載),這太可怕了。

  1. 使用宏
#define Plus(lhs, rhs) (lhs + rhs)

這種方案似乎很不錯,甚至“代碼看上去和Python一樣”。但正如許許多多的書(shū)籍都討論過(guò)的那樣,宏,不僅“拋棄”了類(lèi)型,甚至“拋棄”了代碼。是的,宏不是C語(yǔ)言代碼,其只是交付于預處理器執行的“復制粘貼”的標記。一旦預處理完成,宏已然不再存在??上攵?,在功能變得復雜后,宏的缺點(diǎn)將會(huì )越來(lái)越大:代碼晦澀,無(wú)法調試,“莫名其妙”的報錯...

看到這里,也許你會(huì )覺(jué)得:“哇!C語(yǔ)言真爛!居然連這么簡(jiǎn)單的功能都無(wú)法實(shí)現!”。但請想一想,為什么會(huì )出現這些問(wèn)題呢?讓我們回到故事的起點(diǎn):

請計算兩個(gè)數的和。

仔細分析這句話(huà):“請計算...的和”,意味著(zhù)“加法”語(yǔ)義,這在C語(yǔ)言中可以通過(guò)“+”實(shí)現(也許你會(huì )聯(lián)想到匯編語(yǔ)言中的加法實(shí)現);而“兩個(gè)”,則意味著(zhù)形參的數量是2(也許你會(huì )聯(lián)想到匯編語(yǔ)言中的ESS、ESP、EBP等寄存器);那么,“數”,意味著(zhù)什么語(yǔ)義?C語(yǔ)言中,具有“數”這一語(yǔ)義的類(lèi)型有十幾種:int、double、unsigned,等等,甚至char也具有“數”的語(yǔ)義。那么,“加法”和“+”,“兩個(gè)”和“形參的數量是2”,以及“數”和int、double、unsigned等等之間的關(guān)系是什么?

是抽象。

高級語(yǔ)言的目的,就是對比其更加低級的語(yǔ)言進(jìn)行抽象,從而使得我們能夠實(shí)現更加高級的功能。抽象,是一種人類(lèi)的高級思維活動(dòng),是一種充滿(mǎn)著(zhù)智慧的思維活動(dòng)。匯編語(yǔ)言抽象了機器語(yǔ)言,而C語(yǔ)言則進(jìn)一步抽象了匯編語(yǔ)言:其將匯編語(yǔ)言中的各種加法指令,抽象成了一個(gè)簡(jiǎn)單的加號;將各種寄存器操作,抽象成了形參和實(shí)參...抽象思維是如此的普遍與自然,以至于我們往往甚至忽略了這種思維的存在。

但是,C語(yǔ)言并沒(méi)有針對類(lèi)型進(jìn)行抽象的能力,C語(yǔ)言不知道,也沒(méi)有能力表達“int和double都是數字”這一語(yǔ)義。而這,直接導致了這個(gè)“看起來(lái)非常簡(jiǎn)單的功能”難以完美的實(shí)現。

針對類(lèi)型的抽象是如此重要,以至于編程語(yǔ)言世界出現了與C語(yǔ)言這樣的“靜態(tài)類(lèi)型語(yǔ)言”完全不一樣的“動(dòng)態(tài)類(lèi)型語(yǔ)言”。正如開(kāi)頭所示,在Python這樣的動(dòng)態(tài)類(lèi)型語(yǔ)言中,我們根本就不需要為每個(gè)變量提供類(lèi)型,從而似乎“從根本上解決了問(wèn)題”。但是,“出來(lái)混,遲早要還的”,這種看似完美的動(dòng)態(tài)類(lèi)型語(yǔ)言,犧牲的卻是極大的運行時(shí)效率!我們不禁陷入了沉思:真的沒(méi)有既不損失效率,又能對類(lèi)型進(jìn)行抽象的方案了嗎?

正當我們一籌莫展,甚至感到些許絕望之時(shí),C++的模板,為我們照亮了前行的道路。

1 新手村——模板基礎

1.1 函數模板與類(lèi)模板

模板,即C++中用以實(shí)現泛型編程思想的語(yǔ)法組分。模板是什么?一言以蔽之:類(lèi)型也可以是“變量”的東西。這樣的“東西”,在C++中有二:函數模板和類(lèi)模板。

通過(guò)在普通的函數定義和類(lèi)定義中前置template <...>,即可定義一個(gè)模板,讓我們以上文中的Plus函數進(jìn)行說(shuō)明。請看以下示例:

此為函數模板:

template <typename T>Plus(T lhs, T rhs){    return lhs + rhs;}int main(){    cout << Plus(12) << endl;          // 3,正確!    cout << Plus(1.999, 2.999) << endl;  // 4.998,同樣正確!}

此為類(lèi)模板:

template <typename T>struct Plus{    operator()(T lhs, T rhs)    {        return lhs + rhs;    }};int main(){    cout << Plus<int>()(12) << endl;             // 3,正確!    cout << Plus<double>()(1.999, 2.999) << endl;  // 4.998,同樣正確!}

顯然,模板的出現,使得我們輕而易舉的就實(shí)現了類(lèi)型抽象,并且沒(méi)有(像動(dòng)態(tài)類(lèi)型語(yǔ)言那樣)引入任何因為此種抽象帶來(lái)的額外代價(jià)。

1.2 模板形參、模板實(shí)參與默認值

請看以下示例:

template <typename T>struct Plus{    operator()(T lhs, T rhs)    {        return lhs + rhs;    }};int main(){    cout << Plus<int>()(12) << endl;    cout << Plus<double>()(1.9992.999) << endl;}

上例中,typename T中的T,稱(chēng)為模板形參;而Plus<int>中的int,則稱(chēng)為模板實(shí)參。在這里,模板實(shí)參是一個(gè)類(lèi)型。

事實(shí)上,模板的形參與實(shí)參既可以是類(lèi)型,也可以是值,甚至可以是“模板的模板”;并且,模板形參也可以具有默認值(就和函數形參一樣)。請看以下示例:

template <typename T, int N, template <typename U, typename = allocator<U>> class Container = vector>class MyArray{    Container<T> __data[N];};int main(){    MyArray<int3> _;}

上例中,我們聲明了三個(gè)模板參數:

  1. typename T:一個(gè)普通的類(lèi)型參數
  2. int N:一個(gè)整型參數
  3. template <typename U, typename = allocator<U>> class Container = vector:一個(gè)“模板的模板參數”

什么叫“模板的模板參數”?這里需要明確的是:模板、類(lèi)型和值,是三個(gè)完全不一樣的語(yǔ)法組分。模板能夠“創(chuàng )造”類(lèi)型,而類(lèi)型能夠“創(chuàng )造”值。請參考以下示例以進(jìn)行辨析:

vector<int> v;

此例中,vector是一個(gè)模板,vector<int>是一個(gè)類(lèi)型,而v是一個(gè)值。

所以,一個(gè)“模板的模板參數”,就是一個(gè)需要提供給其一個(gè)模板作為實(shí)參的參數。對于上文中的聲明,Container是一個(gè)“模板的模板參數”,其需要接受一個(gè)模板作為實(shí)參 。需要怎樣的模板呢?這個(gè)模板應具有兩個(gè)模板形參,且第二形參具有默認值allocator<U>;同時(shí),Container具有默認值vector,這正是一個(gè)符合要求的模板。這樣,Container在類(lèi)定義中,便可被當作一個(gè)模板使用(就像vector那樣)。

1.3 特化與偏特化

模板,代表了一種泛化的語(yǔ)義。顯然,既然有泛化語(yǔ)義,就應當有特化語(yǔ)義。特化,使得我們能為某些特定的類(lèi)型專(zhuān)門(mén)提供一份特殊實(shí)現,以達到某些目的。

特化分為全特化與偏特化。所謂全特化,即一個(gè)“披著(zhù)空空如也的template <>的普通函數或類(lèi)”,我們還是以上文中的Plus函數為例:

// 不管T是什么類(lèi)型,都將使用此定義...template <typename T>T Plus(T lhs, T rhs){    return lhs + rhs;}// ...但是,當T為int時(shí),將使用此定義template <>  // 空空如也的template <>int Plus(int lhs, int rhs){    return lhs + rhs;}int main(){    Plus(1., 2.);  // 使用泛型版本    Plus(12);    // 使用特化版本}

那么,偏特化又是什么呢?除了全特化以外的特化,都稱(chēng)為偏特化。這句話(huà)雖然簡(jiǎn)短,但意味深長(cháng),讓我們來(lái)仔細分析一下:首先,“除了全特化以外的...”,代表了template關(guān)鍵詞之后的“<>”不能為空,否則就是全特化,這顯而易見(jiàn);其次,“...的特化”,代表了偏特化也必須是一個(gè)特化。什么叫“是一個(gè)特化”呢?只要特化版本比泛型版本更特殊,那么此版本就是一個(gè)特化版本。請看以下示例:

// 泛化版本template <typename Ttypename U>struct _ {};// 這個(gè)版本的特殊之處在于:僅當兩個(gè)類(lèi)型一樣的時(shí)候,才會(huì )且一定會(huì )使用此版本template <typename T>struct _<T, T> {};// 這個(gè)版本的特殊之處在于:僅當兩個(gè)類(lèi)型都是指針的時(shí)候,才會(huì )且一定會(huì )使用此版本template <typename Ttypename U>struct _<T *, U *> {};// 這個(gè)版本“換湯不換藥”,沒(méi)有任何特別之處,所以不是一個(gè)特化,而是錯誤的重復定義template <typename Atypename B>struct _<A, B> {};

由此可見(jiàn),“更特殊”是一個(gè)十分寬泛的語(yǔ)義,這賦予了模板極大的表意能力,我們將在下面的章節中不斷的見(jiàn)到特化所帶來(lái)的各種技巧。

1.4 惰性實(shí)例化

函數模板不是函數,而是一個(gè)可以生成函數的語(yǔ)法組分;同理,類(lèi)模板也不是類(lèi),而是一個(gè)可以生成類(lèi)的語(yǔ)法組分。我們稱(chēng)通過(guò)函數模板生成函數,或通過(guò)類(lèi)模板生成類(lèi)的過(guò)程為模板實(shí)例化。

模板實(shí)例化具有一個(gè)非常重要的特征:惰性。這種惰性主要體現在類(lèi)模板上。請看以下示例:

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類(lèi)型的。也就是說(shuō),Test類(lèi)對于string類(lèi)型而言,并不是“100%完美的”。當遇到這種情況時(shí),C++的做法十分寬松:不完美?不要緊,只要不調用那些“不完美的函數”就行了。在編譯器層面,編譯器只會(huì )實(shí)例化真的被使用的函數,并對其進(jìn)行語(yǔ)法檢查,而根本不會(huì )在意那些根本沒(méi)有被用到的函數。也就是說(shuō),在上例中,編譯器實(shí)際上只實(shí)例化出了兩個(gè)函數:string版本的Plus,以及int版本的Minus。

在這里,“懶惰即美德”占了上風(fēng)。

1.5 依賴(lài)型名稱(chēng)

在C++中,“::”表達“取得”語(yǔ)義。顯然,“::”既可以取得一個(gè)值,也可以取得一個(gè)類(lèi)型。這在非模板場(chǎng)景下是沒(méi)有任何問(wèn)題的,并不會(huì )引起接下來(lái)即將將要討論的“取得的是一個(gè)類(lèi)型還是一個(gè)值”的語(yǔ)義混淆,因為編譯器知道“::”左邊的語(yǔ)法組分的定義。但在模板中,如果“::”左邊的語(yǔ)法組分并不是一個(gè)確切類(lèi)型,而是一個(gè)模板參數的話(huà),語(yǔ)義將不再是確定的。請看以下示例:

struct A { typedef int TypeOrValue; };struct B { static constexpr int TypeOrValue = 0; };template <typename T>struct C{    T::TypeOrValue;  // 這是什么?};

上例中,如果T是A,則T::TypeOrValue是一個(gè)類(lèi)型;而如果T是B,則T::TypeOrValue是一個(gè)數。我們稱(chēng)這種含有模板參數的,無(wú)法立即確定語(yǔ)義的名稱(chēng)為“依賴(lài)型名稱(chēng)”。所謂“依賴(lài)”,意即此名稱(chēng)的確切語(yǔ)義依賴(lài)于模板參數的實(shí)際類(lèi)型。

對于依賴(lài)型名稱(chēng),C++規定:默認情況下,編譯器應認為依賴(lài)型名稱(chēng)不是一個(gè)類(lèi)型;如果需要編譯器將依賴(lài)型名稱(chēng)視為一個(gè)類(lèi)型,則需要前置typename關(guān)鍵詞。請看以下示例以進(jìn)行辨析:

T::TypeOrValue * N;           // T::TypeOrValue是一個(gè)值,這是一個(gè)乘法表達式typename T::TypeOrValue * N;  // typename T::TypeOrValue是一個(gè)類(lèi)型,聲明了一個(gè)這樣類(lèi)型的指針

1.6 可變參數模板

可變參數模板是C++11引入的一個(gè)極為重要的語(yǔ)法。這里對其進(jìn)行簡(jiǎn)要介紹。

可變參數模板表達了“參數數量,以及每個(gè)參數的類(lèi)型都未知且各不相同”這一語(yǔ)義。如果我們希望實(shí)現一個(gè)簡(jiǎn)單的print函數,其能夠傳入任意數量,且類(lèi)型互不相同的參數,并依次打印這些參數值,此時(shí)就需要使用可變參數模板。

可變參數模板的語(yǔ)法由以下組分構成:

  1. typename...:聲明一個(gè)可變參數模板形參
  2. sizeof...:獲取參數包內參數的數量
  3. Pattern...:以某一模式展開(kāi)參數包

接下來(lái),我們就基于可變參數模板,實(shí)現這一print函數。請看以下示例:

// 遞歸終點(diǎn)void print() {}// 分解出一個(gè)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){    // 每次打印一個(gè)val    cout << val << endl;    // 相當于:print(Args1, Args2, Args3, ...);    // 遞歸地繼續分解...    print(Args...);}int main(){    print(12., '3''4');}

上例中,我們實(shí)現了一對重載的print函數。第一個(gè)print函數是一個(gè)空函數,其將在“Args...”是空的時(shí)候被調用,以作為遞歸終點(diǎn);而第二個(gè)print函數接受一個(gè)val以及余下的所有val作為參數,其將打印val,并使用余下的所有val繼續遞歸調用自己。不難發(fā)現,第二版本的print函數具有不斷打印并分解Args的能力,直到Args被完全分解。

2 平淡無(wú)奇卻暗藏玄機的語(yǔ)法——sizeof與SFINAE

2.1 sizeof

“sizeof?這有什么可討論的?”也許你會(huì )想。只要你學(xué)過(guò)C語(yǔ)言,那么對此必不陌生。那么為什么我們還需要為sizeof這一“平淡無(wú)奇”的語(yǔ)法單獨安排一節來(lái)討論呢?這是因為sizeof有兩個(gè)對于泛型編程而言極為重要的特性:

  1. sizeof的求值結果是編譯期常量(從而可以作為模板實(shí)參使用)
  2. 在任何情況下,sizeof都不會(huì )引發(fā)對其參數的求值或類(lèi)似行為(如函數調用,甚至函數定義!等),因為并不需要

上述第一點(diǎn)很好理解,因為sizeof所考察的是類(lèi)型,而類(lèi)型(當然也包含其所占用的內存大?。?,一定是一個(gè)編譯期就知道的量(因為C++作為一門(mén)靜態(tài)類(lèi)型語(yǔ)言,任何的類(lèi)型都絕不會(huì )延遲到運行時(shí)才知道,這是動(dòng)態(tài)類(lèi)型語(yǔ)言才具有的特性),故sizeof的結果是一個(gè)編譯期常量也就不足為奇了。

上述第二點(diǎn)意味深長(cháng)。利用此特性,我們可以實(shí)現出一些非常特殊的功能。請看下一節。

2.2 稻草人函數

讓我們以一個(gè)問(wèn)題引出這一節的內容:

如何實(shí)現:判定類(lèi)型A是否能夠基于隱式類(lèi)型轉換轉為B類(lèi)型?

乍看之下,這是個(gè)十分棘手的問(wèn)題。此時(shí)我們應當思考的是:如何引導(請注意“引導”一詞的含義)編譯器,在A(yíng)到B的隱式類(lèi)型轉換可行時(shí),走第一條路,否則,走第二條路?

請看以下示例:

template <typename A, typename B>class IsCastable{private:    // 定義兩個(gè)內存大小不一樣的類(lèi)型,作為“布爾值”    typedef char __True;    typedef struct { char _[2]; } __False;    // 稻草人函數    static A __A();    // 只要A到B的隱式類(lèi)型轉換可用,重載確定的結果就是此函數...    static __True __Test(B);    // ...否則,重載確定的結果才是此函數(“...”參數的重載確定優(yōu)先級低于其他一切可行的重載版本)    static __False __Test(...);public:    // 根據重載確定的結果,就能夠判定出隱式類(lèi)型轉換是否能夠發(fā)生    static constexpr bool Value = sizeof(__Test(__A())) == sizeof(__True);};

上例比較復雜,我們依次進(jìn)行討論。

首先,我們聲明了兩個(gè)大小不同的類(lèi)型,作為假想的“布爾值”。也許你會(huì )有疑問(wèn),這里為什么不使用int或double之類(lèi)的類(lèi)型作為False?這是由于C語(yǔ)言并未規定“int、double必須比char大”,故為了“強行滿(mǎn)足標準”(你完全可以認為這是某種“教條主義或形式主義”),這里采用了“兩個(gè)char一定比一個(gè)char大一倍”這一簡(jiǎn)單道理,定義了False。

然后,我們聲明了一個(gè)所謂的“稻草人函數”,這個(gè)看似毫無(wú)意義的函數甚至沒(méi)有函數體(因為并不需要,且接下來(lái)的兩個(gè)函數也沒(méi)有函數體,與此函數同理)。這個(gè)函數唯一的目的就是“獲得”一個(gè)A類(lèi)型的值“給sizeof看”。由于sizeof的不求值特性,此函數也就不需要(我們也無(wú)法提供)函數體了。那么,為什么不直接使用形如“T()”這樣的寫(xiě)法,而需要聲明一個(gè)“稻草人函數”呢?我想,不用我說(shuō)你就已經(jīng)明白原因了:這是因為并不是所有的T都具有默認構造函數,而如果T沒(méi)有默認構造函數,那么“T()”就是錯誤的。

接下來(lái)是最關(guān)鍵的部分,我們聲明了一對重載函數,這兩個(gè)函數的區別有二:

  1. 返回值不同,一個(gè)是sizeof的結果為1的值,而另一個(gè)是sizeof的結果為2的值
  2. 形參不同,一個(gè)是B,一個(gè)是“...”

也就是說(shuō),如果我們給這一對重載函數傳入一個(gè)A類(lèi)型的值時(shí),由于“...”參數的重載確定優(yōu)先級低于其他一切可行的重載版本,只要A到B的隱式類(lèi)型轉換能夠發(fā)生,重載確定的結果就一定是調用第一個(gè)版本的函數,返回值為_(kāi)_True;否則,只有當A到B的隱式類(lèi)型轉換真的不可行時(shí),編譯器才會(huì )“被迫”選擇那個(gè)編譯器“最不喜歡的版本”,從而使得返回值為_(kāi)_False。返回值的不同,就能夠直接體現在sizeof的結果不同上。所以,只需要判定sizeof(__Test(__A()))是多少,就能夠達到我們最終的目的了。下面請看使用示例:

int main(){    cout << IsCastable<intdouble>::Value << endl;  // true    cout << IsCastable<int, string>::Value << endl;  // false}

可以看出,輸出結果完全符合我們的預期。

2.3 SFINAE

SFINAE(Substitution Failure Is Not An Error,替換失敗并非錯誤)是一個(gè)高級模板技巧。首先,讓我們來(lái)分析這一拗口的詞語(yǔ):“替換失敗并非錯誤”。

什么是“替換”?這里的替換,實(shí)際上指的正是模板實(shí)例化;也就是說(shuō),當模板實(shí)例化失敗時(shí),編譯器并不認為這是一個(gè)錯誤。這句話(huà)看上去似乎莫名其妙,也許你會(huì )有疑問(wèn):那怎么樣才認為是一個(gè)錯誤?我們又為什么要討論一個(gè)“錯誤的東西”呢?讓我們以一個(gè)問(wèn)題引出這一技巧的意義:

如何判定一個(gè)類(lèi)型是否是一個(gè)類(lèi)類(lèi)型?

“哇!這個(gè)問(wèn)題似乎比上一個(gè)問(wèn)題更難??!”也許你會(huì )這么想。不過(guò)有了上一個(gè)問(wèn)題的鋪墊,這里我們依然要思考的是:一個(gè)類(lèi)類(lèi)型,有什么獨一無(wú)二的東西是非類(lèi)類(lèi)型所沒(méi)有的?(這樣我們似乎就能讓編譯器在“喜歡和不喜歡”之間做出抉擇)

也許你將恍然大悟:類(lèi)的成員指針。

請看以下示例:

template <typename T>class IsClass{private:    // 定義兩個(gè)內存大小不一樣的類(lèi)型,作為“布爾值”    typedef char __True;    typedef struct { char _[2]; } __False;    // 僅當T是一個(gè)類(lèi)類(lèi)型時(shí),“int T::*”才是存在的,從而這個(gè)泛型函數的實(shí)例化才是可行的    // 否則,就將觸發(fā)SFINAE    template <typename U>    static __True __Test(int U::*);    // 僅當觸發(fā)SFINAE時(shí),編譯器才會(huì )“被迫”選擇這個(gè)版本    template <typename U>    static __False __Test(...);public:    // 根據重載確定的結果,就能夠判定出T是否為類(lèi)類(lèi)型    static constexpr bool Value = sizeof(__Test<T>(0)) == sizeof(__True);};

同樣,我們首先定義了兩個(gè)內存大小一定不一樣的類(lèi)型,作為假想的“布爾值”。然后,我們聲明了兩個(gè)重載模板,其分別以?xún)蓚€(gè)“布爾值”作為返回值。這里的關(guān)鍵在于,重載模板的參數,一個(gè)是類(lèi)成員指針,另一個(gè)是“...”。顯然,當編譯器拿到一個(gè)T,并準備生成一個(gè)“T::*”時(shí),僅當T是一個(gè)類(lèi)類(lèi)型時(shí),這一生成才是正確的,合乎語(yǔ)法的;否則,這個(gè)函數簽名將根本無(wú)法被生成出來(lái),從而進(jìn)一步的使得編譯器“被迫”選擇那個(gè)“最不喜歡的版本”進(jìn)行調用(而不是認為這個(gè)“根本無(wú)法被生成出來(lái)”的模板是一個(gè)錯誤)。所以,通過(guò)sizeof對__Test的返回值大小進(jìn)行判定,就能夠達到我們最終的目的了。下面請看使用示例:

int main(){    cout << IsClass<double>::Value << endl;  // false    cout << IsClass<string>::Value << endl;  // true}

可以看出,輸出結果完全符合我們的預期。

2.4 本章后記

sizeof,作為一個(gè)C語(yǔ)言的“入門(mén)級”語(yǔ)法,其“永不求值”的特性往往被我們所忽略。本章中,我們充分利用了sizeof的這種“永不求值”的特性,做了很多“表面工程”,僅僅是為了“給sizeof看”;同理,SFINAE技術(shù)似乎也只是在“找編譯器的麻煩,拿編譯器尋開(kāi)心”。但正是這些“表面工程、找麻煩、尋開(kāi)心”,讓我們得以實(shí)現了一些非常不可思議的功能。

3 類(lèi)型萃取器——Type Traits

Traits,中文翻譯為“特性”,Type Traits,即為“類(lèi)型的特性”。這是個(gè)十分奇怪的翻譯,故很多書(shū)籍對這個(gè)詞選擇不譯,也有書(shū)籍將其翻譯為“類(lèi)型萃取器”,十分生動(dòng)形象。

Type Traits的定義較為模糊,其大致代表了這樣的一系列技術(shù):通過(guò)一個(gè)類(lèi)型T,取得另一個(gè)基于T進(jìn)行加工后的類(lèi)型,或對T基于某一標準進(jìn)行分類(lèi),得到分類(lèi)結果。

本章中,我們以幾個(gè)經(jīng)典的Type Traits應用,來(lái)見(jiàn)識一番此技術(shù)的精妙。

3.1 為T(mén)“添加星號”

第一個(gè)例子較為簡(jiǎn)單:我們需要得到T的指針類(lèi)型,即:得到“T *”。此時(shí),只需要將“T *”通過(guò)typedef變?yōu)門(mén)ype Traits類(lèi)的結果即可。請看以下示例:

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 *}

這段代碼十分簡(jiǎn)單,但似乎我們寫(xiě)了兩遍“一模一樣”的代碼?認真觀(guān)察和思考即可發(fā)現:特化版本是為了防止一個(gè)已經(jīng)是指針的類(lèi)型發(fā)生“升級”而存在的。如果T已經(jīng)是一個(gè)指針類(lèi)型,則Type就是T本身,否則,Type才是“T *”。

3.2 為T(mén)“去除星號”

上一節,我們實(shí)現了一個(gè)能夠為T(mén)“添加星號”的Traits,這一節,我們將實(shí)現一個(gè)功能與之相反的Traits:為T(mén)“去除星號”。

“簡(jiǎn)單!”也許你會(huì )想,并很快給出了以下實(shí)現:

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}

似乎完成了?不幸的是,這一實(shí)現并不完美。請看以下示例:

int main(){    cout << typeid(RemoveStar<int **>::Type).name() << endl;  // int *,哦不!}

可以看到,我們的上述實(shí)現只能去除一個(gè)星號,當傳入一個(gè)多級指針時(shí),并不能得到我們想要的結果。

這該如何是好?我們不禁想到:如果能夠實(shí)現一個(gè)“while循環(huán)”,就能去除所有的星號了。雖然模板沒(méi)有while循環(huán),但我們知道:遞歸正是循環(huán)的等價(jià)形式。請看以下示例:

// 遞歸終點(diǎn),此時(shí)T真的不是指針了template <typename T>struct RemoveStar { typedef T Type; };// 當T是指針時(shí),Type應該是T本身(已經(jīng)去除了一個(gè)星號)繼續RemoveStar的結果template <typename T>struct RemoveStar<T *> { typedef typename RemoveStar<T>::Type Type; };

上述實(shí)現中,當發(fā)現T選擇了特化版本(即T本身是指針時(shí)),就會(huì )遞歸地對T進(jìn)行去星號,直到T不再選擇特化版本,從而抵達遞歸終點(diǎn)為止。這樣,就能在面對多級指針時(shí),也能夠得到正確的Type。下面請看使用示例:

int main(){    cout << typeid(RemoveStar<int **********>::Type).name() << endl;  // int}

可以看出,輸出結果完全符合我們的預期。

顯然,使用這樣的Traits是具有潛在的較大代價(jià)的。例如上例中,為了去除一個(gè)十級指針的星號,編譯器竟然需要實(shí)例化出11個(gè)類(lèi)!但好在這一切均發(fā)生在編譯期,對運行效率不會(huì )產(chǎn)生任何影響。

3.3 尋找“最強大類(lèi)型”

讓我們繼續討論前言中的Plus函數,以引出本節所要討論的話(huà)題。目前我們給出的“最好實(shí)現”如下:

template <typename T>Plus(T lhs, T rhs){    return lhs + rhs;}int main(){    cout << Plus(12) << endl;  // 3,正確!}

但是,只要在上述代碼中添加一個(gè)“.”,就立即發(fā)生了問(wèn)題:

int main(){    cout << Plus(12.) << endl;  // 二義性錯誤!T應該是int還是double?}

上例中,由于Plus模板只使用了單一的一個(gè)模板參數,故要求兩個(gè)實(shí)參的類(lèi)型必須一致,否則,編譯器就不知道T應該是什么類(lèi)型,從而引發(fā)二義性錯誤。但顯然,任何的兩種“數”之間都應該是可以做加法的,所以不難想到,我們應該使用兩個(gè)而不是一個(gè)模板參數,分別作為lhs與rhs的類(lèi)型,但是,我們立即就遇到了新的問(wèn)題。請看以下示例:

template <typename T1, typename T2>/* 這里應該寫(xiě)什么?*/ Plus(T1 lhs, T2 rhs){    return lhs + rhs;}

應該寫(xiě)T1?還是T2?顯然都不對。我們應該尋求一種方法,其能夠獲取到T1與T2之間的“更強大類(lèi)型”,并將此“更強大類(lèi)型”作為返回值。進(jìn)一步的,我們可以以此為基礎,實(shí)現出一個(gè)能夠獲取到任意數量的類(lèi)型之中的“最強大類(lèi)型”的方法。

應該怎么做呢?事實(shí)上,這個(gè)問(wèn)題的解決方案,確實(shí)是難以想到的。請看以下示例:

template <typename A, typename B>class StrongerType{private:    // 稻草人函數    static A __A();    static B __B();public:    // 3目運算符表達式的類(lèi)型就是“更強大類(lèi)型”    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}

上例中,我們首先定義了兩個(gè)“稻草人函數”,用以分別“獲取”類(lèi)型為A或B的值“給decltype看”。然后,我們使用了decltype探測三目運算符表達式的類(lèi)型,不難發(fā)現,decltype也具有sizeof的“不對表達式進(jìn)行求值”的特性。由于三目運算符表達式從理論上可能返回兩個(gè)值中的任意一個(gè),故表達式的類(lèi)型就是我們所尋求的“更強大類(lèi)型”。隨后的用例也證實(shí)了這一點(diǎn)。

有了獲取兩個(gè)類(lèi)型之間的“更強大類(lèi)型”的Traits以后,我們不難想到:N個(gè)類(lèi)型之中的“最強大類(lèi)型”,就是N - 1個(gè)類(lèi)型之中的“最強大類(lèi)型”與第N個(gè)類(lèi)型之間的“更強大類(lèi)型”。請看以下示例:

// 原型// 通過(guò)typename StrongerType<Types...>::Type獲取Types...中的“最強大類(lèi)型”template <typename... Types>class StrongerType;// 只有一個(gè)類(lèi)型template <typename T>class StrongerType<T>{    // 我自己就是“最強大的”    typedef T Type;};// 只有兩個(gè)類(lèi)型template <typename A, typename B>class StrongerType<A, B>{private:    // 稻草人函數    static A __A();    static B __B();public:    // 3目運算符表達式的類(lèi)型就是“更強大類(lèi)型”    typedef decltype(true ? __A() : __B()) Type;};// 不止兩個(gè)類(lèi)型template <typename T, typename... Types>class StrongerType<T, Types...>{public:    // T和typename StrongerType<Types...>::Type之間的“更強大類(lèi)型”就是“最強大類(lèi)型”    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}

通過(guò)遞歸,我們使得所有的類(lèi)型共同參與了“打擂臺”,這里的“擂臺”,就是我們已經(jīng)實(shí)現了的StrongerType的雙類(lèi)型版本,而“打擂臺的最后大贏(yíng)家”,則正是我們所尋求的“最強大類(lèi)型”。

有了StrongerType這一Traits后,我們就可以實(shí)現上文中的雙類(lèi)型版本的Plus函數了。請看以下示例:

// Plus函數的返回值應該是T1與T2之間的“更強大類(lèi)型”template <typename T1typename T2>typename StrongerType<T1, T2>::Type Plus(T1 lhs, T2 rhs){    return lhs + rhs;}int main(){    Plus(1, 2.);  // 完美!}

至此,我們“終于”實(shí)現了一個(gè)最完美的Plus函數。

3.4 本章后記

本章所實(shí)現的三個(gè)小工具,都是STL的type_traits庫的一部分。值得一提的是我們最后實(shí)現的獲取“最強大類(lèi)型”的工具:這一工具所解決的問(wèn)題,實(shí)際上是一個(gè)非常經(jīng)典的問(wèn)題,其多次出現在多部著(zhù)作中。由于decltype(以及可變參數模板)是C++11的產(chǎn)物,故很多較老的書(shū)籍對此問(wèn)題給出了“無(wú)解”的結論,或只能給出一些較為牽強的解決方案。

4 “壓榨”編譯器——編譯期計算

值也能成為模板參數的一部分,而模板參數是編譯期常量,這二者的結合使得通過(guò)模板進(jìn)行(較復雜的)編譯期計算成為了可能。由于編譯器本就不是“計算器”,故標題中使用了“壓榨”一詞,以表達此技術(shù)的“高昂的編譯期代價(jià)”以及“較大的局限性”的特點(diǎn);同時(shí),合理的利用編譯期計算技術(shù),能夠極大地提高程序的效率,故“壓榨”也有“壓榨性能”之意。

本章中,我們以一小一大兩個(gè)示例,來(lái)討論編譯期計算這一巧妙技術(shù)的應用。

4.1 編譯期計算階乘

編譯期計算階乘是編譯期計算技術(shù)的經(jīng)典案例,許多書(shū)籍對此均有討論(往往作為“模板元編程”一章的首個(gè)案例)。那么首先,讓我們來(lái)看看一個(gè)普通的階乘函數的實(shí)現:

int Factorial(int N){    return N == 1 ? 1 : N * Factorial(N - 1);}

這個(gè)實(shí)現很簡(jiǎn)單,這里就不對其進(jìn)行詳細討論了。下面,我們來(lái)看看如何將這個(gè)函數“翻譯”為一個(gè)編譯期就進(jìn)行計算并得到結果的“函數”。請看以下示例:

// 遞歸起點(diǎn)template <int N>struct Factorial{    static constexpr int Value = N * Factorial<N - 1>::Value;};// 遞歸終點(diǎn)template <>struct Factorial<1>{    static constexpr int Value = 1;};int main(){    cout << Factorial<4>::Value;  // 編譯期就能獲得結果}

觀(guān)察上述代碼,不難總結出我們的“翻譯”規則:

  1. 形參N(運行時(shí)值)變?yōu)榱四0鍏礜(編譯期值)
  2. “N == 1”這樣的“if語(yǔ)句”變?yōu)榱四0逄鼗?/li>
  3. 遞歸變?yōu)榱藙?chuàng )造一個(gè)新的模板(Factorial<N - 1>),這也意味著(zhù)循環(huán)也可以通過(guò)此種方式實(shí)現
  4. “return”變?yōu)榱艘粋€(gè)static constexpr變量

上述四點(diǎn)“翻譯”規則幾乎就是編譯期計算的全部技巧了!接下來(lái),就讓我們以一個(gè)更復雜的例子來(lái)繼續討論這一技術(shù)的精彩之處:編譯期分數的實(shí)現。

4.2 編譯期分數

分數,由分子和分母組成。有了上一節的鋪墊,我們不難發(fā)現:分數正是一個(gè)可以使用編譯期計算技術(shù)的極佳場(chǎng)合。所以首先,我們需要實(shí)現一個(gè)編譯期分數類(lèi)。編譯期分數類(lèi)的實(shí)現非常簡(jiǎn)單,我們只需要通過(guò)一個(gè)“構造函數”將模板參數保留下來(lái),作為靜態(tài)數據成員即可。請看以下示例:

template <long long __Numerator, long long __Denominator>struct Fraction{    // “構造函數”    static constexpr long long Numerator   = __Numerator;    static constexpr long long Denominator = __Denominator;    // 將編譯期分數轉為編譯期浮點(diǎn)數    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<>();}

由使用示例可見(jiàn):編譯期分數的“實(shí)例化”只需要一個(gè)typedef即可;并且,我們也能通過(guò)一個(gè)編譯期分數得到一個(gè)編譯期浮點(diǎn)數。

讓我們繼續討論下一個(gè)問(wèn)題:如何實(shí)現約分和通分?

顯然,約分和通分需要“求得兩個(gè)數的最大公約數和最小公倍數”的算法。所以,我們首先來(lái)看看這兩個(gè)算法的“普通”實(shí)現:

// 求得兩個(gè)數的最大公約數long long GreatestCommonDivisor(long long lhs, long long rhs){    return rhs == 0 ? lhs : GreatestCommonDivisor(rhs, lhs % rhs);}// 求得兩個(gè)數的最小公倍數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 LHSlong long RHS>struct __GreatestCommonDivisor{    static constexpr long long __Value = __GreatestCommonDivisor<RHS, LHS % RHS>::__Value;};// 對應于“return rhs == 0 ? lhs : ...”部分template <long long LHS>struct __GreatestCommonDivisor<LHS, 0>{    static constexpr long long __Value = LHS;};// 對應于“return lhs * rhs / GreatestCommonDivisor(lhs, rhs)”部分template <long long LHSlong long RHS>struct __LeastCommonMultiple{    static constexpr long long __Value = LHS * RHS /        __GreatestCommonDivisor<LHS, RHS>::__Value;};

有了上面的這兩個(gè)工具,我們就能夠實(shí)現出通分和約分了。首先,我們可以改進(jìn)一開(kāi)始的Fraction類(lèi),在“構造函數”中加入“自動(dòng)約分”功能。請看以下示例:

template <long long __Numerator, long long __Denominator>struct Fraction{    // 具有“自動(dòng)約分”功能的“構造函數”    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;}

可以看出,我們只需在“構造函數”中添加對分子、分母同時(shí)除以其最大公約數的運算,就能夠實(shí)現“自動(dòng)約分”了。

接下來(lái),我們來(lái)實(shí)現分數的四則運算功能。顯然,分數的四則運算的結果還是一個(gè)分數,故我們只需要通過(guò)using,將“四則運算模板”與“等價(jià)的結果分數模板”連接起來(lái)即可實(shí)現。請看以下示例:

// FractionAdd其實(shí)就是一個(gè)特殊的編譯期分數模板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    // 自動(dòng)約分>;// FractionMinus其實(shí)也是一個(gè)特殊的編譯期分數模板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    // 自動(dòng)約分>;// FractionMultiply其實(shí)也是一個(gè)特殊的編譯期分數模板template <typename LHS, typename RHS>using FractionMultiply = Fraction<    // 分子與分子相乘    LHS::Numerator * RHS::Numerator,    // 分母與分母相乘    LHS::Denominator * RHS::Denominator    // 自動(dòng)約分>;// FractionDivide其實(shí)也是一個(gè)特殊的編譯期分數模板template <typename LHS, typename RHS>using FractionDivide = Fraction<    // 分子與分母相乘    LHS::Numerator * RHS::Denominator,    // 分母與分子相乘    LHS::Denominator * RHS::Numerator    // 自動(dòng)約分>;int main(){    // 1/2    typedef Fraction<12> OneTwo;    // 2/3    typedef Fraction<23> 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;}

由此可見(jiàn),所謂的四則運算,實(shí)際上就是一個(gè)針對Fraction的using(模板不能使用typedef,只能使用using)罷了。

最后,我們實(shí)現分數的比大小功能。這非常簡(jiǎn)單:只需要先對分母通分,再對分子進(jìn)行比大小即可。而比大小的結果,就是“比大小模板”的一個(gè)數據成員。請看以下示例:

// 這六個(gè)模板都進(jìn)行“先通分,再比較”運算,唯一的區別就在于比較操作符的不同// “operator==”template <typename LHS, typename RHS>struct FractionEqual{    static constexpr bool Value =        LHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__LValue ==        RHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__RValue;};// “operator!=”template <typename LHS, typename RHS>struct FractionNotEqual{    static constexpr bool Value =        LHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__LValue !=        RHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__RValue;};// “operator<”template <typename LHS, typename RHS>struct FractionLess{    static constexpr bool Value =        LHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__LValue <        RHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__RValue;};// “operator<=”template <typename LHS, typename RHS>struct FractionLessEqual{    static constexpr bool Value =        LHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__LValue <=        RHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__RValue;};// “operator>”template <typename LHS, typename RHS>struct FractionGreater{    static constexpr bool Value =        LHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__LValue >        RHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__RValue;};// “operato>=”template <typename LHS, typename RHS>struct FractionGreaterEqual{    static constexpr bool Value =        LHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__LValue >=        RHS::Numerator * __CommonPoints<LHS::Denominator, RHS::Denominator>::__RValue;};int main(){    // 1/2    typedef Fraction<1, 2> OneTwo;    // 2/3    typedef Fraction<2, 3> TwoThree;    // 1/2 == 2/3 => false    cout << FractionEqual<OneTwo, TwoThree>::Value << endl;    // 1/2 != 2/3 => true    cout << FractionNotEqual<OneTwo, TwoThree>::Value << endl;    // 1/2 < 2/3 => true    cout << FractionLess<OneTwo, TwoThree>::Value << endl;    // 1/2 <= 2/3 => true    cout << FractionLessEqual<OneTwo, TwoThree>::Value << endl;    // 1/2 > 2/3 => false    cout << FractionGreater<OneTwo, TwoThree>::Value << endl;    // 1/2 >= 2/3 => false    cout << FractionGreaterEqual<OneTwo, TwoThree>::Value << endl;}

至此,編譯期分數的全部功能就都實(shí)現完畢了。不難發(fā)現,在編譯期分數的使用過(guò)程中,我們全程使用的都是typedef,并沒(méi)有真正的構造任何一個(gè)分數,一切計算都已經(jīng)在編譯期完成了。

4.3 本章后記

讀完本章,也許你會(huì )恍然大悟:“哦!原來(lái)模板也能夠表達形參、if、while、return等語(yǔ)義!”,進(jìn)而,也許你會(huì )有疑問(wèn):“那既然這樣,豈不是所有的計算函數都能換成編譯期計算了?”。

很可惜,答案是否定的。

我們通過(guò)對編譯期計算這一技術(shù)的優(yōu)缺點(diǎn)進(jìn)行總結,從而回答這個(gè)問(wèn)題。編譯期計算的目的,是為了完全消除運行時(shí)代價(jià),從而在高性能計算場(chǎng)合極大的提高效率;但此技術(shù)的缺點(diǎn)也是很多且很明顯的:首先,僅僅為了進(jìn)行一次編譯期計算,就有可能進(jìn)行很多次的模板實(shí)例化(比如,為了計算10的階乘,就要實(shí)例化出10個(gè)Factorial類(lèi)),這是一種極大的潛在的編譯期代價(jià);其次,并不是任何類(lèi)型的值都能作為模板參數,如浮點(diǎn)數(雖然我們可以使用編譯期分數間接的規避這一限制)、以及任何的類(lèi)類(lèi)型值等均不可以,這就使得編譯期計算的應用幾乎被限定在只需要使用整型和布爾類(lèi)型的場(chǎng)合中;最后,“遞歸實(shí)例化”在所有的編譯器中都是有最大深度限制的(不過(guò)幸運的是,在現代編譯器中,允許的最大深度其實(shí)是比較大的)。但即使如此,由于編譯期計算技術(shù)使得我們可以進(jìn)行“搶跑”,在程序還未開(kāi)始運行時(shí),就計算出各種復雜的結果,從而極大的提升程序的效率,故此技術(shù)當然也是瑕不掩瑜的。

注:本文中的部分程序已完整實(shí)現于本文作者的Github上,列舉如下:

  1. 編譯期分數:https://github.com/yingyulou/Fraction
  2. print函數:https://github.com/yingyulou/pprint
  3. Tuple:https://github.com/yingyulou/Tuple
  4. 表達式模板:https://github.com/yingyulou/ExprTmpl

本篇完,敬請期待下篇

本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
C++多態(tài)性(靜多態(tài)和動(dòng)多態(tài))
關(guān)于接口的設計與聲明--對封裝性的理解
C 智能指針的底層實(shí)現原理
[原]C#中引用與C++中指針和引用以及參數的傳遞
Vector類(lèi)模板界面及其函數的實(shí)現
詳解C++右值引用
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久