【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/)