什么是"可变参数模板"
template<typename... Args>void func(Args... args) { } // 0个、1个、3个、100个都行func(); // 0个参数func(1); // 1个:Args = {int}func(1, 2.5, "hi"); // 3个:Args = {int, double, const char*}
三个核心语法
1. typename... Args —— 声明参数包
... 是关键,意思是"这里可以有零个或多个类型":
template<typename... Args> // Args 是"类型参数包"voidfunc(Args... args){ // args 是"值参数包"}
2. sizeof...(Args) —— 获取参数个数
template<typename... Args>void func(Args... args) {std::cout << sizeof...(Args) << "\n"; // 编译期执行,类型数量std::cout << sizeof...(args) << "\n"; // 编译期,值数量(和上面一样)}func(); // 0func(1); // 1func(1, 2.5, 3); // 3
注意:sizeof... 是编译期常量,不是运行时函数。
3. args... —— 展开参数包
参数包不能直接用,必须"展开"——把里面每个元素取出来:
template<typename... Args>void func(Args... args) {// args 不能直接 cout// std::cout << args; 编译错误// 展开方式1:递归(C++11,后面详细讲)// 展开方式2:fold expression(C++17,后面详细讲)}
C++11:递归展开(核心难点)
C++11 展开 参数包 的唯一方法:写一个递归函数,每次剥一个参数。
经典例子:打印所有参数
// 1. 递归终止条件:0个参数时调用这个voidprint(){std::cout << "\n";}// 2. 递归本体:剥第一个参数,剩下的继续递归template<typename T, typename... Rest>void print(T first, Rest... rest) {std::cout << first << " ";print(rest...); // 递归调用,参数少了一个}// 使用print(1, 2.5, "hello");// 第1次:first=1, rest={2.5, "hello"} → 打印 "1 "// 第2次:first=2.5, rest={"hello"} → 打印 "2.5 "// 第3次:first="hello", rest={} → 打印 "hello "// 第4次:print() 终止 → 打印换行
C++14:没有新语法,但泛型 lambda 让代码更简洁
template<typename... Args>voidsubmit_task(Args... args) {auto task = [args...]() { // C++14:拷贝捕获参数包(不能move)process(args...);};task();}
C++17:Fold Expression —— 一行展开(革命性简化)
C++17 之前,展开 参数包 必须写递归(终止函数 + 递归函数 = 至少2个函数)。
C++17 的 fold expression 让你一行搞定。语法:
(args + ...) // 一元右折叠:(arg1 + (arg2 + (arg3 + arg4)))(... + args) // 一元左折叠:(((arg1 + arg2) + arg3) + arg4)(args + ... + 0) // 二元右折叠:(arg1 + (arg2 + (arg3 + (arg4 + 0))))(0 + ... + args) // 二元左折叠:((((0 + arg1) + arg2) + arg3) + arg4)
实际例子:
// 求和 —— 以前要写2个函数,现在一行template<typename... Args>intsum(Args... args) {return (args + ...); // 一行!}sum(1, 2, 3); // 6sum(1, 2, 3, 4, 5); // 15// 求乘积template<typename... Args>intproduct(Args... args) {return (args * ...);}product(2, 3, 4); // 24
带初始值的二元折叠(处理空参数包)
// 空参数包问题:(args || ...) // 空 → 编译成功 返回false(args && ...) // 空 → 编译成功 返回true(args * ...) // 空 → 编译错误!(args + ...) // 空 → 编译错误!// 二元折叠:给一个初始值(args + ... + 0) // 空参数包 → 返回 0(args * ... + 0) // 空 → 返回 0
夜雨聆风