C++的模板(Templates)是為了解決類型參數化問題,允許在編寫代碼時不固定類型,而是在編譯時根據需要傳入具體類型,支持類型的參數化。

C++模板是在編譯時進行實例化的。C++編譯器根據模板的定義生成不同類型的代碼,並在編譯過程中根據實際傳入的類型生成相應的類或函數。

  • 這意味着模板是靜態的,編譯器在編譯期間確定最終的類型。
  • 通過模板,C++可以進行「零開銷抽象」,即編譯器優化掉模板相關的代碼,最終生成非常高效的機器代碼。

C++模板非常強大,可以在編譯時進行複雜的計算,這種特性被稱為模板元編程。通過SFINAE(Substitution Failure Is Not An Error)等機制,C++模板可以根據類型的特性做出條件選擇。可以通過遞歸模板、SFINAE、類型萃取等方式實現強大的功能。

C++11及之後的版本引入了變長模板參數(Variadic Templates),允許模板參數的數量是可變的,這使得C++可以編寫更加靈活的泛型代碼。例如,使用std::tuple和std::make_tuple等功能。

模板形參

編輯

模板定義以關鍵字template開始,後接模板形參表(template parameter list),模板形參表是用尖括號括住的一個或者多個模板形參的列表,形參之間以逗號分隔。模板形參可以是:

  • 表示類型的類型形參(type parameter)。類型形參跟在關鍵字class或typename之後聲明。不允許cv限定。在 C++11 之前,類型的模板實參不允許是局部類型(local type)、無鏈接性的類型(type with no linkage)、無名類型(unnamed type)或包括了這三種情形的複合類型。因為這些類型不具備全局鏈接性或命名。然而,C++11 以後,這些限制被放寬,允許使用局部類型、無名類型等作為模板參數,使得模板編程更加靈活和強大。
  • 表示常量表達式的非類型形參(non-type parameter)。非類型形參跟在類型說明符之後聲明。允許用const或volatile限定。不允許聲明為浮點型、class類型、void型。允許聲明為下述形式:
    • 整型或枚舉型
    • 到對象的指針或函數指針,必須是常量指針
    • 到對象的引用或函數引用,必須是常量引用
    • 成員指針
    • nullptr_t
    • 模板的非類型參數被聲明為數組或函數的,將被decay為指針或函數指針。例如:

模板特化

編輯

模板特化允許對特定類型做優化,或者去修正模板對某一特定類型實例化之後的行為。

模板特化分為:

  • 全特化:
  • 偏特化:

別名模板

編輯

別名模板(aliase template)是C++11引入的技術。在C++03標準中,可以用typedef給全特化模板定義新的類型名。但是不允許用typedef施加於偏特化模板上。C++11增加了給偏特化模板增加別名的功能,例如:

template <typename First, typename Second, int Third>
class SomeType;

template <typename Second>
using TypedefName = SomeType<OtherType, Second, 5>;

//using在C++11中也可用于其他的类型别名的声明:
typedef void (*FunctionType)(double);       // Old style
using FunctionType1 = void (*)(double); // New introduced syntax

默認模板參數

編輯

模板形參可以給出默認值(default arguments for template parameters)。如果一個模板參數給出了默認值,那麼模板形參列表中在其後聲明的模板參數都應該給出默認值。例如:

template<class T = char, class U, class V = int> class X { }; //编译出错,或者给出U的默认值,或者不给出T的默认值

可變參數模板

編輯

省略號(...)在可變參數模板中有兩種用途:

  • 省略號出現在形參名字左側,聲明了一個參數包(parameter pack)[1]。使用這個參數包,可以綁定0個或多個模板實參給這個模板參數包。參數包也可以用於非類型的模板參數。
  • 省略號出現在包含參數包的表達式的右側,稱作參數包展開(parameter pack expansion),是把這個參數包解開為一組實參,使得在省略號前的整個表達式使用每個被解開的實參完成求值,所有表達式求值結果被逗號分開。這種表達式必須是可接受任意個數的以逗號分開的子表達式。注意這裡的逗號不是作為逗號運算符。

如果在一個參數包展開中同時出現了兩個參數包的名字,則二者在一起同時展開,且應該具有相同長度。這可能出現在類模板帶有一個參數包,它的嵌套的類模板或成員函數模板帶有另一個參數包。