乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

可变参数模板

定义与作用

可变参数模板(Variadic Templates,C++11)允许模板接受任意数量的模板参数:

template <typename... Args>
void print(Args... args);  // Args 是类型参数包,args 是函数参数包

它终结了"为每个参数数量写一个重载"的噩梦,是 std::tuple、std::make_shared、std::thread 等标准库组件的核心机制。

核心原理

参数包展开模式

递归展开 vs 折叠表达式

完整示例

示例一:通用打印函数——递归展开

场景说明:实现一个类似 Python print 的泛型打印函数,支持任意类型和数量。

#include <iostream>
#include <string>
#include <sstream>

// ====== C++11 递归展开 ======

// 终止条件:无参数时什么都不做
void format_print(std::ostream& os) {}

// 递归展开:每次处理第一个参数
template <typename T, typename... Args>
void format_print(std::ostream& os, const T& first, const Args&... rest) {
    os << first;
    if constexpr (sizeof...(rest) > 0) {
        os << ", ";
    }
    format_print(os, rest...);
}

// 包装函数
template <typename... Args>
void println(const Args&... args) {
    format_print(std::cout, args...);
    std::cout << std::endl;
}

// ====== 求和函数:递归展开 ======

// 终止条件
template <typename T>
T variadic_sum(T last) {
    return last;
}

template <typename T, typename... Args>
T variadic_sum(T first, Args... rest) {
    return first + variadic_sum(rest...);
}

// ====== 拼接字符串——C++17 折叠表达式 ======

template <typename... Args>
std::string concat(const Args&... args) {
    std::ostringstream oss;
    (oss << ... << args);  // 二元左折叠:(((oss << a1) << a2) << a3)
    return oss.str();
}

// ====== 全真判断 ======

template <typename... Args>
bool all_true(Args... args) {
    return (... && args);  // 一元右折叠:a1 && (a2 && (a3 && true))
}

int main() {
    std::cout << "===== 泛型打印 =====" << std::endl;
    println("Hello", 42, 3.14, std::string("World"));
    println("学生:", "张三", "分数:", 95);

    std::cout << "\n===== 求和 =====" << std::endl;
    std::cout << "sum(1,2,3,4,5) = " << variadic_sum(1, 2, 3, 4, 5) << std::endl;
    std::cout << "sum(1.1, 2.2, 3.3) = " << variadic_sum(1.1, 2.2, 3.3) << std::endl;

    std::cout << "\n===== 折叠表达式 =====" << std::endl;
    std::cout << "concat: " << concat("C++", 17, " is ", "modern") << std::endl;
    std::cout << "all_true(true, true, false, true): "
              << std::boolalpha << all_true(true, true, false, true) << std::endl;
    std::cout << "all_true(true, true, true): "
              << all_true(true, true, true) << std::endl;

    return 0;
}

预期输出:

===== 泛型打印 =====
Hello, 42, 3.14, World
学生:, 张三, 分数:, 95

===== 求和 =====
sum(1,2,3,4,5) = 15
sum(1.1, 2.2, 3.3) = 6.6

===== 折叠表达式 =====
concat: C++17 is modern
all_true(true, true, false, true): false
all_true(true, true, true): true

逐段分析:

  • 递归展开(C++11):format_print 每次处理参数包的第一个参数 first,剩余参数 rest... 递归调用
  • 终止条件:空参数版本的 format_print(os) 是递归基(base case)
  • sizeof...(rest) 在编译期返回参数包剩余参数数量
  • 折叠表达式(C++17):(... && args) 等价于 a1 && a2 && a3 && ...,代码量从递归的 3 个函数减少到 1 行

示例二:简易 Tuple 实现

#include <iostream>
#include <string>
#include <utility>

// ====== 简易 Tuple 实现 ======

// 主模板声明
template <typename... Types>
class Tuple;

// 递归基:空 Tuple
template <>
class Tuple<> {
public:
    static constexpr size_t size = 0;
};

// 递归定义:Tuple<Head, Tail...>
template <typename Head, typename... Tail>
class Tuple<Head, Tail...> : private Tuple<Tail...> {
public:
    Tuple() = default;

    Tuple(Head head, Tail... tail)
        : head_(std::move(head)), Tuple<Tail...>(std::move(tail)...) {}

    Head& head() { return head_; }
    const Head& head() const { return head_; }

    Tuple<Tail...>& tail() {
        return *this;  // 向上转型为基类
    }

