1、重載與模板ios
函數模板能夠被另外一個模板或普通非模板函數重載。與往常同樣,名字相同的函數必須具備不一樣數量或類型的參數。數組
若是涉及函數模板,則函數匹配規則會在如下幾方面受到影響:ide
2、可變函數模板函數
一個可變參數模板就是一個接受可變數目參數的模板函數或模板類。可變數目的參數被稱爲參數包。存在兩種參數包:模板參數包,表示零個或多個模板參數;函數參數包,表示零個或多個函數參數。spa
咱們用一個省略號來指出一個模板參數或函數參數表示一個包。在一個模板參數列表中,class...或typename ...指出接下來的參數表示零個或多個類型的列表;一個類型名後面跟一個省略號表示零個或多個給定類型的非類型參數的列表。在函數參數列表中,若是一個參數的類型是一個模板參數包,則此參數包也是一個函數參數包。debug
template<typename T, typename...args>
std::ostream &print(std::ostream &os, const T &t, const args &...rest);指針
args是一個模板參數包;rest是一個函數參數包。args表示零個或多個模板類型參數,rest表示零個或多個函數參數。rest
1)sizeof...運算符code
當咱們須要知道包中有多少元素時,可使用sizeof...運算符。sizeof...返回一個常量表達式,並且不會對其實參求值。對象
template<typename T, typename...args>
std::ostream &print(std::ostream &os, const T &t, const args &...rest) {
std::cout << sizeof...(args) << std::endl; // 類型參數的數目
std::cout << sizeof...(rest) << std::endl; // 函數參數的數目
}
一、可變參數函數模板
當咱們既不知道想要處理的實參的數目也不知道它們的類型時,可變參數函數是頗有用的。
可變參數函數一般是遞歸的。第一步調用處理包中的第一個實參,而後用剩餘實參調用自身。咱們自定義的print函數也是這樣的模式,每次遞歸調用將第二個實參打印到第一個實參表示的流中。爲了終止遞歸,咱們還須要定義一個非可變參數的print函數,它接受一個流和一個對象。
1 #include <iostream> 2 #include <memory> 3 #include <string> 4 #include <vector> 5 6 template<typename T> 7 std::ostream &print(std::ostream &os, const T &t) { 8 return os << t; 9 } 10 11 template<typename T, typename...args> 12 std::ostream &print(std::ostream &os, const T &t, const args &...rest) { 13 os << t << ", "; 14 return print(os, rest...); // 遞歸調用,打印其餘實參 15 } 16 int main() 17 { 18 print(std::cout, 2, 3.3, "hello"); 19 return 0; 20 }
二、包擴展
對於一個參數包,除了獲取其大小外,咱們能對它作的惟一的事情就是擴展它。當擴展一個包時,咱們還要提供用於每一個擴展元素的模式。擴展一個包就是將它分解爲構造的元素,對每一個元素應用模式,得到擴展後的列表。咱們經過在模式右邊放一個省略號(...)來觸發擴展操做。
1 template<typename T, typename...args> 2 std::ostream &print(std::ostream &os, const T &t, const args &...rest) { 3 os << t << ", "; 4 return print(os, rest...); // 遞歸調用,打印其餘實參 5 }
對args的擴展中,編譯器將模式const args&應用到模板參數包args中的每一個元素。所以,此模式的擴展結果是一個逗號分隔的零個或多個類型的列表,每一個類型都形如const type&。
第二個擴展發生在對print的遞歸調用中。在此狀況下,模式是函數參數包的名字(即rest)。此模式擴展出一個由包中元素組成的、逗號分隔的列表。
1)理解包擴展
C++語言還容許更復雜的擴展模式。例如,咱們能夠編寫第二個可變參數,對其每一個實參調用debug_rep,而後調用print打印string。
1 #include <iostream> 2 #include <sstream> 3 #include <memory> 4 #include <string> 5 #include <vector> 6 7 template<typename T> 8 std::ostream &print(std::ostream &os, const T &t) { 9 return os << t; 10 } 11 12 template<typename T, typename...args> 13 std::ostream &print(std::ostream &os, const T &t, const args &...rest) { 14 os << t << ", "; 15 return print(os, rest...); // 遞歸調用,打印其餘實參 16 } 17 18 template<typename T> 19 std::string debug_rep(const T &t) { 20 std::ostringstream ret; 21 ret << "-" << t << "-"; 22 return ret.str(); 23 } 24 25 template<typename... args> 26 std::ostream &errorMsg(std::ostream &os, const args& ...rest) { 27 return print(os, debug_rep(rest)...); 28 } 29 int main() 30 { 31 errorMsg(std::cout, 2, 3.3, "hello"); 32 return 0; 33 }
三、轉發參數包
3、模板特例化
編寫單一模板,使之對任何可能的模板實參都是最合適的,都能實例化,這並不老是能辦到。在某些狀況下,通用模板的定義對特定類型是不合適的:通用定義可能編譯失敗或作得不正確。其餘時候,咱們也能夠利用某些特定知識來編寫更高效的代碼,而不是從通用模板實例化。當咱們不能(或不但願)使用模板版本時,能夠定義類或函數模板的一個特例化版本。
1)定義函數模板特例化
當咱們特例化一個函數模板時,必須爲原模板中的每一個模板參數都提供實參。爲了指出咱們正在實例化一個模板,應使用關鍵字template後跟一個空尖括號對(<>)。空尖括號指出咱們將爲原模板的全部模板參數提供實參。
1 template<> 2 void show(const char* const &x) { // show的特殊版本,處理字符數組的指針 3 std::cout << x << " second" << std::endl; 4 }
2)函數重載與模板實例化
當定義函數模板的特例化版本時,咱們本質上接管了編譯器的工做。即,咱們爲原模板的一個特殊實例提供了定義。重要的是要弄清:一個特例化版本本質上是一個實例,而非函數名的一個重載版本。
咱們將一個函數定義爲一個特例化版本仍是一個獨立的非模板函數,會影響到函數匹配。
注意:模板及其特例化版本應該聲明在同一個頭文件中。全部同名版本的聲明應該放在前面,而後是這些模板的特例化版本。
將函數定義爲特例化版本:
1 #include <iostream> 2 #include <sstream> 3 #include <memory> 4 #include <string> 5 #include <vector> 6 7 template<typename T> 8 void show(const T& x) { 9 std::cout << x << " first" << std::endl; 10 } 11 12 template<> 13 void show(const char* const &x) { // show的特殊版本,處理字符數組的指針 14 std::cout << x << " second" << std::endl; 15 } 16 17 template<std::size_t N> 18 void show(const char(&x)[N]) { 19 std::cout << x << " third" << std::endl; 20 } 21 int main() 22 { 23 show("hi"); 24 return 0; 25 }
將函數定義爲普通函數:
1 #include <iostream> 2 #include <sstream> 3 #include <memory> 4 #include <string> 5 #include <vector> 6 7 template<typename T> 8 void show(const T& x) { 9 std::cout << x << " first" << std::endl; 10 } 11 12 void show(const char* const &x) { 13 std::cout << x << " second" << std::endl; 14 } 15 16 template<std::size_t N> 17 void show(const char(&x)[N]) { 18 std::cout << x << " third" << std::endl; 19 } 20 int main() 21 { 22 show("hi"); 23 return 0; 24 }
3)類模板特例化
咱們還能夠特例化類模板。
1 #include <iostream> 2 #include <sstream> 3 #include <memory> 4 #include <string> 5 #include <vector> 6 7 template<typename T> 8 class Base { 9 public: 10 Base(T x):data(x){} 11 void show() { 12 std::cout << "T" << std::endl; 13 } 14 private: 15 T data; 16 }; 17 // Base的特例化版本 18 template<> 19 class Base<int> { 20 public: 21 Base(int x) :data(x) {} 22 void show() { 23 std::cout << "int" << std::endl; 24 } 25 private: 26 int data; 27 }; 28 int main() 29 { 30 Base<std::string> sb("hi"); 31 sb.show(); 32 Base<int> ib(233); 33 ib.show(); 34 return 0; 35 }
4)類模板部分特例化
與函數模板不一樣,類模板的特例化沒必要爲全部模板參數提供實參。咱們能夠指定一部分而非全部模板參數,或是參數的一部分而非所有特性。一個類模板的部分特例化自己是一個模板,使用它時用戶還必須爲那些在特例化版本中未指定的模板參數提供實參。
咱們只能夠部分特例化類模板,而不能部分特例化函數模板。
因爲一個部分特例化版本本質是一個模板,與往常同樣,咱們首先定義模板參數。相似任何其餘特例版本,部分特例化版本的名字與原模板的名字相同。對每一個未徹底肯定類型的模板參數,在特例化版本的模板參數列表中都有一項與之相應。在類名以後,咱們要爲特例化的模板參數指定實參,這些實參列於模板名以後的尖括號中。這些實參與原始模板中的參數按位置對應。
5)特例化成員
咱們能夠只特例化成員函數而不是特例化整個模板。
1 #include <iostream> 2 #include <sstream> 3 #include <memory> 4 #include <string> 5 #include <vector> 6 7 template<typename T> 8 class Base { 9 public: 10 Base(T x):data(x){} 11 void show() { 12 std::cout << "T" << std::endl; 13 } 14 private: 15 T data; 16 }; 17 18 template<> 19 void Base<int>::show() { 20 std::cout << "int" << std::endl; 21 } 22 int main() 23 { 24 Base<std::string> sb("hi"); 25 sb.show(); 26 Base<int> ib(233); 27 ib.show(); 28 return 0; 29 }