前置知识: C++

变参模板

00:00
3 min Advanced 2026/6/14

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

概述

可变参数模板(Variadic Templates)是 C++11 引入的核心特性,允许模板接受任意数量、任意类型的参数。这一特性是现代 C++ 元编程的基石,使得类型安全的泛型容器、完美转发的工厂函数和编译期类型遍历成为可能。C++17 引入的折叠表达式进一步简化了参数包的操作,消除了递归模板实例化的需要。

可变参数模板的典型应用包括:std::make_uniquestd::tuplestd::variantstd::visit标准库组件。

基础概念

参数包

  • 模板参数typename... Args,接受零个或类型参数
  • 函数参数Args... args,接受零个或函数参数
  • 参数包展开args...,将参数包展开为逗号分隔的参数列表

sizeof… 运算符

template<typename... Args>
void printCount() {
    std::cout << "参数数量: " << sizeof...(Args) << std::endl;
    std::cout << "参数数量: " << sizeof...(Args) << std::endl;
}

printCount<int, double, std::string>();  // 参数数量: 3
printCount<>();                           // 参数数量: 0

快速上手

基本的变参模板函数

#include <iostream>

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

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

折叠表达式

C++17 引入的折叠表达式操作参数的简洁语法

// 四种折叠形式

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

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

// 3. 二元右折叠: (pack op ... op init)
template<typename... Args>
auto sumWithInit(Args... args) {
    return (args + ... + 0);  // 带初始值的右折叠
}

// 4. 二元左折叠: (init op ... op pack)
template<typename... Args>
auto allTrue(Args... args) {
    return (true && ... && args);  // 带初始值的左折叠,空包返回 true
}

详细用法

递归式参数包处理(C++11/14 风格)

#include <iostream>

// 递归终止条件
void print() {}

// 递归展开参数包
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");  // 输出: 1, 2.5, hello

完美转发与变参模板

#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)...));
}

// 使用
auto ptr = makeUnique<std::string>(5, 'a');  // 构造 "aaaaa"
auto ptr2 = makeUnique<std::string>("hello");  // 构造 "hello"

变参模板与 tuple

#include <tuple>
#include <iostream>

// 遍历 tuple 的每个元素
template<typename Tuple, typename Func, size_t... Is>
void forEachImpl(Tuple& t, Func&& func, std::index_sequence<Is...>) {
    (func(std::get<Is>(t)), ...);  // 折叠表达式遍历
}

template<typename... Args, typename Func>
void forEach(std::tuple<Args...>& t, Func&& func) {
    forEachImpl(t, std::forward<Func>(func),
        std::index_sequence_for<Args...>{});
}

// 使用
auto data = std::make_tuple(42, 3.14, "hello");
forEach(data, [](const auto& item) {
    std::cout << item << " ";
});
// 输出: 42 3.14 hello

变参模板类

// 类型列表
template<typename... Types>
struct TypeList {};

// 获取类型数量
template<typename List>
struct Size;

template<typename... Types>
struct Size<TypeList<Types...>> {
    static constexpr size_t value = sizeof...(Types);
};

// 在头部添加类型
template<typename T, typename List>
struct PushFront;

template<typename T, typename... Types>
struct PushFront<T, TypeList<Types...>> {
    using type = TypeList<T, Types...>;
};

// 使用
using MyList = TypeList<int, double, std::string>;
static_assert(Size<MyList>::value == 3);

using Extended = PushFront<char, MyList>::type;
static_assert(Size<Extended>::value == 4);

常见场景

类型安全的 printf

#include <iostream>
#include <string>

// 类型安全的格式化输出
template<typename T>
void formatPrint(std::ostream& os, const std::string& fmt, const T& value) {
    auto pos = fmt.find("{}");
    if (pos != std::string::npos) {
        os << fmt.substr(0, pos) << value << fmt.substr(pos + 2);
    }
}

template<typename T, typename... Args>
void formatPrint(std::ostream& os, const std::string& fmt,
                 const T& first, const Args&... rest) {
    auto pos = fmt.find("{}");
    if (pos != std::string::npos) {
        os << fmt.substr(0, pos) << first;
        formatPrint(os, fmt.substr(pos + 2), rest...);
    }
}

// 使用
formatPrint(std::cout, "姓名: {}, 年龄: {}, 分数: {}\n",
            "张三", 25, 95.5);
// 输出: 姓名: 张三, 年龄: 25, 分数: 95.5

多类型事件系统

#include <functional>
#include <unordered_map>
#include <any>

// 支持多种事件类型的事件总线
class EventBus {
    std::unordered_map<size_t, std::any> handlers_;

public:
    template<typename Event, typename Handler>
    void subscribe(Handler&& handler) {
        handlers_[typeid(Event).hash_code()] =
            std::function<void(const Event&)>(std::forward<Handler>(handler));
    }

    template<typename Event>
    void publish(const Event& event) {
        auto it = handlers_.find(typeid(Event).hash_code());
        if (it != handlers_.end()) {
            auto handler = std::any_cast<std::function<void(const Event&)>>(&it->second);
            if (handler) (*handler)(event);
        }
    }
};

注意事项

  • 参数包必须在展开时使用,不能直接参数包算术运算
  • sizeof...(Args)编译常量,可用于模板条件和 static_assert
  • 折叠表达式中的空参数包行为:&& 返回 true|| 返回 false+ 需要 + 0 初始值* 需要 * 1 初始值
  • 递归参数包处理会增加编译时间编译产物大小,优先使用折叠表达式
  • 参数包展开位置限制:只能在函数参数列表模板参数初始化列表、基等处展开

进阶用法

编译期类型遍历

#include <variant>

// 编译期遍历 variant 的所有可能类型
template<typename Variant, typename Func, size_t... Is>
auto visitAllImpl(Func&& func, std::index_sequence<Is...>) {
    return std::array{func(std::variant_alternative_t<Is, Variant>{})...};
}

template<typename Variant, typename Func>
auto visitAll(Func&& func) {
    return visitAllImpl<Variant>(
        std::forward<Func>(func),
        std::make_index_sequence<std::variant_size_v<Variant>>{}
    );
}

// 使用
using MyVariant = std::variant<int, double, std::string>;
auto results = visitAll<MyVariant>([](auto val) {
    return typeid(val).name();
});

C++20 Lambda 捕获包展开

// C++20: 在 Lambda 中展开参数包
template<typename... Args>
void forEachArg(Args... args) {
    // 使用初始化捕获展开参数包
    [... captures = std::move(args)]() {
        ((std::cout << captures << "\n"), ...);
    }();
}

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式