参考
定义
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;
}