【C++ 筆記】模板(Templates)(下) - part 32 === 目錄(Table of Contents): [TOC] --- 很感謝你點進來這篇文章。 你好,我並不是什麼 C++、程式語言的專家,所以本文若有些錯誤麻煩請各位鞭大力一點,我極需各位的指正及指導!!本系列文章的性質主要以詼諧的口吻,一派輕鬆的態度自學程式語言,如果你喜歡,麻煩留言說聲文章讚讚吧! 模板特製化(Template Specialization) --- > It is possible in C++ to get a special behavior for a particular data type. This is called template specialization. > From GeeksForGeeks 在 C++ 中,可以為特定資料型態賦予特殊的行為,就稱為模板特製化。 簡單來說,就是可以為特定資料型態去做不一樣的事情,如下範例: ```cpp= #include <iostream> using namespace std; template <typename T> void print(T value){ cout << "一般型態" << endl; } template <> // 特製化語法 void print<int>(int value){ cout << "這是 int 型態" << endl; } int main(){ print('a'); print(123); print(14.87); return 0; } ``` Output: ``` 一般型態 這是 int 型態 一般型態 ``` 以上範例針對 int 去做模板特製化的事情,除了 int 以外的型態都是按照正常的模板去做一般的執行,只有 int 型態被特別處理。 上述 `template <> void print<int>(int value)` 的語法就稱為模板特製化語法,除了對函數做模板特製化,也可以對類別做模板特製化: ```cpp= #include <bits/stdc++.h> using namespace std; template<typename T> class MyContainer { public: void print() { cout << "Generic template" << endl; } }; template<> class MyContainer<int> { public: void print() { cout << "Specialized template for int" << endl; } }; int main(){ MyContainer <float> m1; MyContainer <int> m2; MyContainer <string> m3; m1.print(); m2.print(); m3.print(); return 0; } ``` Output: ``` Generic template Specialized template for int Generic template ``` 當建立 `MyContainer <int>` 物件時,會呼叫他的特製化版本,接下來再呼叫 `print()` 函數,會發現 int 的內容與其他型態不一樣。 非型態模板參數(Template Non-Type Arguments) --- 就是字面上意思,可以傳遞資料型態以外的參數。 但是這個參數必須要是常數,必須在編譯時期可確定(constexpr)。 可接受的型態: - 整數型態(`int`, `size_t` 等) - 列舉型態(`enum`) - 指標或參考(對函式、物件或靜態成員) - C++20 開始支援浮點數型態。 使用限制: - 傳入的值必須是編譯期常數(constexpr) - 不能使用非 constexpr 的變數作為非型態模板參數 範例(From [GeeksForGeeks](https://www.geeksforgeeks.org/cpp/templates-cpp/)): ```cpp= #include <iostream> using namespace std; // 模板第二個參數不是模板型態 // Second argument of template is // not template type template <class T, int max> int arrMin(T arr[], int n) { int m = max; for (int i = 0; i < n; i++) if (arr[i] < m) m = arr[i]; return m; } int main() { int arr1[] = {10, 20, 15, 12}; int n1 = sizeof(arr1) / sizeof(arr1[0]); char arr2[] = {1, 2, 3}; int n2 = sizeof(arr2) / sizeof(arr2[0]); // 第二個模板參數 arrMin 必須是常數 // Second template parameter // to arrMin must be a // constant cout << arrMin<int, 10000>(arr1, n1) << endl; cout << arrMin<char, 256>(arr2, n2); return 0; } ``` Output: ``` 10 1 ``` `int max` 為非型態模板參數。這支程式主要是找出陣列中的最小值。 模板參數推導(Template Argument Deduction) --- > Template argument deduction automatically deduces the data type of the argument passed to the templates. This allows us to instantiate the template without explicitly specifying the data type. > From GeeksForGeeks 模板參數推導會自動推導出傳遞給模板的參數的資料型態。這使我們能夠實例化模板而無需明確指定資料型態。 而這種自動推導特性在 C++ 17 之後才出現,如果在這之前的版本上用這個,會 throw 出例外。 而原本我們在創造模板實例的時候,是這樣子寫的: ```cpp func <int> (100); ``` 但是有自動推導的特性時,我們就不用明確指定型態 `<int>`: ```cpp func (100); ``` ### 函數模板參數推導(Function Template Arguments Deduction) 這個推導是從 C++98 開始就有的了。 範例: ```cpp= #include <iostream> using namespace std; template <typename T> T display(T value){ return value; } int main(){ cout << display(123) << endl; cout << display("Hello World!") << endl; cout << display(123.123); return 0; } ``` Output: ``` 123 Hello World! 123.123 ``` 呼叫 `display()` 的部分就在做推導了,總之就會根據傳入的參數型態去做調整。 ### 類別模板參數推導(Class Template Arguments Deduction) 該推導是於 C++ 17 後才加入的特性,基本上使用方式與函數模板參數推導相同。 範例: ```cpp= #include <bits/stdc++.h> using namespace std; template <typename T> class Box{ public: T value; Box (T val) : value(val) {} T display(){ return value; } }; int main(){ Box B1(123); Box B2(123.123); Box B3("Hello World!"); cout << B1.display() << endl; cout << B2.display() << endl; cout << B3.display(); return 0; } ``` Output: ``` 123 123.123 Hello World! ``` 在建立物件 B1、B2、B3 時,就在做自動推導的動作了,編譯器會推導出每一個值所對應的資料型態是什麼。 可變參數模板(Variadic Templates) --- 這是 C++ 11 引入的特性,之前建立的模板參數數量只能固定,有了這個特性以後,想加多少就加多少! 大致上有兩個概念需要去理解: - 模板參數包(template parameter pack):用省略號`...`表示的可變長度模板參數列表。 ```cpp= template<typename... Types> void foo(Types... params); ``` - 函數參數包(function parameter pack):與模板參數包對應的函數參數列表,也用`...`表示,如上範例的 `Types... params` 代表零個或多個函數參數。 沒錯,這個語法就是這麼直覺,所以以下是個範例: ```cpp= #include <bits/stdc++.h> using namespace std; // 基本的模板:只接收一個參數 template<typename T> T sum(T value) { return value; } // 可變參數模板:可接收多個參數 template <typename T, typename... Args> T sum (T f, Args... rest){ return f + sum(rest...); } int main(){ cout << "sum(1, 2, 3): " << sum(1, 2, 3) << endl; cout << "sum(1.5, 2.5, 3.0, 4.0): " << sum(1.5, 2.5, 3.0, 4.0) << endl; cout << "sum(10): " << sum(10) << endl; return 0; } ``` Output: ``` sum(1, 2, 3): 6 sum(1.5, 2.5, 3.0, 4.0): 11 sum(10): 10 ``` 總結 --- ### 模板特製化(Template Specialization) 模板特製化就是對特定型態提供不同的實作方式,取代一般模板的預設行為。 分有函數跟類別模板特製化。 函數: ```cpp= template <typename T> void print(T value) { ... } template <> void print<int>(int value) { ... } ``` 類別: ```cpp= template<typename T> class MyContainer { ... }; template<> class MyContainer<int> { ... }; ``` 特製化語法:`template<>`,而在後面的函數或類別名稱須加上要特製化的資料型態。 ### 非型態模板參數(Template Non-Type Arguments) 可將常數(非型態)作為模板參數。此參數必須在編譯時期即可確定。 可接受的型態: - 整數型態(`int`, `size_t` 等) - 列舉型態(`enum`) - 指標或參考(對函式、物件或靜態成員) - C++20 開始支援浮點數型態。 使用限制: - 傳入的值必須是編譯期常數(constexpr) - 不能使用非 constexpr 的變數作為非型態模板參數 範例: ```cpp= template <class T, int max> // int max 非型態模板參數 int arrMin(T arr[], int n) { ... } arrMin<int, 10000>(arr1, n1); ``` ### 模板參數推導(Template Argument Deduction) 讓編譯器自動推斷模板參數型態,可不用手動指定 `<T>` 型態。 #### 函數模板參數推導(C++98 起) 呼叫函數時自動推導: ```cpp= template <typename T> T display(T value); display(123); // 推導為 int ``` #### 類別模板參數推導(C++17 起) 建構物件時自動推導: ```cpp= template <typename T> class Box { ... }; Box b1(123); // 推導為 Box<int> ``` ### 可變參數模板(Variadic Templates) 可讓模板接收任意數量的參數。 核心概念: - 模板參數包:`template<typename... Types>` - 函數參數包:`Types... args` ```cpp= template<typename T> T sum(T value) { return value; } // 基本模板 // 以下是可變參數模板 template <typename T, typename... Args> T sum(T first, Args... rest) { return first + sum(rest...); } ``` ### 一覽表 | 特性名稱 | 語法 | 說明 | | -------- | ----------------------------- | ------------------ | | 模板特製化 | `template<>` | 為特定型態提供特製版本 | | 非型態模板參數 | `template<typename T, int N>` | 傳入常數當作模板參數 | | 函數模板參數推導 | `display(123)` | 編譯器自動推導型態 | | 類別模板參數推導 | `Box b(123)` | 建構時自動推導型態(C++17) | | 可變參數模板 | `template<typename... Args>` | 接收任意數量參數 | 參考資料 --- [Template Specialization (C++) | Microsoft Learn](https://learn.microsoft.com/en-us/cpp/cpp/template-specialization-cpp?view=msvc-170) [C++ template筆記 (六):樣板特製化 - micky85lu的創作 - 巴哈姆特](https://home.gamer.com.tw/creationDetail.php?sn=5646680) [Templates in C++ - GeeksforGeeks](https://www.geeksforgeeks.org/cpp/templates-cpp/) [Template Specialization in C++ - GeeksforGeeks](https://www.geeksforgeeks.org/template-specialization-c/)