學習目標
讀完本章後,你應該能夠:
- 說明 為什麼需要模板,並比較模板與「複製貼上多份程式碼」、巨集(macro)、
void*三種泛型手段的優劣 - 撰寫 函式模板,理解 模板引數推導(template argument deduction) 的規則與限制
- 使用 明確模板引數、多型別參數、非型別模板參數(NTTP)
- 撰寫 類別模板,並能把成員函式定義在類別外
- 區分並實作 完整特化(full specialization) 與 部分特化(partial specialization)
- 認識 可變參數模板(variadic templates) 與 C++17 折疊表達式(fold expressions)
- 理解 模板編譯模型:為什麼模板的定義通常要放在標頭檔,什麼是 實體化(instantiation)
- 初步使用 型別特徵(
<type_traits>) 與if constexpr(C++17) - 學會 閱讀模板的錯誤訊息
- 認識 概念 Concepts(C++20) 作為下一步的進階主題
1. 為什麼需要模板?
1.1 問題:同一段邏輯、不同型別
假設我們要寫一個「回傳兩者較大值」的函式。沒有模板時,每種型別都得寫一份:
int max_int(int a, int b) { return (a > b) ? a : b; }
double max_double(double a, double b) { return (a > b) ? a : b; }
char max_char(char a, char b) { return (a > b) ? a : b; }
// ... std::string、long、自訂型別 ... 寫不完
這種「複製貼上」的做法有三大問題:
| 問題 | 說明 |
|---|---|
| 程式碼重複(DRY 違反) | 邏輯完全相同,只有型別不同,違反 Don't Repeat Yourself |
| 維護惡夢 | 一旦邏輯要修改(例如加入 log),所有版本都得手動同步,很容易漏改 |
| 無法涵蓋未來型別 | 使用者自訂的新型別你事先不可能知道,永遠補不齊 |
1.2 在模板之前:人們用什麼?
在泛型機制成熟前,C/C++ 程式設計師有兩種「土法煉鋼」的替代品。理解它們的缺陷,才能體會模板的價值。
(A) 前置處理器巨集(macro)
#define MAX(a, b) ((a) > (b) ? (a) : (b))
巨集只是「純文字替換」,編譯器在做真正的語法分析「之前」就把它展開了,因此:
int x = 5;
int y = MAX(x++, 3); // 展開成 ((x++) > (3) ? (x++) : (3))
// x++ 可能被求值兩次!結果是未定義行為般的陷阱
(B) void* + 函式指標
C 的 qsort 就是這種風格:傳入 void* 與一個比較函式。缺點是 完全沒有型別安全,要靠強制轉型,且因為無法 inline 而較慢。
1.3 三種泛型手段比較
| 特性 | 巨集 macro | void* 泛型 |
模板 template |
|---|---|---|---|
| 型別安全 | ❌ 無(純文字) | ❌ 弱(需轉型) | ✅ 完整,編譯期檢查 |
| 多次求值副作用 | ⚠️ 有風險 | ✅ 安全 | ✅ 安全 |
| 效能 | 快(inline) | 慢(無法 inline、需轉型) | 快(可 inline,零額外成本) |
| 除錯/錯誤訊息 | ❌ 難(展開後才報錯) | ⚠️ 執行期才爆 | ⚠️ 訊息冗長但是編譯期 |
| 支援自訂型別 | 勉強 | 需手動轉型 | ✅ 自然支援 |
核心觀念:模板是 編譯期的程式碼產生器(code generator)。你寫「一份藍圖」,編譯器依照實際用到的型別,自動「生出」對應的具體版本。這就是 泛型程式設計(generic programming) 的基礎,也是整個 STL 的根基。
2. 函式模板
2.1 基本語法
template <typename T>
T my_max(T a, T b) {
return (a > b) ? a : b;
}
template <typename T>是 模板參數列(template parameter list),宣告這是一個模板,T是 型別參數(type parameter)。typename可以替換成class,在此處兩者 完全等價(現代風格偏好typename,因為T不一定是 class)。T是個「佔位符」,編譯器之後會用真正的型別把它填進去。
2.2 模板引數推導(Template Argument Deduction)
最神奇也最常用的功能:你 不必明確寫出型別,編譯器會從引數推導出 T:
my_max(3, 7); // 兩個都是 int → T = int
my_max(3.14, 2.71); // 兩個都是 double → T = double
my_max('a', 'z'); // 兩個都是 char → T = char
編譯器看到 my_max(3, 7),推導出 T = int,於是「實體化(instantiate)」出一份:
// 編譯器自動產生(概念示意)
int my_max(int a, int b) { return (a > b) ? a : b; }
推導失敗的經典陷阱:當兩個引數型別不一致時,T 無法同時被推導成兩種型別:
my_max(3, 2.5); // ❌ 編譯錯誤:T 既要是 int(來自 3) 又要是 double(來自 2.5)
錯誤訊息會類似(GCC):
error: no matching function for call to 'my_max(int, double)'
note: candidate: template<class T> T my_max(T, T)
note: template argument deduction/substitution failed:
note: deduced conflicting types for parameter 'T' ('int' and 'double')
解法見下一節。
2.3 明確指定模板引數(Explicit Template Arguments)
當推導失敗、或你想強制某個型別時,用尖括號明確指定:
my_max<double>(3, 2.5); // 明確 T = double;3 會隱式轉成 3.0 → 結果 3.0
my_max<int>(3.9, 2.1); // 強制 T = int;兩者截斷成 3 和 2 → 結果 3
2.4 多個型別參數
模板可以有多個型別參數,用於處理不同型別的引數:
template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) { // 回傳型別由 a + b 推導
return a + b;
}
add(3, 2.5); // int + double → 5.5(型別為 double)
add(1.5, 2); // double + int → 3.5
-> decltype(a + b)是 尾置回傳型別(trailing return type),讓回傳型別依運算結果決定。- 在 C++14 以後,函式模板可直接用
auto推導回傳型別:template <typename T, typename U> auto add(T a, U b) { return a + b; }。
2.5 函式模板的多載與明確特化
函式模板可以被 多載(overload),也可以被 完整特化。對於函式,多載通常比特化更直觀、更不容易出錯(這是 C++ Core Guidelines 與多數專家的建議,原因見 §7.3)。
#include <cstring>
template <typename T>
T my_max(T a, T b) { return (a > b) ? a : b; }
// 完整特化:針對 const char*,用 strcmp 比較「內容」而非指標位址
template <>
const char* my_max<const char*>(const char* a, const char* b) {
return (std::strcmp(a, b) > 0) ? a : b;
}
my_max("apple", "banana"); // 呼叫特化版,比較字串內容
📖 完整可執行範例請參考 function_templates.cpp
3. 非型別模板參數(NTTP)
模板參數 不一定要是型別,也可以是 編譯期常數值:
template <typename T, int Size> // Size 是「非型別模板參數」
class FixedArray {
T data_[Size];
public:
T& operator[](int index) { return data_[index]; }
constexpr int size() const { return Size; }
};
FixedArray<int, 10> arr; // 大小為 10 的 int 陣列(大小寫死在型別裡)
FixedArray<double, 5> d; // 大小為 5 的 double 陣列
關鍵特性:Size 在 編譯期 就確定,所以 FixedArray<int, 10> 和 FixedArray<int, 5> 是 兩個完全不同的型別。std::array<T, N> 正是這樣設計的。
非型別模板參數的允許型別:
| 允許 | 範例 |
|---|---|
整數型別、bool、char |
template <int N>、template <bool B> |
| 列舉(enum) | template <Color C> |
| 指標 / 參考(指向有 linkage 的物件) | template <int* P> |
std::nullptr_t |
— |
| 浮點數、字面值類別 | C++20 才開始支援(C++17 之前不行) |
template <typename T, int N>
void print_array(const T (&arr)[N]) { // 編譯器自動推導 N
for (int i = 0; i < N; ++i) std::cout << arr[i] << ' ';
}
int nums[] = {1, 2, 3, 4, 5};
print_array(nums); // N 自動推導為 5
4. 類別模板
4.1 基本語法
template <typename T>
class Stack {
std::vector<T> data_;
public:
void push(const T& value);
void pop();
const T& top() const;
bool empty() const { return data_.empty(); }
std::size_t size() const { return data_.size(); }
};
使用時 必須明確提供型別引數(C++17 之前),因為類別模板不像函式模板那樣能從引數推導:
Stack<int> int_stack;
Stack<std::string> str_stack;
C++17 補充——CTAD(類別模板引數推導):C++17 起,某些情況下可省略尖括號,例如
std::vector v = {1, 2, 3};會推導為std::vector<int>。但初學階段建議仍明確寫出型別。
4.2 成員函式定義在類別外
把宣告與定義分開時,每一個 類別外的成員函式都要重新帶上 template <typename T> 前綴,且類別名稱要寫成 Stack<T>:
template <typename T>
void Stack<T>::push(const T& value) { // 注意 Stack<T>:: 與前綴
data_.push_back(value);
}
template <typename T>
const T& Stack<T>::top() const {
if (data_.empty())
throw std::out_of_range("Stack<T>::top(): 堆疊為空");
return data_.back();
}
4.3 預設模板參數
類別模板可以有預設型別參數(函式模板從 C++11 起也可以):
template <typename T = int, typename Container = std::vector<T>>
class SimpleList {
Container data_;
public:
void add(const T& value) { data_.push_back(value); }
};
SimpleList<> a; // T = int, Container = vector<int>
SimpleList<double> b; // T = double, Container = vector<double>
📖 完整可執行範例請參考 class_templates.cpp
5. 模板特化
「特化」就是:為某些特定型別提供和通用版本不同的實作。
5.1 完整特化(Full Specialization)
把 所有 模板參數都填上具體型別。函式模板與類別模板都支援。
// 通用版本
template <typename T>
class Printer {
public:
void print(const T& value) const {
std::cout << "[通用] 值: " << value << '\n';
}
};
// 針對 std::string 的完整特化(template <> 後面沒有參數)
template <>
class Printer<std::string> {
public:
void print(const std::string& value) const {
std::cout << "[std::string] \"" << value
<< "\" (長度: " << value.size() << ")\n";
}
};
預期輸出:
Printer<int>{}.print(42); → [通用] 值: 42
Printer<std::string>{}.print("hi"); → [std::string] "hi" (長度: 2)
5.2 部分特化(Partial Specialization)
只把 一部分 參數特化,或限定參數的「形狀」(如「是個指標」)。
⚠️ 重要規則:部分特化只適用於「類別模板」,函式模板不支援部分特化(函式請改用多載)。
// 通用版本
template <typename T>
class TypeInfo {
public:
void describe() const { std::cout << "一般型別\n"; }
};
// 部分特化:任何「指標型別 T*」
template <typename T>
class TypeInfo<T*> {
public:
void describe() const { std::cout << "指標型別\n"; }
};
// 部分特化:任何「左值參考 T&」
template <typename T>
class TypeInfo<T&> {
public:
void describe() const { std::cout << "左值參考型別\n"; }
};
TypeInfo<int>{}.describe(); // 一般型別
TypeInfo<int*>{}.describe(); // 指標型別 ← 命中 T* 部分特化
TypeInfo<int&>{}.describe(); // 左值參考型別
多參數的部分特化也很常見:
template <typename T, typename U>
class Pair { /* 通用:兩個不同型別 */ };
template <typename T>
class Pair<T, T> { /* 部分特化:兩個型別相同時 */ };
template <typename T, typename U>
class Pair<T, U*> { /* 部分特化:第二個是指標 */ };
📖 完整可執行範例請參考 template_specialization.cpp
6. 可變參數模板(Variadic Templates,C++11)
可變參數模板能接受 任意數量、任意型別 的引數。語法核心是 參數包(parameter pack) ...。
// 終止條件:沒有參數時換行
void print() { std::cout << '\n'; }
// 遞迴展開:取出第一個,其餘繼續遞迴
template <typename T, typename... Args>
void print(const T& first, const Args&... rest) {
std::cout << first;
if (sizeof...(rest) > 0) std::cout << ", ";
print(rest...); // 展開:把 rest 拆開再呼叫
}
print(1, 2.5, "hello", 'A'); // 輸出: 1, 2.5, hello, A
關鍵語法整理:
| 語法 | 意義 |
|---|---|
typename... Args |
宣告一個 型別參數包 |
const Args&... rest |
宣告一個 函式參數包 |
rest... |
展開 參數包 |
sizeof...(rest) |
取得參數包內 元素的數量(編譯期常數) |
6.1 C++17 折疊表達式(Fold Expressions)
C++17 引入折疊表達式,讓「對參數包做一連串運算」不再需要寫遞迴:
template <typename... Args>
auto sum(Args... args) {
return (args + ...); // 一元右折疊:a1 + (a2 + (a3 + ...))
}
sum(1, 2, 3, 4); // = 10
四種折疊形式:
| 形式 | 寫法 | 展開(以 a, b, c 為例) |
|---|---|---|
| 一元右折疊 | (args + ...) |
a + (b + c) |
| 一元左折疊 | (... + args) |
(a + b) + c |
| 二元右折疊 | (args + ... + 0) |
a + (b + (c + 0)) |
| 二元左折疊 | (0 + ... + args) |
((0 + a) + b) + c |
二元形式可提供「初始值/空包安全值」,例如空參數包時 (args + ...) 會編譯錯誤,但 (0 + ... + args) 回傳 0。
實用範例——用折疊把全部引數印出來:
template <typename... Args>
void println(const Args&... args) {
((std::cout << args << ' '), ...); // 用逗號運算子折疊
std::cout << '\n';
}
println("x =", 42, "pi =", 3.14); // x = 42 pi = 3.14
7. 模板編譯模型:為什麼定義要放在標頭檔?
這是初學者最常踩的坑,務必理解。
7.1 兩階段編譯與實體化
模板本身 不是程式碼,而是「產生程式碼的藍圖」。編譯器只有在 看到你用某個具體型別 時,才會「實體化(instantiate)」出真正的函式/類別:
template <typename T> 使用 my_max(3, 7) 使用 my_max(1.5, 2.5)
T my_max(T a, T b){...} ──► 實體化 int 版本 ──► 實體化 double 版本
(藍圖,不產生程式碼) int my_max(int,int) double my_max(double,double)
7.2 為什麼定義要放在 .h 而不是 .cpp?
C++ 採用 分離編譯:每個 .cpp 各自編譯成 .o,最後連結。問題在於——當編譯器在 main.cpp 看到 my_max(3, 7),它需要 my_max 的 完整定義 才能實體化出 int 版本。如果定義藏在 utils.cpp 裡,main.cpp 看不到,就無法產生程式碼:
// 若把模板定義放在 utils.cpp:
編譯 main.cpp → 只看到宣告,無法實體化 → 暫時 OK,產生「未定義符號」
編譯 utils.cpp → 沒人用,編譯器不知道要實體化 int 版本 → 沒產生
連結階段 → undefined reference to `int my_max<int>(int, int)` ❌
| 一般函式 | 模板 |
|---|---|
宣告放 .h,定義放 .cpp |
宣告與定義都放 .h(或 .hpp/.ipp) |
| 連結期解析符號 | 使用點需看到完整定義才能實體化 |
實務結論:模板的定義要寫在標頭檔中(這也是為什麼 STL 幾乎全是標頭檔)。進階做法有「顯式實體化(explicit instantiation)」,但初學階段記住「模板放 header」即可。C++20 的 Modules 正在改變這個格局,但目前仍以 header 為主流。
8. 型別特徵與 if constexpr(C++11 / C++17)
8.1 <type_traits> 簡介
標準函式庫提供一組 編譯期型別查詢/轉換 工具,能在模板中「詢問型別的性質」:
#include <type_traits>
static_assert(std::is_integral<int>::value); // int 是整數型別 → true
static_assert(std::is_floating_point<double>::value); // double 是浮點 → true
static_assert(std::is_same<int, int>::value); // 兩型別相同 → true
// C++17 起有 _v 簡寫
static_assert(std::is_integral_v<int>);
| 常用 trait | 用途 |
|---|---|
std::is_integral_v<T> |
T 是否為整數型別 |
std::is_floating_point_v<T> |
T 是否為浮點型別 |
std::is_pointer_v<T> |
T 是否為指標 |
std::is_same_v<T, U> |
T 與 U 是否為同一型別 |
std::remove_const_t<T> |
去掉 const 後的型別 |
std::decay_t<T> |
模擬傳值時的型別退化(去參考/const、陣列轉指標) |
8.2 if constexpr(C++17)——編譯期分支
if constexpr 讓你在模板中根據型別 在編譯期 選擇要保留哪段程式碼,被捨棄的分支 完全不會被編譯:
template <typename T>
std::string describe(const T& value) {
if constexpr (std::is_integral_v<T>) {
return "整數: " + std::to_string(value);
} else if constexpr (std::is_floating_point_v<T>) {
return "浮點數: " + std::to_string(value);
} else {
return "其他型別";
}
}
describe(42); // 整數: 42
describe(3.14); // 浮點數: 3.140000
describe("hi"); // 其他型別
為什麼不用普通
if?因為普通if兩個分支 都要能通過編譯。若某分支對某些型別不合法(例如對字串呼叫std::to_string),就會編譯失敗。if constexpr會把不成立的分支直接丟棄,所以安全。回顧 §6 的可變參數遞迴,if constexpr (sizeof...(rest) > 0)也是同樣道理。
9. 概念 Concepts 預告(C++20)
模板最大的痛點是 錯誤訊息冗長難懂:當你傳了不支援 operator> 的型別給 my_max,編譯器會吐出幾十行「深入模板內部才報錯」的訊息。
C++20 的 Concepts 讓你能 為模板參數加上約束,把錯誤提前、訊息變清楚:
#include <concepts>
template <typename T>
concept Comparable = requires(T a, T b) {
{ a > b } -> std::convertible_to<bool>; // 要求 T 支援 a > b 且結果可轉 bool
};
template <Comparable T> // 只接受滿足 Comparable 的型別
T my_max(T a, T b) {
return (a > b) ? a : b;
}
若傳入不可比較的型別,錯誤訊息會直接說「不滿足 Comparable」,而不是一堆內部細節。本章僅作預告,深入學習可留待進階。
10. 最佳實踐(參考 C++ Core Guidelines)
- [T.1] 用模板提升程式碼的抽象層次:把「演算法邏輯」與「資料型別」解耦,寫一次到處用。
- [T.2] 用模板表達適用於多種型別的演算法:例如排序、搜尋這類與元素型別無關的邏輯。
- [T.3] 用模板表達容器與範圍:自訂容器應做成類別模板。
- [T.10 / 概念]:盡量為模板參數加上 概念約束(C++20),讓介面意圖明確、錯誤訊息友善。
- [T.42] 用模板別名(alias)簡化記法:
template <typename T> using Vec = std::vector<T>;。 - [T.47] 避免不受約束的高度可見模板與普通名稱衝突:別讓你的
template <typename T> void swap(...)意外蓋掉別人的多載。 - [T.62] 把與參數無關的類別模板成員抽出去:避免每個實體化都重複產生相同的非相依程式碼(減少程式碼膨脹)。
- [ES.5 / 偏好]:函式優先用 多載,類別才用特化;函式模板的「完整特化」容易與多載互動出意外,能避則避。
- 優先用標準工具:
std::max、std::swap、std::array、std::tuple等已存在,別重造輪子;自己寫模板是為了學習與處理標準庫沒涵蓋的需求。
11. 常見錯誤與陷阱
| 錯誤 | 說明與解法 |
|---|---|
模板定義放在 .cpp |
連結期出現 undefined reference。解法:把定義移到標頭檔(見 §7)。 |
| 型別推導衝突 | my_max(3, 2.5) 中 T 無法同時是 int 與 double。解法:明確指定 my_max<double>(3, 2.5) 或用兩個型別參數。 |
忘記 template <> |
寫完整特化時漏掉前綴 template <>,會被當成重新定義。 |
| 對函式模板做部分特化 | 語法不允許。解法:改用 函式多載。 |
| 非型別參數用浮點數(C++17 前) | template <double D> 在 C++17 之前不合法,C++20 才放寬。 |
typename 漏寫(相依名稱) |
存取相依型別的巢狀型別要加 typename,例如 typename Container::iterator it;,否則編譯器以為那是成員變數。 |
| 每個類別外成員都要前綴 | 漏寫 template <typename T> 或把 Stack<T>:: 寫成 Stack:: 都會報錯。 |
| 以為模板會「自動」實體化全部型別 | 只有「被用到」的型別才會被實體化;沒用到的特化不存在。 |
| 程式碼膨脹(code bloat) | 大量不同型別實體化會放大二進位檔。可用 §10 的 [T.62] 技巧減輕。 |
如何閱讀模板錯誤訊息?
模板錯誤往往幾十行,閱讀訣竅:
- 從最上面第一條
error:看起(不是note:),那通常是根因。 - 找
required from here:它會告訴你「是哪一行的使用」觸發了這個錯誤。 - 看
deduced conflicting types/no matching function:代表引數型別不符或推導衝突。 - 善用 C++20 Concepts 把約束寫清楚,能讓訊息大幅精簡。
example.cpp: In instantiation of 'T my_max(T, T) [with T = Widget]':
example.cpp:30:18: required from here ← 第 30 行的呼叫造成的
example.cpp:6:22: error: no match for 'operator>' (operand types are 'Widget' and 'Widget')
← 根因:Widget 沒有定義 operator>
重點整理
- 模板是編譯期的程式碼產生器:寫一份藍圖,編譯器依用到的型別生出具體版本,兼顧 程式碼重用 與 型別安全。
- 相較巨集(無型別安全、副作用陷阱)與
void*(無型別安全、較慢),模板是最佳泛型手段。 - 函式模板 支援 引數推導,多數情況不需明確指定型別;推導衝突時用明確引數或多型別參數。
- 非型別模板參數 是編譯期常數,會讓不同數值成為不同型別(如
std::array<T, N>)。 - 類別模板 的類別外成員定義需帶
template<...>前綴與ClassName<T>::。 - 完整特化 適用函式與類別;部分特化只限類別模板,函式請用多載。
- 可變參數模板 + 折疊表達式(C++17) 處理任意數量引數。
- 模板定義要放標頭檔,因為使用點需看到完整定義才能實體化。
<type_traits>+if constexpr讓你在編譯期依型別性質做分支。- Concepts(C++20) 為模板參數加約束,提前報錯、改善錯誤訊息。
練習題
以下每題都能用
g++ -std=c++17 -Wall編譯。建議先自己嘗試,再對照對應程式碼檔案中的相關範例。
練習 1(基礎):泛型搜尋函式
寫一個函式模板 find_index,接受一個 C 風格陣列(用 const T (&arr)[N] 接收)與目標值,回傳目標值的索引,找不到回傳 -1。
- 提示:用非型別模板參數
int N讓編譯器自動推導陣列大小;以for迴圈逐一用==比較。可參考function_templates.cpp中的find_in_array。
練習 2(基礎):泛型 clamp
實作 template <typename T> T my_clamp(T value, T lo, T hi),把 value 限制在 [lo, hi] 區間內(小於 lo 回 lo,大於 hi 回 hi)。用 int 與 double 各測一次。
- 提示:只需兩個三元運算子或
if。注意三個參數都是T,所以呼叫時型別要一致,否則需明確指定。
練習 3(中級):泛型 Pair 類別與比較
實作類別模板 MyPair<T, U>,提供 first()、second() 存取器,以及 operator==(兩個 pair 的對應元素都相等才相等)。
- 提示:成員
T first_; U second_;;operator==比較first_與second_。可參考class_templates.cpp的Pair。
練習 4(中級):完整特化的格式化輸出
延續練習 3,為 MyPair<std::string, std::string> 撰寫 完整特化,新增 print() 以 "key = value" 格式輸出(例如 language = C++)。
- 提示:
template <> class MyPair<std::string, std::string> { ... };整個類別重寫。可參考template_specialization.cpp的Printer<std::string>。
練習 5(中級):可變參數 println
用可變參數模板寫 println,以空格分隔印出所有引數並自動換行。請 同時 提供「遞迴展開」與「C++17 折疊表達式」兩種版本。
- 提示:遞迴版需要一個無參數的終止函式;折疊版用
((std::cout << args << ' '), ...)。
練習 6(挑戰):用 if constexpr 寫泛型 to_string
實作 template <typename T> std::string smart_to_string(const T& v):若 T 是整數或浮點數,回傳數字字串;若是 std::string 或 const char*,直接回傳內容;其他型別回傳 "<unprintable>"。
- 提示:
#include <type_traits>,用if constexpr (std::is_arithmetic_v<T>)、std::is_same_v<T, std::string>等做分支;數字用std::to_string。
練習 7(挑戰):非型別參數固定矩陣
實作 template <typename T, int Rows, int Cols> class Matrix,內部用 T data_[Rows][Cols],提供 at(r, c)、fill(value)、print(),以及相同維度矩陣的 operator+。
- 提示:兩個非型別參數
Rows、Cols讓維度成為型別的一部分;operator+的回傳型別是Matrix<T, Rows, Cols>,逐元素相加。
對應程式碼檔案
- function_templates.cpp — 函式模板:泛型
max/swap、多型別參數、非型別參數、明確特化 - class_templates.cpp — 類別模板:
Stack<T>與Pair<T, U>、預設型別參數、類別外成員定義 - template_specialization.cpp — 完整特化(函式與類別)與部分特化(指標、參考、相同型別)