c++泛型编程

1.模板参数的类型

模板参数分为三大类:

类别 语法 说明
类型参数 typename T / class T 代表一个类型
非类型参数 int N, auto V (C++17) 编译期常量值,可为整型、枚举、指针、引用、成员指针等
模板模板参数 template<class> class Container 参数本身是一个类模板
c 复制代码
// 类型参数 + 非类型参数 + 模板模板参数
template<
    typename T,                    // 类型参数
    size_t N,                      // 非类型参数
    template<typename> class Alloc // 模板模板参数
>
class Array {
    T data[N];
    Alloc<T> allocator;
};

2.函数模板

2.1.基本形式

c 复制代码
template<typename T>
T add(T a, T b) {
    return a + b;
}

// 编译器自动推导:add(1, 2) -> T=int
// 显式指定:add<double>(1, 2)

2.2.函数模板重载

函数模板之间、模板与非模板之间可以重载,匹配优先级为:

  • 普通非模板函数(精确匹配)
  • 特化版本(如果存在)
  • 更"特化"的模板(部分排序规则)
  • 更泛化的模板
c 复制代码
// 通用版本
template<typename T>
void foo(T x) { cout << "generic\n"; }

// 指针版本(更特化)
template<typename T>
void foo(T* x) { cout << "pointer\n"; }

// 非模板函数
void foo(int x) { cout << "non-template int\n"; }

int a = 1;
foo(a);      // 调用非模板函数
foo(&a);     // 调用指针版本
foo(3.14);   // 调用通用版本

2.3.函数模板的默认参数

c 复制代码
template<typename T = int, typename U = double>
void print(T a, U b) {
    cout << a << ", " << b << endl;
}

print<>(1, 1);          // T=int, U=double
print(1, 2);           // T=int, U=double
print(1, "hello");  // T=int, U=const char*

注意:函数模板不支持偏特化,只能用重载来实现类似效果。

3.类模板

3.1.基本形式

c 复制代码
template<typename T, size_t N = 10>
class Stack {
    T data[N];
    size_t top = 0;
public:
    void push(const T& val) { data[top++] = val; }
    T& pop() { return data[--top]; }
};

3.2.全特化(Full Specialization)

对特定类型组合提供完全独立的实现。

c 复制代码
template<typename T, typename U>
class Pair {
    T first;
    U second;
};

// 全特化:当 T=void, U=void 时
template<>
class Pair<void, void> {
    // 完全不同的实现
};

3.3.偏特化(Partial Specialization)

对模板参数的部分约束提供特化,是类模板独有特性。

c 复制代码
template<typename T, typename U>
class MyContainer { /* 通用实现 */ };

// 偏特化1:第二个参数固定为 int
template<typename T>
class MyContainer<T, int> { /* ... */ };

// 偏特化2:两个参数相同
template<typename T>
class MyContainer<T, T> { /* ... */ };

// 偏特化3:T 是指针类型
template<typename T, typename U>
class MyContainer<T*, U> { /* ... */ };

// 偏特化4:T 是引用类型
template<typename T, typename U>
class MyContainer<T&, U> { /* ... */ };

偏特化匹配规则:编译器选择最特化(most specialized)的版本。例如:

c 复制代码
MyContainer<int*, int>  obj1; // 匹配 T*, U(指针偏特化)
MyContainer<int, int>   obj2; // 匹配 T, T(相同类型偏特化)
MyContainer<int, double>obj3; // 匹配通用版本

4.SFINAE 与约束(C++11/17/20)

在模板元编程中,SFINAESubstitution Failure Is Not An Error)用于控制模板参与重载决议。

SFINAE(Substitution Failure Is Not An Error)是 C++ 模板系统的核心规则:

当编译器尝试用具体类型替换模板参数时,如果推导失败,不会产生编译错误,而是将该模板从重载候选集中移除。

c 复制代码
// C++11/14: 使用 enable_if
template<typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
foo(T x) { return x; }

// C++20: 使用 concept(更简洁)
template<typename T>
concept Integral = std::is_integral_v<T>;

template<Integral T>
T foo(T x) { return x; }

4.1.最简示例:理解"推导失败不是错误"

c 复制代码
// 通用版本:接受任意类型
template<typename T>
void foo(T x) {
    cout << "generic: " << x << endl;
}

// 指针版本:T 必须能解引用出 int 成员
template<typename T>
void foo(T x, typename T::iterator* = nullptr) {
    cout << "has iterator\n";
}

foo(42);           // 调用通用版本
                   // 尝试匹配指针版本时:
                   // T=int, int::iterator 不存在 → 推导失败
                   // 但这不是编译错误!只是移除此候选

4.2.SFINAE 发生的时机

c 复制代码
模板定义 → 类型替换(Substitution) → 推导失败? 
    ↓              ↓                    ↓
   编译期      编译期(重载决议)          是 → 静默丢弃
                                      否 → 继续编译

关键点:SFINAE 只在替换阶段生效,函数体内部的错误不会被 SFINAE 捕获。

4.3.enable_if:SFINAE 的"开关"

std::enable_if 是 <type_traits> 中的工具,本质是条件类型开关。

4.3.1.原理剖析

c 复制代码
// enable_if 的实现(简化版)
template<bool B, typename T = void>
struct enable_if {};

// 偏特化:当 B=true 时生效
template<typename T>
struct enable_if<true, T> { using type = T; };

// 使用
enable_if<true, int>::type   // OK, type = int
enable_if<false, int>::type  // 错误!没有 type 成员

4.3.2.两种使用位置

  • 位置一:返回值(最常用)
