經常地,你會撰寫相同演算流程的函式,雖然參數型態不同,然而物件的協定相同:
bool greaterThan(int a, int b) {
return a > b;
}
bool greaterThan(string a, string b) {
return a > b;
}
這時就會希望,參數的型態也可以…呃…參數化,也就是 a
、b
的型態可以在呼叫時指定,而不是如上寫死了兩個版本,這時可以使用函式模版:
#include <iostream>
using namespace std;
template <typename T>
bool greaterThan(T a, T b) {
return a > b;
}
int main() {
// 顯示 0
cout << greaterThan(10, 20) << endl;
// 顯示 1
cout << greaterThan(string("xyz"), string("abc")) << endl;
return 0;
}
在這個範例中,greaterThan
是個函式模版(function template),或稱為泛型函式(generic function),定義模版時使用 template
,之後跟著模版參數列,typename
定義了一個模版參數 T
,若有多個模版參數,各自都要使用 typename
來定義,每個模版參數以逗號區隔。
程式碼 greaterThan(10, 20)
建立了一個模版的實例(instance),相當於 greaterThan<int>(10, 20)
,而 greaterThan(string("xyz"), string("abc"))
建立了另一個模版實例,相當於 greaterThan(string("xyz"), string("abc"))
。
建立一個模版實例的意思是,編譯器推斷出 T
的型態,產生並編譯了一個對應版本,這就是之所以名為模版的原因,也就是說編譯器以你定義的模版為基礎,為 greaterThan(10, 20)
建立了 bool greaterThan(int, int)
,為 greaterThan(string("xyz"), string("abc"))
建立了 bool greaterThan(string, string)
。
如果有某個版本,不想要編譯器建立,而想要自行實作呢?可以明確地定義特化版本:
#include <iostream>
using namespace std;
template <typename T>
bool greaterThan(T a, T b) {
return a > b;
}
template <>
bool greaterThan(string s1, string s2) {
return s1.size() > s2.size();
}
int main() {
cout << greaterThan(10, 20) << endl;
cout << greaterThan(string("xyz"), string("abc")) << endl;
return 0;
}
在這個例子中,也許你想比的是字串的長度而不是字典順序,為此建立了特化版本,因此編譯器就不會自行建立 bool greaterThan(string, string)
的版本,而是使用你定義的特化版本,執行結果就都顯示 0 了。
現在來看看一個需求,傳遞陣列給函式時會使用指標,一個例子是:
#include <iostream>
using namespace std;
void printAll(int *arr, int len) {
for(int i = 0; i < len; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
int main() {
int arr1[] = {1, 2};
int arr2[] = {3, 4, 5};
printAll(arr1, 2);
printAll(arr2, 3);
return 0;
}
如果想在 printAll
中使用 for range 可行嗎?就上例來說沒辦法,根據〈指標與陣列〉與〈指標的指標〉的說明,必須改為以下:
#include <iostream>
using namespace std;
void printAll(int (*arr)[2]) {
for(auto elem : *arr) {
cout << elem << " ";
}
cout << endl;
}
void printAll(int (*arr)[3]) {
for(auto elem : *arr) {
cout << elem << " ";
}
cout << endl;
}
int main() {
int arr1[] = {1, 2};
int arr2[] = {3, 4, 5};
printAll(&arr1);
printAll(&arr2);
return 0;
}
問題是解決了,然而也在參數上寫死了陣列長度,仔細看看兩個函式的實作內容是相同的,這時你會想,編譯器可以為函式模版推斷出對應型態的版本,那可否推斷出陣列長度的值呢?可以喔!
#include <iostream>
using namespace std;
template <typename T, int L>
void printAll(T (*arr)[L]) {
for(auto elem : *arr) {
cout << elem << " ";
}
cout << endl;
}
int main() {
int arr1[] = {1, 2};
int arr2[] = {3, 4, 5};
printAll(&arr1);
printAll(&arr2);
return 0;
}
在上例中,L
並不是以 typename
定義,而是 int
,這稱為模版的非型態參數(nontype parameter),編譯器會試著為非型態參數推斷出一個值,值的推斷來源必須是個常數運算式,也就是靜態時期可決定的值。
上例可以使用參考,令呼叫時更直覺一些,例如:
#include <iostream>
using namespace std;
template <typename T, int L>
void printAll(T (&arr)[L]) {
for(auto elem : arr) {
cout << elem << " ";
}
cout << endl;
}
int main() {
int arr1[] = {1, 2};
int arr2[] = {3, 4, 5};
printAll(arr1);
printAll(arr2);
return 0;
}
實際上,T (&arr)[L]
的宣告是多此一舉,既然都用了模版了,底下照樣也行得通:
#include <iostream>
using namespace std;
template <typename T>
void printAll(T &arr) {
for(auto elem : arr) {
cout << elem << " ";
}
cout << endl;
}
int main() {
int arr1[] = {1, 2};
int arr2[] = {3, 4, 5};
printAll(arr1);
printAll(arr2);
return 0;
}