【C++ 筆記】模板(Templates)(下) - part 32

很感謝你點進來這篇文章。

你好,我並不是什麼 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++ 中,可以為特定資料型態賦予特殊的行為,就稱為模板特製化。

簡單來說,就是可以為特定資料型態去做不一樣的事情,如下範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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:

1
2
3
一般型態
這是 int 型態
一般型態

以上範例針對 int 去做模板特製化的事情,除了 int 以外的型態都是按照正常的模板去做一般的執行,只有 int 型態被特別處理。

上述 template <> void print<int>(int value) 的語法就稱為模板特製化語法,除了對函數做模板特製化,也可以對類別做模板特製化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#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:

1
2
3
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#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:

1
2
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 出例外。

而原本我們在創造模板實例的時候,是這樣子寫的:

1
func <int> (100);

但是有自動推導的特性時,我們就不用明確指定型態 <int>

1
func (100);

函數模板參數推導(Function Template Arguments Deduction)

這個推導是從 C++98 開始就有的了。

範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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:

1
2
3
123
Hello World!
123.123

呼叫 display() 的部分就在做推導了,總之就會根據傳入的參數型態去做調整。

類別模板參數推導(Class Template Arguments Deduction)

該推導是於 C++ 17 後才加入的特性,基本上使用方式與函數模板參數推導相同。

範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#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:

1
2
3
123
123.123
Hello World!

在建立物件 B1、B2、B3 時,就在做自動推導的動作了,編譯器會推導出每一個值所對應的資料型態是什麼。

可變參數模板(Variadic Templates)

這是 C++ 11 引入的特性,之前建立的模板參數數量只能固定,有了這個特性以後,想加多少就加多少!

大致上有兩個概念需要去理解:

  • 模板參數包(template parameter pack):用省略號...表示的可變長度模板參數列表。
1
2
template<typename... Types>
void foo(Types... params);
  • 函數參數包(function parameter pack):與模板參數包對應的函數參數列表,也用...表示,如上範例的 Types... params 代表零個或多個函數參數。

沒錯,這個語法就是這麼直覺,所以以下是個範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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:

1
2
3
sum(1, 2, 3): 6
sum(1.5, 2.5, 3.0, 4.0): 11
sum(10): 10

總結

模板特製化(Template Specialization)

模板特製化就是對特定型態提供不同的實作方式,取代一般模板的預設行為。

分有函數跟類別模板特製化。

函數:

1
2
template <typename T> void print(T value) { ... }
template <> void print<int>(int value) { ... }

類別:

1
2
template<typename T> class MyContainer { ... };
template<> class MyContainer<int> { ... };

特製化語法:template<>,而在後面的函數或類別名稱須加上要特製化的資料型態。

非型態模板參數(Template Non-Type Arguments)

可將常數(非型態)作為模板參數。此參數必須在編譯時期即可確定。

可接受的型態:

  • 整數型態(int, size_t 等)
  • 列舉型態(enum
  • 指標或參考(對函式、物件或靜態成員)
  • C++20 開始支援浮點數型態。

使用限制:

  • 傳入的值必須是編譯期常數(constexpr)
  • 不能使用非 constexpr 的變數作為非型態模板參數

範例:

1
2
3
4
template <class T, int max> // int max 非型態模板參數
int arrMin(T arr[], int n) { ... }

arrMin<int, 10000>(arr1, n1);

模板參數推導(Template Argument Deduction)

讓編譯器自動推斷模板參數型態,可不用手動指定 <T> 型態。

函數模板參數推導(C++98 起)

呼叫函數時自動推導:

1
2
template <typename T> T display(T value);
display(123); // 推導為 int

類別模板參數推導(C++17 起)

建構物件時自動推導:

1
2
template <typename T> class Box { ... };
Box b1(123); // 推導為 Box<int>

可變參數模板(Variadic Templates)

可讓模板接收任意數量的參數。

核心概念:

  • 模板參數包:template<typename... Types>
  • 函數參數包:Types... args
1
2
3
4
5
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

C++ template筆記 (六):樣板特製化 - micky85lu的創作 - 巴哈姆特

Templates in C++ - GeeksforGeeks

Template Specialization in C++ - GeeksforGeeks