参考
定义
C++模板是一种强大特性, 允许程序员编写与类型无关代码
通过模板, 可以编写一次代码, 用于多种数据类型, 从而提高代码重用性和灵活性
template<typename T>
T
是一个类型参数(type parameter), 是一个占位符, 用于在模板实例化时指定具体类型
种类
普通模板
函数模板
单参数
template<typename T>
- 示例, 定义单参数模板
#include <iostream>
template<typename T>
T Add(T x, T y) {
return x + y;
}
int main() {
std::cout << Add<int>(0xA, 0xB) << std::endl;
std::cout << Add<double>(3.1415, 2.7141) << std::endl;
std::cout << Add<std::string>("Hello", "World") << std::endl;
return 0;
}
多参数
template<typename T, typename V>
- 示例, 定义多参数模板
#include <iostream>
template<typename T, typename V>
void Print(T x, V y) {
std::cout << x << std::endl << y << std::endl;
}
int main() {
Print(1, 0.1412);
Print("abcdef", 'A');
return 0;
}
类模板
- 示例, 定义类模板
#include <iostream>
template <typename T>
class Composer {
public:
Composer(T x, T y) : mX(x), mY(y) {}
T GetMax() const {
return mX > mY ? mX : mY;
}
private:
T mX;
T mY;
};
int main() {
Composer<int> com1(0xFF, 0xAB);
std::cout << com1.GetMax() << std::endl;
return 0;
}
结构体模板
template<typename T>
struct Node {
T mData;
Node* mNext;
};
可变参数模板
C++11允许模板中包含任意数量模板参数, 称可变参数模板(variadic templates)
variadic template functions(可变参数模板函数)
定义
typename
后跟 ...Args
表明Args是可变模板参数, 可接收多种数据类型, 又称模板参数包
args参数, 类型为 Args...
, 可以接收任意个参数
template<typename... Args>
void VairFun(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 printAll(Args... args) {
// 调用递归模板函数开始解包
print(args...);
}
int main() {
printAll(1, 2.5, "Hello");
return 0;
}
std::initializer_list和逗号运算符解包
#include <iostream>
// 可变参数模板函数, 使用std::initializer_list和逗号运算符解包
template<typename... Args>
void printAll(Args... args) {
(void) std::initializer_list<int>{ (std::cout << args << " ", 0)... };
std::cout << std::endl;
}
int main() {
printAll(1, 2.5, "Hello");
return 0;
}
特征
特化
模板特化允许为特定类型提供不同实现, 有全特化和偏特化两种类型
explicit specialization(全特化)
全特化是对模板所有参数提供特定实现, 模板特化类型必须完全匹配
- 示例, 偏特化模板
#include <iostream>
#include <string>
template<typename T>
struct ExplicitDemo {
void Print(const T& value) {
std::cout << "general template: " << value << std::endl;
}
};
// 针对 std::string 类型全特化
template<>
struct ExplicitDemo<std::string> {
void Print(const std::string& value) {
std::cout << "string specialization: " << value << std::endl;
}
};
int main() {
ExplicitDemo<int> intDemo;
intDemo.Print(42);
// 特化情况
ExplicitDemo<std::string> strDemo;
strDemo.Print("Hello, World!");
return 0;
}
partial specialization(偏特化)
偏特化允许特化模板部分参数, 只能用于类模板
- 示例, 模板偏特化
#include <iostream>
// 通用模板类定义
template<typename T>
class PartialDemo {
public:
void Display() {
std::cout << "generic template for type: " << typeid(T).name() << std::endl;
}
};
// 对int类型偏特化
template<>
class PartialDemo<int> {
public:
void Display() {
std::cout << "specialized template for int type" << std::endl;
}
};
int main() {
PartialDemo<int> intDemo;
intDemo.Display();
PartialDemo<double> doubleDemo;
doubleDemo.Display();
return 0;
}
变量类型
提取模板变量类型及其特性主要通过类型萃取(type traits)和SFINAE
(substitution failure is not an error)技术实现
提取
利用标准库中头文件<type_traits>
, 可检查、提取和操作模板变量类型信息
std::decay
std::decay
是一个类型特征(type trait), std::decay
会去掉模板变量引用、cv-限定符(const
、volatile
), 并将数组和函数类型转换为指针类型, 之后提取模板类型
std::decay
本身并不直接”提取”模板变量类型, 它只是转换一个给定类型到其衰减后形式
- 示例, 提取模板变量类型
#include <iostream>
#include <type_traits>
template <typename T>
void PrintDecayedType() {
using DecayedT = typename std::decay<T>::type
std::cout << typeid(DecayedT).name() << std::endl;
}
int main() {
// int(移除引用)
PrintDecayedType<int&>();
// int const * __ptr64
PrintDecayedType<const int[]>();
}
remove_reference/remove_cv
std::remove_reference
和std::remove_cv
是两种类型特征(type traits
), 分别用于从给定类型中移除引用和cv-限定符(const和volatile)
#include <iostream>
#include <type_traits>
template <typename T>
void PrintStrippedType() {
// 先移除引用, 再移除cv限定符
using CleanedT = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
std::cout << typeid(CleanedT).name() << std::endl;
}
int main() {
// int
PrintStrippedType<const int&>();
}
std::type_traits
std::type_traits
是 C++11 所引入标准库, 提供一组模板类和函数, 用于在编译时查询和操作类型特性, 包括但不限于类型是否为整型、是否为浮点型、是否有默认构造函数、是否可拷贝等
#include <iostream>
#include <type_traits>
template <typename T>
void PrintTypeTraits() {
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
PrintTypeTraits<int*>();
// is_reference: 1
PrintTypeTraits<int&>();
// is_float: 1
PrintTypeTraits<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;
}
编译
C++编译器在编译模板时并不知道模板会被用来实例化何种类型, 所有无法生成具体代码, 只有在使用模板时(实例化)编译器才知道模板参数类型, 才能生成具体实现代码
因此若在分离编译模型下(将模板声明放在.h, 定义放在.cpp), 编译器在处理.h文件时无法获悉模板定义, 无法实例化模板
模板实例化需要在同一个编译单元中同时看到模板声明和定义, 才能生成特定类型实例化代码
声明定义均放头文件
// test_template.hpp
#ifndef __TEST_TEMPLATE_HPP__
#define __TEST_TEMPLATE_HPP__
#include <iostream>
template <typename T>
class TemplateDemo {
public:
void Print(const T& value);
};
template <typename T>
void TemplateDemo<T>::Print(const T& value) {
std::cout << value << std::endl;
}
#endif
// main.cpp
#include "test_template.hpp"
int main() {
TemplateDemo<int> intDemo;
intDemo.Print(1);
TemplateDemo<std::string> strDemo;
strDemo.Print("Hello");
return 0;
}
定义放内联实现文件
// test_template.hpp
#ifndef __TEST_TEMPLATE_HPP__
#define __TEST_TEMPLATE_HPP__
#include <iostream>
template <typename T>
class TemplateDemo {
public:
void Print(const T& value);
};
// 包含模板定义
#include "test_template.tpp"
#endif
// test_template.tpp
template <typename T>
void TemplateDemo<T>::Print(const T& value) {
std::cout << value << std::endl;
}
// main.cpp
#include "test_template.hpp"
int main() {
TemplateDemo<int> intDemo;
intDemo.Print(1);
TemplateDemo<std::string> strDemo;
strDemo.Print("Hello");
return 0;
}
这样可在.hpp中保持模板声明简洁性, 同时将模板实现放在.cpp中便于管理
显式实例化
若确定模板只会用于特定类型, 可使用显式实例化, 将模板定义放在.cpp中实例化所需类型
// test_template.hpp
#ifndef __TEST_TEMPLATE_HPP__
#define __TEST_TEMPLATE_HPP__
#include <iostream>
template <typename T>
class TemplateDemo {
public:
void print(const T& value);
};
#endif
// test_template.cpp
#include "test_template.hpp"
template <typename T>
void TemplateDemo<T>::print(const T& value) {
std::cout << value << std::endl;
}
// 显式实例化
template class TemplateDemo<int>;
template class TemplateDemo<double>;
现在.cpp只会生成特定类型模板实例, 对于没有实例化类型编译器会报错
traits
traits
是一种用于泛型编程(generic programming)技术, 允许程序根据类型特征进行编译期决策
traits
可以在编译时查询和操作类型属性, 而无需在运行时进行类型检查, 通常用于模板编程中以增强代码灵活性和可重用性
例如可以创建一个traits来确定类型是否为某种类型(如整数、浮点数、指针等), 是否满足某种条件(如可复制、可移动), 或者是否具备某种操作(如算术操作、输入输出操作)等
功能
traits通常以模板类形式实现, 并使用模板特化(template specialization)来定义特定类型特性
is_same
std::is_same
是C++标准库中所提供, 用于判断两个模板类型是否相同
#include <iostream>
#include <type_traits>
template <typename T>
void CheckType() {
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() {
CheckType<int>();
CheckType<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 CheckIntegerType() {
if (IsInteger<T>::value) {
std::cout << "type is integer" << std::endl;
return;
}
std::cout << "type is not integer" << std::endl;
}
int main() {
CheckIntegerType<int>();
CheckIntegerType<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++ 标准库中一个模板结构体, 提供方法来查询迭代器特性, 如迭代器类别、值类型、差异类型、指针类型和引用类型等
std::iterator_traits
主要定义几个类型别名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
- 示例, 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;
}