    const Tuple<Tail...>& tail() const {
        return *this;
    }

    static constexpr size_t size = 1 + sizeof...(Tail);

private:
    Head head_;
};

// ====== 按索引获取元素 ======

template <size_t Index, typename TupleType>
struct TupleElement;

// 索引 0:取当前 Tuple 的 head
template <typename Head, typename... Tail>
struct TupleElement<0, Tuple<Head, Tail...>> {
    using type = Head;

    static Head& get(Tuple<Head, Tail...>& t) { return t.head(); }
    static const Head& get(const Tuple<Head, Tail...>& t) { return t.head(); }
};

// 索引 N > 0:递归到 tail
template <size_t Index, typename Head, typename... Tail>
struct TupleElement<Index, Tuple<Head, Tail...>> {
    using type = typename TupleElement<Index - 1, Tuple<Tail...>>::type;

    static type& get(Tuple<Head, Tail...>& t) {
        return TupleElement<Index - 1, Tuple<Tail...>>::get(t.tail());
    }

    static const type& get(const Tuple<Head, Tail...>& t) {
        return TupleElement<Index - 1, Tuple<Tail...>>::get(t.tail());
    }
};

// 获取函数
template <size_t Index, typename... Types>
auto get(Tuple<Types...>& t)
    -> typename TupleElement<Index, Tuple<Types...>>::type& {
    return TupleElement<Index, Tuple<Types...>>::get(t);
}

int main() {
    std::cout << "===== 简易 Tuple =====" << std::endl;

    Tuple<int, std::string, double> t(42, "Hello", 3.14);

    std::cout << "Tuple size: " << t.size << std::endl;
    std::cout << "get<0>: " << get<0>(t) << std::endl;
    std::cout << "get<1>: " << get<1>(t) << std::endl;
    std::cout << "get<2>: " << get<2>(t) << std::endl;

    return 0;
}

预期输出:

===== 简易 Tuple =====
Tuple size: 3
get<0>: 42
get<1>: Hello
get<2>: 3.14

逐段分析:

  • Tuple 通过递归继承实现:Tuple<Head, Tail...> 继承自 Tuple<Tail...>,层层存储 head
  • 空 Tuple<> 是递归基类
  • TupleElement<Index, Tuple> 通过偏特化递归剥离类型来获取指定索引的元素
  • 这是 std::tuple 的简化实现原理——展示可变参数模板的核心应用

易错场景与面试考点

易错场景

场景错误表现正确做法
递归展开缺少终止条件编译期无限递归必须提供 0 或 1 参数的重载作为递归基
参数包展开位置错误语法错误(args... vs args ...)... 紧跟参数包名
折叠表达式括号缺失运算符优先级问题折叠表达式整体加括号 (args + ...)
空参数包调用少数情况需要额外处理提供空参数的函数重载
sizeof...(Args) 用于运行时编译期常量无法直接用于运行时用 if constexpr 或模板特化分流

常见面试问题

  1. 可变参数模板的展开方式有哪些?——(1) 递归展开(C++11);(2) 折叠表达式(C++17);(3) 初始化列表展开 int arr[] = { (func(args), 0)... };;(4) 逗号表达式展开。

  2. 折叠表达式的四种形式?——一元右折叠 (pack op ...)、一元左折叠 (... op pack)、二元右折叠 (pack op ... op init)、二元左折叠 (init op ... op pack)。

  3. 参数包的 sizeof... 是什么?——编译期返回参数包中元素数量的常量。sizeof...(Args) 返回类型参数个数,sizeof...(args) 返回函数参数个数。

  4. std::tuple 的实现原理?——通过可变参数模板 + 递归继承实现。每个实例存储一个元素并继承剩余元素的 Tuple。

  5. 完美转发结合可变参数模板怎么写?——template<typename... Args> void f(Args&&... args) { g(std::forward<Args>(args)...); }。

小结

  • 可变参数模板(C++11)接受任意数量的模板参数——参数包用 ... 声明
  • 递归展开是最基本的处理方式——必须提供递归终止条件
  • 折叠表达式(C++17)大幅简化参数包操作——一元/二元、左折叠/右折叠
  • std::tuple 通过递归继承实现——可变参数模板的最经典应用
  • 与完美转发结合:std::forward<Args>(args)... 是 C++ 中最强大的转发机制
上一页
模板特化与偏特化
下一页
别名模板与模板模板参数