可变模板参数

 Tue, 21-Apr-2026 14:36:12

C++11 可变模板参数(Variadic Templates)

可变模板参数是 C++11 引入的重要特性,允许模板接受任意数量、任意类型的参数,极大地增强了模板的灵活性和表达能力。
下面通过代码示例从基础到进阶逐步讲解。


1. 基础语法:定义可变参数模板

#include <iostream>

// 声明一个可变参数的函数模板
// Args 是一个模板参数包,可以包含零个或多个类型
template<typename... Args>
void print(Args... args) {
    // args 是一个函数参数包,包含零个或多个实际参数值
    std::cout << "参数数量: " << sizeof...(args) << std::endl;
}

int main() {
    print();                // 参数数量: 0
    print(1);               // 参数数量: 1
    print(1, 3.14, "hello"); // 参数数量: 3
    return 0;
}

2. 递归展开参数包

要逐个处理参数包中的每个元素,通常采用递归 + 特化的方式。

// 递归终止函数(无参数版本)
void print_all() {
    std::cout << std::endl;  // 换行
}

// 可变参数模板:处理第一个参数 + 剩余参数
template<typename T, typename... Rest>
void print_all(T first, Rest... rest) {
    std::cout << first << " ";   // 处理当前参数
    print_all(rest...);          // 递归调用,展开剩余参数
}

int main() {
    print_all(1, 3.14, "hello", 'c');
    // 输出:1 3.14 hello c 
    return 0;
}

执行过程

  1. print_all(1, 3.14, "hello", 'c') → 打印 1,调用 print_all(3.14, "hello", 'c')

  2. print_all(3.14, "hello", 'c') → 打印 3.14,调用 print_all("hello", 'c')

  3. print_all("hello", 'c') → 打印 hello,调用 print_all('c')

  4. print_all('c') → 匹配 T=char, Rest=空,打印 c,调用 print_all()(终止函数)

  5. print_all() 换行结束。


3. 使用初始化列表展开(无递归)

C++11 中可以利用初始化列表和逗号运算符在一条语句中展开参数包,避免递归。

#include <iostream>

template<typename... Args>
void print_all(Args... args) {
    // 使用一个 int 数组的初始化列表来触发包展开
    int dummy[] = { (std::cout << args << " ", 0)... };
    // 展开后相当于:
    // { (cout << 1 << " ", 0), (cout << 3.14 << " ", 0), (cout << "hello" << " ", 0) };
    // 逗号运算符返回 0,所以数组元素全是 0
    (void)dummy; // 避免编译器未使用变量的警告
    std::cout << std::endl;
}

int main() {
    print_all(1, 3.14, "hello");
    return 0;
}

更简洁的 C++17 写法(折叠表达式):

template<typename... Args>
void print_all(Args... args) {
    (std::cout << ... << args) << std::endl;  // 二元左折叠
}

4. 完美转发与可变参数模板

在实际库实现中(如 std::make_uniquestd::thread 构造函数),往往需要将参数完美转发给其他函数。

#include <iostream>
#include <utility>

// 目标函数:接收一个左值或右值
void target(int&& x) {
    std::cout << "右值: " << x << std::endl;
}
void target(int& x) {
    std::cout << "左值: " << x << std::endl;
}

// 包装器:完美转发可变参数
template<typename Func, typename... Args>
void wrapper(Func&& f, Args&&... args) {
    // 将参数包完美转发给函数 f
    f(std::forward<Args>(args)...);
}

int main() {
    int a = 10;
    wrapper(target, a);    // 左值
    wrapper(target, 20);   // 右值
    return 0;
}

5. 构建元组(std::tuple 的原理示意)

std::tuple 就是利用可变模板参数存储任意多个不同类型的值。下面是一个极简实现:

#include <iostream>

// 前向声明
template<typename... Types>
class Tuple;

// 特化:空元组
template<>
class Tuple<> {};

// 递归定义:一个头部 + 尾部元组
template<typename Head, typename... Tail>
class Tuple<Head, Tail...> : public Tuple<Tail...> {
private:
    Head value;
public:
    Tuple(Head h, Tail... t) : Tuple<Tail...>(t...), value(h) {}

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

    // 继承 Tuple<Tail...>,可以访问尾部
    Tuple<Tail...>& tail() { return *this; }
    const Tuple<Tail...>& tail() const { return *this; }
};

// 辅助函数:类似 std::get<0>(tuple)
template<size_t N, typename Head, typename... Tail>
auto& get(Tuple<Head, Tail...>& t) {
    if constexpr (N == 0)
        return t.head();
    else
        return get<N-1>(t.tail());
}

int main() {
    Tuple<int, double, std::string> t(42, 3.14, "hello");
    std::cout << get<0>(t) << ", " << get<1>(t) << ", " << get<2>(t) << std::endl;
    return 0;
}

6. 可变参数模板与 sizeof...

sizeof... 运算符可以在编译期获得参数包中的元素个数,常用于静态断言或分配数组长度。

template<typename... Args>
void check_size(Args... args) {
    static_assert(sizeof...(args) > 0, "至少需要一个参数");
    // ...
}

7. 实际应用:类型安全的 printf

利用可变模板参数实现一个类似 printf 但类型安全的函数:

#include <iostream>
#include <string>

void safe_printf(const char* fmt) {
    // 无参数时,直接打印格式字符串(要求 fmt 中没有 %)
    while (*fmt) {
        if (*fmt == '%' && *(fmt+1) == '%') ++fmt; // 跳过 %%
        else if (*fmt == '%') throw std::runtime_error("参数不足");
        else std::cout << *fmt++;
    }
}

template<typename T, typename... Args>
void safe_printf(const char* fmt, T value, Args... args) {
    while (*fmt) {
        if (*fmt == '%' && *(fmt+1) == '%') {
            ++fmt;
            std::cout << '%';
        }
        else if (*fmt == '%') {
            std::cout << value;           // 打印当前参数
            safe_printf(fmt+1, args...);  // 递归处理剩余参数
            return;
        }
        else {
            std::cout << *fmt;
        }
        ++fmt;
    }
    // 如果格式字符串结束但还有参数未使用,可以报错或忽略
}

int main() {
    safe_printf("整数:%,浮点数:%,字符串:%\n", 42, 3.14, std::string("hello"));
    return 0;
}

8. 注意事项


总结

C++11 的可变模板参数通过参数包包展开机制,实现了对任意数量、任意类型参数的泛型处理。
它被广泛应用于标准库中:std::tuplestd::functionstd::make_uniquestd::threadstd::async 等。
掌握它能够编写出高度灵活且类型安全的泛型代码。