【C++可变模板参数】

C++11 可变模板参数总结:搞懂参数包、包扩展和 emplace


1. 为什么 C++11 需要可变模板参数?

在 C++11 之前,如果我们想写一个"参数个数可变、参数类型也可变"的函数,基本只能靠:

  • 写很多重载
  • 或者用 ...(C 风格可变参数,类型不安全)

而可变模板参数(Variadic Templates)解决的是:

  • 参数个数可变
  • 参数类型可变
  • 编译期类型安全
  • 能和泛型、右值引用、完美转发组合

一句话:它是现代 C++ 泛型编程的基础设施。


2. 基础语法:模板参数包 & 函数参数包

2.1 模板参数包

cpp 复制代码
template<class... Args>

Args 代表"0 个或多个类型"。

2.2 函数参数包

cpp 复制代码
template<class... Args>
void Func(Args... args) {}

args 代表"0 个或多个函数参数"。

如果要支持完美转发,常见写法是:

cpp 复制代码
template<class... Args>
void Func(Args&&... args) {}

3. sizeof...:统计参数包个数

cpp 复制代码
#include <iostream>
using namespace std;

template<class... Args>
void PrintCount(Args&&... args)
{
    cout << sizeof...(args) << endl; // 参数个数
    //sizeof...(Args) 也可统计
}

int main()
{
    double x = 2.2;
    PrintCount();                         // 0
    PrintCount(1);                        // 1
    PrintCount(1, string("xxxxx"));       // 2
    PrintCount(1.1, string("xxxxx"), x);  // 3
}

4. 包扩展(最核心)

参数包不能像数组那样 args[i] 访问,能做的核心操作就是"展开(expand)"。

4.1 递归展开(C++11 经典写法)

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// 递归终止
void ShowList()
{
    cout << endl;
}

// 每次取一个,剩下的继续递归
template<class T, class... Args>
void ShowList(T x, Args... args)
{
    cout << x << " ";
    ShowList(args...);
}

template<class... Args>
void Print(Args... args)
{
    ShowList(args...);
}

int main()
{
    Print();
    Print(1);
    Print(1, string("hello"));
    Print(1, string("hello"), 2.2);
}

这段代码的本质:编译器会在编译期"展开"为多组具体函数调用。

4.2 模式扩展:Func(args...) 不是唯一玩法

cpp 复制代码
template<class T>
const T& GetArg(const T& x)
{
    cout << x << " ";
    return x;
}

template<class... Args>
void Arguments(Args... args) {}

template<class... Args>
void Print(Args... args)
{
    Arguments(GetArg(args)...); // 包扩展模式:对每个 args 套一层 GetArg
}

关键理解:GetArg(args)... 等价于
GetArg(arg1), GetArg(arg2), GetArg(arg3)...


5. 可变模板参数 + 完美转发(高频考点)

5.1 为什么要 std::forward<Args>(args)...

因为:有名字的变量都是左值表达式

如果你只写 args...,右值信息会丢失,可能触发错误重载或性能下降。

cpp 复制代码
template<class... Args>
void Wrapper(Args&&... args)
{
    Target(std::forward<Args>(args)...); // 保留原始值类别
}

5.2 一句话区分

  • std::move(x):无脑转右值
  • std::forward<T>(x):按模板推导结果"有条件转发"

6. 真实价值场景:emplace 系列接口

emplace_back / emplace 本质是可变模板参数 + 完美转发。

cpp 复制代码
template<class... Args>
void emplace_back(Args&&... args);

template<class... Args>
iterator emplace(const_iterator pos, Args&&... args);

相比 push_back

  • push_back 往往是"先构造对象,再拷贝/移动进容器"
  • emplace_back 可以"直接把构造参数传到底层,在容器内存上原地构造"

示例理解:

cpp 复制代码
std::vector<std::pair<std::string, int>> v;
v.emplace_back("apple", 1); // 直接构造 pair<string,int>

这就是"参数包一路转发到节点/元素构造函数"的价值。


7. 可变模板参数常见坑

7.1 不能把参数包当数组用

错误思路:

cpp 复制代码
// args[i] // 不支持

参数包不是运行时容器,而是编译期语法结构。

7.2 忘记递归终止函数

递归展开必须有"空包终止版本",不然会无限模板实例化报错。

7.3 忘记完美转发导致右值退化

Args&&... args + 下层调用 foo(args...),会把右值当左值传下去。

7.4 const 对象即使 move 也不一定能移动

const T 通常无法真正"偷资源",很多时候仍走拷贝语义。

相关推荐
为何创造硅基生物11 小时前
C语言 结构体内存对齐规则(通俗易懂版)
c语言·开发语言
吃好睡好便好11 小时前
在Matlab中绘制横直方图
开发语言·学习·算法·matlab
星寂樱易李11 小时前
iperf3 + Python-- 网络带宽、网速、网络稳定性
开发语言·网络·python
仰泳之鹅11 小时前
【C语言】自定义数据类型2——联合体与枚举
c语言·开发语言·算法
之歆12 小时前
DAY_12JavaScript DOM 完全指南(二):实战与性能篇
开发语言·前端·javascript·ecmascript
于小猿Sup13 小时前
VMware在Ubuntu22.04驱动Livox Mid360s
linux·c++·嵌入式硬件·自动驾驶
cen__y13 小时前
Linux12(Git01)
linux·运维·服务器·c语言·开发语言·git
AI人工智能+电脑小能手13 小时前
【大白话说Java面试题 第65题】【JVM篇】第25题:谈谈对 OOM 的认识
java·开发语言·jvm
社交怪人13 小时前
【算平均分】信息学奥赛一本通C语言解法(题号2071)
c语言·开发语言
郭涤生14 小时前
不同主机之间网络通信-以太网连接复习
开发语言·rk3588