先给你一句终极人话(背下来,这就是全部)
Args = 你随便起的一个名字,跟 int a 里的 a 一样,想改成 ABC、Params、Shit 都行,没有任何魔法。
… = 只有两个功能:打包、拆包
我只盯着这段代码讲,别的一概不提
线程池里这段,是你唯一要懂的:
template<typename F, typename… Args>
void submit(F&& f, Args&&… args) {
auto task = std::bind(std::forward<F>(f), std::forward<Args>(args)…);
}
我逐字、逐符号,翻译成人话。
第一行:template<typename F, typename… Args>
1. typename F
就是:我要接收一个 “要执行的函数 / 任务”,给它起个名叫 F。
2. typename… Args
- … 在左边 → 打包
- Args 是包名, 代表着大量的各种各样的参数类型名
- 整句翻译:我还要接收 “一堆随便多少个、随便什么类型的参数”,我把这一堆打包,包名叫 Args
“一堆” 可以是:
- 0 个
- 1 个:int
- 2 个:int + string
- 3 个:int + string + double
- 100 个都行
这里的 … 就是:允许传 “一堆”,而不是固定一个。
你完全可以写成:
template<typename F, typename… ABC>
void submit(F&& f, ABC&&… abc)
功能一模一样,Args 就是个名字,别把它当神仙。
第二行:void submit(F&& f, Args&&… args)
1. F&& f
对应上面的 F:那个要执行的函数本体,叫 f。
2. Args&&… args
- Args:上面打包的那 “一堆类型”
- … 在左边 → 继续打包
- args:给 “这一堆参数的本体” 起的名字
- 整句翻译:这是跟上面那包类型对应的、一堆真实的参数,叫 args
大白话:
- Args = 一包类型(int、string、double…)
- args = 一包数值(10、"hello"、3.14…)
第三行:std::forward<Args>(args)…
这里的 … 在最右边 → 拆包!
翻译:
把刚才打包好的 args 这一包参数,一个一个拆出来,挨个传给 std::bind。
我给你看「编译器实际做了什么」,你瞬间就懂
你写一行通用代码,编译器会根据你传的参数,自动生成对应代码。
例子 1:你提交 1 个参数
pool.submit(函数, 10);
编译器看到后,自动把你的模板展开成:
template<typename F, typename… Args>
// Args 被自动推导为:int
void submit(F&& f, int&& args) {
auto task = std::bind(…, std::forward<int>(args));
}
例子 2:你提交 2 个参数
pool.submit(Calculate(), 5, "square");
编译器自动展开成:
template<typename F, typename… Args>
// Args 被自动推导为:int, const char*
void submit(F&& f, int&& args1, const char*&& args2) {
auto task = std::bind(
…,
std::forward<int>(args1),
std::forward<const char*>(args2)
);
}
例子 3:你提交 3 个参数
pool.submit(func, 1, 2.2, "abc");
编译器自动展开成:
template<typename F, typename… Args>
// Args = int, double, const char*
void submit(F&& f, int&& a1, double&& a2, const char*&& a3) {
auto task = std::bind(
…,
std::forward<int>(a1),
std::forward<double>(a2),
std::forward<const char*>(a3)
);
}
现在告诉你:为什么要写这玩意?
如果不写 …,你必须给 1 个参数、2 个参数、3 个参数… 各写一个函数:
// 不支持可变参,你要写死人
void submit(F f) { … }
void submit(F f, A1 a1) { … }
void submit(F f, A1 a1, A2 a2) { … }
void submit(F f, A1 a1, A2 a2, A3 a3) { … }
写了 typename… Args + Args&&… args + args…你写 1 行,编译器自动帮你写 1000 行重载。
这就是它唯一的作用。
我再把三个 … 汇总,用最蠢的话再说一遍
只看这三句,记住就毕业:
typename… Args→ 我要收一堆类型,打包,包名叫 Args
Args&&… args→ 我要收一堆对应的值,打包,包名叫 args
args…→ 把 args 这一包,拆成一个一个,挨个用
C++ 把 **“打包” 和 “拆包” 用同一个符号 … 表示 **,写在左边是打包,写在右边是拆包,长得一模一样,所以看起来很复杂;
这不是你的问题,是 C++ 语法设计得反人类。
最后给你一个 “傻瓜版写法”,你以后照抄就行
你以后写任意 “接收随便多少参数” 的函数,固定照抄这个模板:
template<typename F, typename… 随便起个名>
void 函数名(F&& f, 随便起个名&&… 随便起个值名) {
用到参数的地方写:随便起个值名…
}
放到线程池,就是:
template<typename F, typename… Args>
void submit(F&& f, Args&&… args) {
auto task = std::bind(std::forward<F>(f), std::forward<Args>(args)…);
}
C++11递归式调用
示例 1:
基础版 —— 打印任意数量、任意类型的参数(无返回值)
这是最直观的例子,能清晰看到参数包的定义和展开逻辑:
#include <iostream>
#include <string>
// 步骤1:定义「递归终止函数」(必须!参数包拆空时调用)
// 作用:当参数包没有参数时,结束递归
void print_all() {
std::cout << "\\n参数打印完成\\n";
}
// 步骤2:定义可变参数模板函数(核心)
// T:第一个参数的类型;Args… rest:剩余参数的参数包
template<typename T, typename… Args>
void print_all(T first, Args… rest) {
// 1. 处理第一个参数(类型安全,编译器知道T的类型)
std::cout << "参数:" << first << " | 类型:" << typeid(T).name() << " ";
// 2. 递归调用:把剩余参数包传进去,逐步拆解
print_all(rest…); // "rest…" 是参数包展开,拆成单个参数
}
// 测试:传任意数量、任意类型的参数
int main() {
// 测试1:传int、string、double、bool
print_all(100, std::string("hello"), 3.14, true);
// 测试2:传0个参数(触发递归终止函数)
print_all();
// 测试3:传自定义类型(可变参数模板的优势——支持任意类型)
struct Person { std::string name; int age; };
// 给Person重载<<,才能打印
std::ostream& operator<<(std::ostream& os, const Person& p) {
return os << "Person(" << p.name << "," << p.age << ")";
}
print_all(Person{"张三", 20}, Person{"李四", 25});
return 0;
}
输出示例(类型名因编译器不同略有差异):
参数:100 | 类型:int 参数:hello | 类型:basic_string<char,…> 参数:3.14 | 类型:double 参数:1 | 类型:bool
参数打印完成
参数打印完成
参数:Person(张三,20) | 类型:Person 参数:Person(李四,25) | 类型:Person
参数打印完成
核心语法解释:
- template<typename T, typename… Args>:定义模板参数包,Args 表示 “任意数量的类型”;
- void print_all(T first, Args… rest):函数参数包,first 是第一个参数,rest 是剩余参数的集合;
- print_all(rest…):参数包展开,把 rest 拆成单个参数传给下一次递归;
- 递归终止函数 print_all():参数包拆空时必须有这个函数,否则编译器会报错(递归无法终止)。
示例 2:
进阶版 —— 求和任意数量的数值型参数(有返回值)
展示如何处理 “有返回值 + 类型限制” 的场景(比如只允许数值类型):
#include <iostream>
#include <type_traits> // 用于类型检查
// 递归终止:0个参数时返回0
template<typename T = int>
constexpr T sum_all() {
return 0;
}
// 可变参数模板:求和任意数值类型参数
template<typename T, typename… Args>
auto sum_all(T first, Args… rest) -> std::enable_if_t<std::is_arithmetic_v<T>, decltype(first)> {
// 静态断言:确保所有参数都是数值类型(int/double/float等)
static_assert((std::is_arithmetic_v<Args> && …), "所有参数必须是数值类型!");
// 递归求和:第一个参数 + 剩余参数的和
return first + sum_all(rest…);
}
int main() {
// 测试1:int + double
std::cout << "1+2.5+3 = " << sum_all(1, 2.5, 3) << std::endl; // 6.5
// 测试2:仅1个参数
std::cout << "100 = " << sum_all(100) << std::endl; // 100
// 测试3:错误示例(传字符串,触发静态断言)
// std::cout << sum_all(1, "hello") << std::endl; // 编译报错:所有参数必须是数值类型!
return 0;
}
C++17 折叠表达式(无递归,推荐)
折叠表达式是 C++17 专门为参数包设计的语法,能直接 “展开” 参数包并执行批量操作(比如打印、求和),完全不需要递归。
示例 3:无递归打印任意参数(最直观)
#include <iostream>
#include <string>
// 无递归!折叠表达式直接展开参数包
template<typename… Args>
void print_all(Args&&… args) {
// 折叠表达式核心:(操作, …) 或 (… , 操作)
// 这里是:对每个参数执行 cout << args << " ",用逗号分隔
(std::cout << … << args) << " | 打印完成\\n";
}
int main() {
// 测试任意参数
print_all(100, std::string("hello"), 3.14, true);
print_all(); // 0个参数也能处理(折叠表达式空展开,无输出)
return 0;
}
输出:
100hello3.141 | 打印完成
| 打印完成
折叠表达式核心语法解释:
(std::cout << … << args) 是二元左折叠,等价于:
// 如果传 100, "hello", 3.14 → 展开为:
std::cout << 100 << "hello" << 3.14;
折叠表达式的通用格式:
- 左折叠:(init op … op args) → 从左到右展开(比如 (a + … + args));
- 右折叠:(args op … op init) → 从右到左展开;
- 空折叠:参数包为空时,数值类型返回 0,逻辑类型返回 true,流操作无输出(安全)。
示例 2:无递归求和任意数值参数
#include <iostream>
#include <type_traits>
// 无递归求和:折叠表达式 + 类型检查
template<typename… Args>
auto sum_all(Args&&… args) {
// 静态断言:确保所有参数都是数值类型
static_assert((std::is_arithmetic_v<Args> && …), "必须传数值类型!");
// 折叠表达式求和:1 + 2 + 3 → (1 + (2 + 3))
return (… + args);
}
int main() {
std::cout << "1+2.5+3 = " << sum_all(1, 2.5, 3) << std::endl; // 6.5
std::cout << "仅1个参数:" << sum_all(100) << std::endl; // 100
std::cout << "0个参数:" << sum_all() << std::endl; // 0(空折叠默认值)
return 0;
}
示例 3:无递归处理复杂逻辑(比如每个参数执行自定义操作)
如果需要对每个参数执行多步操作(比如打印值 + 类型),可以用「折叠表达式 + lambda」:
#include <iostream>
#include <string>
#include <typeinfo>
template<typename… Args>
void print_with_type(Args&&… args) {
// 对每个参数执行lambda,用逗号折叠(执行多个操作)
(
[&](auto&& arg) {
std::cout << "值:" << arg << " | 类型:" << typeid(arg).name() << "\\n";
}(std::forward<Args>(args)),
…
);
}
int main() {
print_with_type(100, std::string("hello"), 3.14);
return 0;
}
输出(类型名因编译器略有差异):
值:100 | 类型:int
值:hello | 类型:basic_string<char,…>
值:3.14 | 类型:double
递归 vs 折叠表达式 对比
| 递归 | C++11+ | 中等 | 中等 | 高 | 兼容旧编译器、复杂逻辑 |
| 折叠表达式 | C++17+ | 极高 | 极高 | 高 | 现代编译器、简单 / 复杂逻辑 |


