【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 通常无法真正"偷资源",很多时候仍走拷贝语义。

相关推荐
Freak嵌入式2 小时前
MicroPython LVGL基础知识和概念:时序与动态效果
开发语言·python·github·php·gui·lvgl·micropython
2501_933329552 小时前
企业媒体发布与舆情管理实战:Infoseek舆情系统技术架构与落地解析
大数据·开发语言·人工智能·数据库开发
"菠萝"2 小时前
C#知识学习-021(文字关键字)
开发语言·学习·c#
minji...2 小时前
Linux 线程同步与互斥(二) 线程同步,条件变量,pthread_cond_init/wait/signal/broadcast
linux·运维·开发语言·jvm·数据结构·c++
zhangzeyuaaa2 小时前
Python 中的 Map 和 Reduce 详解
开发语言·python
梓䈑2 小时前
高性能 C++ 日志实战:spdlog 核心架构解析与最佳实践指南
c++·架构
游乐码3 小时前
c#HashTable
开发语言·c#
小白学大数据3 小时前
Scrapy 分布式爬虫:大规模采集汽车之家电车评论
开发语言·分布式·爬虫·scrapy