参考
定义
c++ template是实现泛型编程 (generic programming) 的核心特性, 允许开发者编写与类型无关代码, 在编译期根据传入的实际类型生成具体的代码实例, 从而极大地提高代码的重用性和灵活性
template<typename T>
-
template: 声明这是一个模板 -
typename(或class): 关键字, 表示后面跟着一个类型参数 -
T: 类型参数 (type parameter), 是一个占位符, 在模板实例化时会被具体的类型(如intdoublestd::string)替换
种类
普通模板
函数模板(function templates)
函数模板允许定义一个通用的函数逻辑, 适用于多种数据类型
编译器会根据传入的参数自动推导类型(模板参数推导), 或者由开发者显式指定
// 单参数
template<typename T>
// 多参数
template<typename T, typename V>
#include <iostream>
#include <string>
// 单参数模板
template<typename T>
T add(T x, T y) {
return x + y;
}
// 多参数模板
template<typename T, typename U>
void print(T x, U y) {
std::cout << "x: " << x << ", y: " << y << std::endl;
}
int main() {
// 1. 显式指定类型
std::cout << add<int>(1, 2) << std::endl;
// 2. 自动类型推导 (编译器根据 3.14 和 2.71 推导出 T 为 double)
std::cout << add(3.14, 2.71) << std::endl;
// 3. 多参数模板调用
print(1, 0.1412);
print(std::string("Hello"), 'A');
return 0;
}
类模板(class templates)
类模板用于定义通用的类、结构体或联合体
与函数模板不同, 类模板通常不能自动推导参数类型(c++17 引入类模板参数推导 CTAD, 但在复杂场景下仍需显式指定)
#include <iostream>
template <typename T>
class Composer {
public:
Composer(T x, T y) : mX(x), mY(y) {}
T get_max() const {
return mX > mY ? mX : mY;
}
private:
T mX;
T mY;
};
int main() {
Composer<int> com(1, 2);
std::cout << com.get_max() << std::endl;
return 0;
}
可变参数模板(variadic templates)
c++11 引入可变参数模板, 允许模板接受任意数量、任意类型的参数
定义
typename后跟 ...Args 表明 Args是可变模板参数, 可接收多种数据类型, 又称模板参数包
// args参数, 类型为Args... , 可以接收任意个参数
template<typename... Args>
void vair_fun(Args... args) {
// ...
}
读取
- 递归方式解包
定义一个辅助递归模板函数, 每次递归调用时从参数包中取出一个参数, 直到参数包为空
#include <iostream>
// 基础递归终止函数
void print() {
// 换行作为递归终止标志
std::cout << std::endl;
}
// 递归模板函数
template<typename T, typename... Args>
void print(T firstArg, Args... args) {
// 打印第一个参数
std::cout << firstArg << " ";
// 递归调用, 解包剩余参数
print(args...);
}
// 可变参数模板函数
template<typename... Args>
void print_all(Args... args) {
// 调用递归模板函数开始解包
print(args...);
}
int main() {
print_all(1, 2.5, "Hello");
return 0;
}
std::initializer_list和逗号运算符解包
#include <iostream>
// 可变参数模板函数, 使用std::initializer_list和逗号运算符解包
template<typename... Args>
void print_all(Args... args) {
(void) std::initializer_list<int>{ (std::cout << args << " ", 0)... };
std::cout << std::endl;
}
int main() {
print_all(1, 2.5, "Hello");
return 0;
}
特征
特化
模板特化允许为某些特定的类型提供不同于通用模板的定制化实现
分为全特化和偏特化
explicit specialization(全特化)
全特化是指将模板的所有参数都指定为具体的类型
#include <iostream>
#include <string>
// 通用模板
template<typename T>
struct Printer {
void print(const T& value) {
std::cout << "General: " << value << std::endl;
}
};
// 全特化: 针对 std::string 提供特定实现
template<>
struct Printer<std::string> {
void print(const std::string& value) {
std::cout << "String Specialization: [" << value << "]" << std::endl;
}
};
int main() {
Printer<int> p1;
p1.print(42);
Printer<std::string> p2;
p2.print("Hello");
return 0;
}
partial specialization(偏特化)
偏特化(部分特化)是指只特化模板的部分参数, 或者对参数的某种类型特征(如指针、引用)进行特化
注意: 函数模板不支持偏特化, 只有类模板支持
#include <iostream>
#include <typeinfo>
// 通用类模板
template<typename T>
class TypeAnalyzer {
public:
void analyze() {
std::cout << "Generic type: " << typeid(T).name() << std::endl;
}
};
// 偏特化 1: 针对所有指针类型 (T*)
template<typename T>
class TypeAnalyzer<T*> {
public:
void analyze() {
std::cout << "Pointer to type: " << typeid(T).name() << std::endl;
}
};
// 偏特化 2: 针对 std::pair 类型 (包含两个模板参数)
template<typename T1, typename T2>
class TypeAnalyzer<std::pair<T1, T2>> {
public:
void analyze() {
std::cout << "std::pair of " << typeid(T1).name() << " and " << typeid(T2).name() << std::endl;
}
};
int main() {
TypeAnalyzer<int> t1;
t1.analyze(); // Generic type: i (int)
TypeAnalyzer<int*> t2;
t2.analyze(); // Pointer to type: i (int)
TypeAnalyzer<std::pair<int, double>> t3;
t3.analyze(); // std::pair of i and d
return 0;
}
变量类型
提取模板变量类型及其特性主要通过类型萃取(type traits)和SFINAE(substitution failure is not an error)技术实现
提取
利用标准库中头文件<type_traits>, 可检查、提取和操作模板变量类型信息
std::decay
std::decay是一个类型特征(type trait), 其会去掉模板变量引用、cv-限定符(const、volatile), 并将数组和函数类型转换为指针类型, 之后提取模板类型
std::decay本身并不直接”提取”模板变量类型, 它只是转换一个给定类型到其衰减后形式
示例, 提取模板变量类型
#include <iostream>
#include <type_traits>
template <typename T>
void print_decay_type() {
using DecayedT = typename std::decay<T>::type
std::cout << typeid(DecayedT).name() << std::endl;
}
int main() {
// int(移除引用)
print_decay_type<int&>();
// int const * __ptr64
print_decay_type<const int[]>();
return 0;
}
remove_reference/remove_cv
std::remove_reference和std::remove_cv是两种类型特征(type traits), 分别用于从给定类型中移除引用和cv-限定符(const和volatile)
#include <iostream>
#include <type_traits>
template <typename T>
void print_stripped_type() {
// 先移除引用, 再移除cv限定符
using CleanedT = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
std::cout << typeid(CleanedT).name() << std::endl;
}
int main() {
// int
print_stripped_type<const int&>();
return 0;
}
std::type_traits
std::type_traits 是 c++11 所引入标准库, 提供一组模板类和函数, 用于在编译时查询和操作类型特性, 包括但不限于类型是否为整型、是否为浮点型、是否有默认构造函数、是否可拷贝等
#include <iostream>
#include <type_traits>
template <typename T>
void print_type_traits() {
std::cout << "is_pointer: " << std::is_pointer<T>::value << std::endl;
std::cout << "is_reference: " << std::is_reference<T>::value << std::endl;
std::cout << "is_integral: " << std::is_integral<T>::value << std::endl;
std::cout << "is_float: " << std::is_floating_point<T>::value << std::endl;
}
int main() {
// is_pointer: 1
print_type_traits<int*>();
// is_reference: 1
print_type_traits<int&>();
// is_float: 1
print_type_traits<float>();
return 0;
}
decltype
decltype 操作符用于在编译时推导表达式类型
template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
int main() {
// 推导出result类型为double
auto result = add(1, 2.0);
// double
std::cout << typeid(result).name() << std::endl;
return 0;
}
编译
问题
在 c++ 的分离编译模型中, 通常将声明放在 .h 文件, 定义放在 .cpp 文件
但模板不能这样做(除非使用显式实例化), 因为模板本身不是真实的代码, 而是一个”代码生成器”
编译器在编译 .cpp 文件时, 如果看不到模板被具体类型实例化, 就不会生成任何机器码
当链接器尝试将使用模板的 中间文件和包含模板定义的库文件链接时, 会找不到具体的符号, 从而报 Undefined Reference错误
graph TD
A[main.cpp: 调用 add<int>] --> B(编译器)
C[template.cpp: 定义 add<T>] --> B
B --> D{遇到 add<int> 实例化}
D -- 在 main.cpp 中 --> E[找不到 add<T> 的定义, 报错!]
F[正确做法: 将定义放在头文件] --> G(编译器同时看到声明与定义)
G --> H[成功生成 add<int> 的机器码]
解决
声明定义均放头文件
// math_utils.hpp
#pragma once
#include <iostream>
template <typename T>
class MathUtils {
public:
void print(const T& value);
};
// 在头文件中直接给出实现
template <typename T>
void MathUtils<T>::print(const T& value) {
std::cout << "Value: " << value << std::endl;
}
定义放内联实现文件
为保持头文件整洁, 可将模板实现放在 .tpp 文件中, 并在 .hpp 的末尾 #include 它
// math_utils.hpp
#pragma once
template <typename T>
class MathUtils {
public:
void print(const T& value);
};
// 包含实现文件 (注意: 不是 .cpp, 防止被编译器独立编译)
#include "math_utils.tpp"
// math_utils.tpp
template <typename T>
void MathUtils<T>::print(const T& value) {
// 实现代码
}
显式实例化
若确定模板只会用于特定类型, 可使用显式实例化, 将模板定义放在.cpp中实例化所需类型
// math_utils.cpp
#include "math_utils.hpp"
template <typename T>
void MathUtils<T>::print(const T& value) { /*...*/ }
// 显式实例化: 强制编译器生成 int 和 double 版本的代码
template class MathUtils<int>;
template class MathUtils<double>;
现在.cpp只会生成特定类型模板实例, 对于没有实例化类型编译器会报错
类型萃取(type traits)
traits是一种用于泛型编程(generic programming)技术, 允许程序根据类型特征进行编译期决策
traits可以在编译时查询和操作类型属性, 而无需在运行时进行类型检查, 通常用于模板编程中以增强代码灵活性和可重用性
例如可以创建一个traits来确定类型是否为某种类型(如整数、浮点数、指针等), 是否满足某种条件(如可复制、可移动), 或者是否具备某种操作(如算术操作、输入输出操作)等
功能
traits通常以模板类形式实现, 并使用模板特化(template specialization)来定义特定类型特性
is_same
std::is_same 是c++标准库中所提供, 用于判断两个模板类型是否相同
#include <iostream>
#include <type_traits>
template <typename T>
void check_type() {
if (std::is_same<T, int>::value) {
std::cout << "type is int" << std::endl;
return;
}
std::cout << "type is not int" << std::endl;
}
int main() {
check_type<int>();
check_type<double>();
return 0;
}
自定义traits类
可以自定义traits类来检查某个类型, 通常使用模板特化来对特定类型进行处理
#include <iostream>
#include <type_traits>
// 默认情况下, 非整数类型
template <typename T>
struct IsInteger {
static const bool value = false;
};
// 针对整数类型进行特化
template <>
struct IsInteger<int> {
static const bool value = true;
};
template <>
struct IsInteger<long> {
static const bool value = true;
};
template <typename T>
void check_int_type() {
if (IsInteger<T>::value) {
std::cout << "type is integer" << std::endl;
return;
}
std::cout << "type is not integer" << std::endl;
}
int main() {
check_int_type<int>();
check_int_type<double>();
return 0;
}
上面代码中定义一个 IsInteger traits 类
通过模板特化将 IsInteger<int> 和 IsInteger<long> 设置为 true, 而其他类型默认为 false
类型别名
value_type
value_type定义容器中存储元素类型
#include <iostream>
#include <vector>
int main() {
using value_type = std::vector<int>::value_type
std::vector<int> vec = {1, 2, 3, 4, 5};
value_type x = 10;
vec.push_back(x);
std::cout << vec.back() << std::endl;
return 0;
}
difference_type
difference_type表示两个迭代器之间距离类型, 通常是一个有符号整数类型
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
auto it1 = vec.begin();
auto it2 = vec.end();
// 计算迭代器之间距离
std::vector<int>::difference_type diff = it2 - it1;
std::cout << diff << std::endl;
return 0;
}
pointer
pointer提供指向容器中元素指针类型
reference
reference定义容器中元素引用类型
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 获取vec中第一个元素引用
std::vector<int>::reference ref = vec[0];
// 修改引用值, 即修改vec中第一个元素值
ref = 10;
// 输出10, 表示vec中第一个元素被修改
std::cout << vec[0] << std::endl;
return 0;
}
std::iterator_traits
std::iterator_traits是 c++ 标准库中一个模板结构体, 提供方法来查询迭代器特性, 如迭代器类别、值类型、差异类型、指针类型和引用类型等, 其主要定义几个类型别名value_type、difference_type、pointer、reference 等
获取指向类型
std::iterator_traits<T>::value_type 用于获取迭代器 T 所指向值类型
- 示例, 获取迭代器类型
#include <iterator>
#include <type_traits>
template <typename T>
struct MyIterator {
using iterator_category = std::forward_iterator_tag; // 迭代器类别
using value_type = T; // 值类型
using difference_type = std::ptrdiff_t; // 差异类型
using pointer = T*; // 指针类型(如果需要)
using reference = T&; // 引用类型
// ... 其他成员和函数 ...
};
// 通常不需要特化 std::iterator_traits, 除非你有特殊需求
// 在这个例子中, MyIterator 已经定义所有必要类型成员, 所以 std::iterator_traits<MyIterator<T>> 会自动工作
int main() {
// 使用 std::iterator_traits 来查询 MyIterator特性
static_assert(std::is_same<std::iterator_traits<MyIterator<int>>::iterator_category, std::forward_iterator_tag>::value, "");
static_assert(std::is_same<std::iterator_traits<MyIterator<int>>::value_type, int>::value, "");
// ... 其他静态断言 ...
return 0;
}
例如, 若 T 是 std::vector<int>::iterator, 那么 iterator_traits<T>::value_type 将是 int
- 示例,
iterator_traits使用
template<typename T>
typename iterator_traits<T>::value_type TestFunc(T iter) {
return *iter;
}
(1) 模板声明
template<typename T> 表示 TestFunc 是模板函数, 可以接受任意类型 T 参数
(2) 函数返回类型
typename iterator_traits<T>::value_type
函数返回类型, 使用 iterator_traits 提取 T 类型所对应迭代器值类型(即 value_type)
typename 关键字用于指明iterator_traits<T>::value_type 是一个类型(type), 而不是一个静态成员或其他内容
(3) 函数参数
该函数接受一个类型为 T 参数 iter, 通常是一个迭代器(例如指向容器指针或迭代器)
(4) 函数体
函数返回 *iter, 即传入迭代器所指向值, 意味着 T 必须是一个可以解引用迭代器类型
(5) 用途
该函数接受一个迭代器 ite, 并返回迭代器解引用后值
返回类型是 T 所对应迭代器 value_type, 确保返回值类型与 ite 所指向对象类型相匹配
因此, 该函数可以处理任意迭代器, 例如 std::vector<int>::iterator、std::list<std::string>::iterator 等, 而不需要知道具体容器类型
#include <iostream>
#include <vector>
#include <iterator>
template<typename T>
typename std::iterator_traits<T>::value_type func(T iter) {
return *iter;
}
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin();
int val = func(it);
std::cout << "The value is: " << val << std::endl;
return 0;
}