c 复制代码
template<typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
foo(T x) { return x; }

// 当 T=double 时:
// enable_if<false, double>::type → 不存在
// 推导失败 → SFINAE → 此模板被移除
  • 位置二:模板参数列表
c 复制代码
template<
    typename T,
    typename = typename std::enable_if<std::is_integral<T>::value>::type
>
T foo(T x) { return x; }

// 优点:不占用返回值位置,更通用
// 缺点:默认模板参数不参与函数签名匹配
  • 位置三:函数参数列表(C++11 常用技巧)
c 复制代码
template<typename T>
T foo(T x, 
    typename std::enable_if<std::is_integral<T>::value, int>::type = 0
) { return x; }

4.3.3.多条件组合

c 复制代码
// 条件:T 是整数 且 有符号
template<typename T>
typename std::enable_if<
    std::is_integral<T>::value && std::is_signed<T>::value,
    T
>::type
process(T x) { return -x; }

// C++14 起可用 _v 简写
template<typename T>
std::enable_if_t<std::is_integral_v<T> && std::is_signed_v<T>, T>
process(T x) { return -x; }

4.4.SFINAE 实战:类型特征检测

4.4.1.检测类型是否有某个成员

5.STL 中模板的常见使用模式

5.1.容器(Containers)

c 复制代码
std::vector<int>                v;  // 类型参数
std::array<int, 100>            a;  // 类型 + 非类型参数
std::map<std::string, int>      m;  // 多类型参数
std::vector<int, MyAlloc<int>>  v2; // 模板模板参数(分配器)

5.2.迭代器(Iterators)

迭代器是模板参数化的指针抽象,算法通过模板参数接受任意迭代器类型:

c 复制代码
template<typename InputIt, typename T>
InputIt find(InputIt first, InputIt last, const T& value) {
    for (; first != last; ++first)
        if (*first == value) return first;
    return last;
}

5.3.算法(Algorithms)

STL 算法大量使用函数模板,典型模式:

c 复制代码
// 1. 接受迭代器范围
std::sort(v.begin(), v.end());

// 2. 接受自定义比较器(函数对象/仿函数)
std::sort(v.begin(), v.end(), std::greater<int>());

// 3. 接受一元/二元操作
std::transform(in.begin(), in.end(), out.begin(), 
               [](int x) { return x * 2; });

5.4.适配器(Adapters)

通过模板组合和默认参数实现功能扩展:

c 复制代码
// stack 默认底层容器是 deque
template<typename T, typename Container = std::deque<T>>
class stack {
    Container c;
public:
    void push(const T& x) { c.push_back(x); }
    // ...
};

std::stack<int>           s1; // 底层为 deque
std::stack<int, vector<int>> s2; // 底层为 vector

5.5.仿函数 / 函数对象(Functors)

重载 operator() 的类模板,用于向算法传递策略:

c 复制代码
template<typename T>
struct Plus {
    T operator()(const T& a, const T& b) const {
        return a + b;
    }
};

std::plus<int> adder;
int sum = adder(3, 4); // 7

5.6.策略模式(Policy-Based Design)

通过模板参数注入行为策略,典型如 std::shared_ptr 的删除器:

c 复制代码
template<typename T, typename Deleter = std::default_delete<T>>
class unique_ptr {
    T* ptr;
    Deleter del;
public:
    ~unique_ptr() { del(ptr); }
};

5.7.类型萃取(Type Traits)

编译期类型信息查询与转换,是模板元编程的基础:

cpp 复制代码
// 查询
static_assert(std::is_pointer_v<int*>);     // true
static_assert(std::is_same_v<int, int32_t>); // true (通常)

// 转换
using T = std::remove_reference_t<int&>;    // T = int
using U = std::add_pointer_t<int>;          // U = int*

6.总结对比表

特性 函数模板 类模板
自动类型推导 ✅ 支持 ❌ 需显式指定(除 CTAD C++17)
全特化 ✅ 支持 ✅ 支持
偏特化 ❌ 不支持(用重载替代) ✅ 支持
默认模板参数 ✅ 支持(C++11 起尾部可省) ✅ 支持
非类型模板参数 ✅ 支持 ✅ 支持
模板模板参数 ✅ 支持 ✅ 支持
相关推荐
OPEN-F19 分钟前
C++综合实战:网络编程入门与HTTP客户端
网络·c++·http
十五年专注C++开发24 分钟前
CMake基础:BUILD_INTERFACE 与 INSTALL_INTERFACE 完全解析
c++·cmake·build_interface
布莱克60544 分钟前
strcpy 函数详解:作用、用法与安全缺陷
c语言·开发语言·c++·安全
不想看见4041 小时前
一次关于“鼠标是不是串口”的追问:从USB协议到CAN总线
c++·qt·计算机外设
Zenova EdgeOS1 小时前
等保 2.0 在工业网关的落地:测评点、整改、长期维护
c++·边缘计算·工业网关
牛油果子哥q2 小时前
C++大型项目工程精讲:CMake完整实战、静态库&动态库、模块化拆分、单元测试、gdb调试、性能工具、工程踩坑全解
开发语言·c++·单元测试
Dream Cosmos2 小时前
C++ 多态上篇:从 virtual 到抽象类,彻底理解多态的使用
开发语言·c++
秋田君2 小时前
Qt_Qt(c++)开发中常见错误与解决方法
开发语言·c++·qt
星恒随风3 小时前
C++11详解(一):统一初始化——列表初始化与 initializer_list
c++·笔记·学习·list·状态模式