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