前置知识: C++

可变参数模板与折叠表达式

3 minAdvanced2026/6/14

C++可变参数模板与折叠表达式详解。

概述

可变参数模板(Variadic Templates)和折叠表达式(Fold Expressions)是 C++ 模板元编程的核心工具。可变参数模板允许模板接受任意数量和型的参数,而 C++17 引入的折叠表达式则极大地简化了参数包的操作方式。两者结合使得编写型安全、零开销的泛型代码变得直观而高效。

从 C++11 的递归展开到 C++17 的折叠表达式,再到 C++20 的 Lambda 捕获包展开,参数包的处理方式越来越简洁。理解这些机制是掌握现代 C++ 模板编程的必经之路。

基础概念

参数包的

  • 模板参数包typename... Types,匹配零个或多个
  • 非类型参数包int... Values,匹配零个或多个值
  • 模板模板参数包template<typename> class... Tmpls,匹配零个或多个模板

参数包展开的位置

参数包可以在以下位置展开:

  • 函数参数列表:func(args...)
  • 模板参数列表:Class<Types...>
  • 初始化列表:{args...}
  • 列表:class Derived : public Bases...
  • 成员初始化列表:Bases()...

快速上手

基本的可变参数模板

#include <iostream>

// 使用折叠表达式打印所有参数
template<typename... Args>
void print(Args... args) {
    ((std::cout << args << " "), ...);  // C++17 折叠表达式
    std::cout << '\n';
}

// 使用
print(1, "hello", 3.14);  // 输出: 1 hello 3.14
print("仅一个参数");        // 输出: 仅一个参数
print();                   // 输出: (空行)

折叠表达式的四种形式

// 1. 一元右折叠: (pack op ...)
// 展开为: e1 op (e2 op (... op en))
template<typename... Args>
auto sumRight(Args... args) {
    return (args + ...);  // 右折叠求和
}

// 2. 一元左折叠: (... op pack)
// 展开为: ((e1 op e2) op e3) op ...
template<typename... Args>
auto sumLeft(Args... args) {
    return (... + args);  // 左折叠求和
}

// 3. 二元右折叠: (pack op ... op init)
// 空包时返回 init
template<typename... Args>
auto sumSafe(Args... args) {
    return (args + ... + 0);  // 带初始值,空包返回 0
}

// 4. 二元左折叠: (init op ... op pack)
// 空包时返回 init
template<typename... Args>
bool allTrue(Args... args) {
    return (true && ... && args);  // 空包返回 true
}

// 验证
static_assert(sumRight(1, 2, 3) == 6);
static_assert(sumLeft(1, 2, 3) == 6);
static_assert(sumSafe() == 0);  // 空包安全
static_assert(allTrue(true, true, false) == false);

详细用法

递归式参数包处理(C++11 兼容)

#include <iostream>

// 递归终止条件
void print() { std::cout << std::endl; }

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

// 使用
print(1, 2.5, "hello", 'c');  // 输出: 1, 2.5, hello, c

完美转发参数包

#include <memory>
#include <utility>

// 完美转发所有参数到构造函数
template<typename T, typename... Args>
std::unique_ptr<T> makeUnique(Args&&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

// 多参数构造
class Config {
    std::string host_;
    int port_;
    bool verbose_;
public:
    Config(std::string host, int port, bool verbose)
        : host_(std::move(host)), port_(port), verbose_(verbose) {}
};

auto cfg = makeUnique<Config>("localhost", 8080, true);

变参模板与继承

// 混入模式:组合多个功能类
template<typename... Mixins>
class Composite : public Mixins... {
public:
    Composite(const Mixins&... mixins) : Mixins(mixins)... {}

    // 继承所有 Mixin 的方法
    using Mixins::method...;  // C++17: 继承所有基类的 method
};

// 定义功能类
struct Loggable { void log(const std::string& msg) { std::cout << msg << std::endl; } };
struct Serializable { std::string serialize() { return "{}"; } };
struct Validatable { bool validate() { return true; } };

// 组合使用
using Service = Composite<Loggable, Serializable, Validatable>;
Service svc;
svc.log("启动服务");
auto json = svc.serialize();
bool ok = svc.validate();

折叠表达式的多种运算符

#include <vector>

// 逗号运算符:对每个参数执行操作
template<typename... Args>
void forEach(Args... args) {
    ((std::cout << args << "\n"), ...);  // 每个参数输出一行
}

// 逻辑运算:检查所有/任一条件
template<typename... Predicates>
bool allOf(Predicates... preds) {
    return (... && preds());  // 所有谓词都为 true
}

template<typename... Predicates>
bool anyOf(Predicates... preds) {
    return (... || preds());  // 任一谓词为 true
}

// 位运算:合并标志
template<typename... Flags>
uint32_t combineFlags(Flags... flags) {
    return (flags | ...);  // 按位或合并
}

常见场景

通用访问者(Visitor)

#include <variant>
#include <string>

// 使用变参模板和折叠表达式实现多态访问者
template<typename... Visitors>
struct Overloaded : Visitors... {
    using Visitors::operator()...;  // C++17: 继承所有 operator()
};

// 推导指引
template<typename... Visitors>
Overloaded(Visitors...) -> Overloaded<Visitors...>;

// 使用
using Value = std::variant<int, double, std::string>;

Value v = 42;
std::visit(Overloaded{
    [](int i) { std::cout << "整数: " << i << std::endl; },
    [](double d) { std::cout << "浮点: " << d << std::endl; },
    [](const std::string& s) { std::cout << "字符串: " << s << std::endl; }
}, v);

编译期型检查

#include <type_traits>

// 检查所有类型是否满足条件
template<typename Condition, typename... Types>
constexpr bool allSatisfy = (Condition<Types>::value && ...);

// 检查任一类型是否满足条件
template<typename Condition, typename... Types>
constexpr bool anySatisfy = (Condition<Types>::value || ...);

// 使用
static_assert(allSatisfy<std::is_integral, int, long, short>);
static_assert(!allSatisfy<std::is_integral, int, double>);
static_assert(anySatisfy<std::is_integral, int, double>);

注意事项

  • 空参数包的一元折叠行为:&& 返回 true,|| 返回 false,逗号运算符返回 void,其他运算符的空包是编译错误
  • 二元折叠通过提供初始值解决了空包问题,如 (args + ... + 0) 对空包返回 0
  • 折叠表达式只能使用特定的运算符,不能使用自定义运算符
  • 参数包展开时每个元素必须是相同的”模式”,如 std::forward<Args>(args)...
  • 递归式参数包处理会增加编译时间和实例化深度,应优先使用折叠表达式

进阶用法

C++20 Lambda 捕获包展开

// C++20: 在 Lambda 初始化捕获中展开参数包
template<typename... Args>
auto makeCallback(Args... args) {
    // 将所有参数移动捕获到 Lambda 中
    return [... captures = std::move(args)]() {
        return (captures + ... + 0);  // 在 Lambda 内使用折叠表达式
    };
}

auto cb = makeCallback(1, 2, 3);
std::cout << cb() << std::endl;  // 6

编译期字符串哈希

// 使用变参模板实现编译期字符串哈希
template<char... Chars>
constexpr uint32_t operator""_hash() {
    uint32_t h = 0;
    ((h = h * 31 + static_cast<uint32_t>(Chars)), ...);  // 折叠表达式
    return h;
}

// 使用字面量运算符
constexpr auto cmd_hash = "start"_hash;