本文是 C++ 系列教程的第 18 篇。上一篇讲解了特化与类型萃取,本篇深入模板高级技巧:变参模板(参数包、sizeof…、递归展开)、C++17 折叠表达式、SFINAE 与 enable_if、void_t 技巧、C++20 concepts 预告。
一、变参模板
1.1 什么是变参模板
变参模板(Variadic Templates)允许模板接受任意数量的参数,C++11 引入:
// 参数包:typename... Argstemplate<typename...Args>voidprint(Args...args);Args...是类型参数包。args...是函数参数包。sizeof...(Args)获取参数个数。
1.2 参数包展开与递归
#include<iostream>usingnamespacestd;// 递归终止条件:空参数版本voidprintAll(){cout<<endl;}// 递归展开:取出第一个参数,剩余继续递归template<typenameT,typename...Rest>voidprintAll(T first,Rest...rest){cout<<first<<" ";printAll(rest...);// 递归调用剩余参数}intmain(){printAll(1,2.5,"hello",'a');// 1 2.5 hello aprintAll(10);// 10return0;}1.3 sizeof… 获取参数个数
#include<iostream>usingnamespacestd;template<typename...Args>voidcountArgs(Args...args){cout<<"参数个数: "<<sizeof...(Args)<<endl;cout<<"参数个数: "<<sizeof...(args)<<endl;// 等价}intmain(){countArgs();// 0countArgs(1);// 1countArgs(1,2.5,"three");// 3return0;}二、折叠表达式(C++17)
2.1 折叠表达式语法
折叠表达式对参数包中的全部元素应用二元运算符,大大简化变参运算:
| 形式 | 含义 |
|---|---|
(pack op ...) | 右折叠:a op (b op (c op init)) |
(... op pack) | 左折叠:((init op a) op b) op c |
(pack op ... op init) | 带初始值的右折叠 |
(init op ... op pack) | 带初始值的左折叠 |
2.2 折叠求和
#include<iostream>usingnamespacestd;// C++17 折叠:一行实现任意个数求和template<typename...Args>autosum(Args...args){return(args+...+0);// 右折叠,初始值 0}// 左折叠template<typename...Args>autosumLeft(Args...args){return(0+...+args);}intmain(){cout<<sum(1,2,3,4,5)<<end l;// 15cout<<sum(1.5,2.5,3.0)<<endl;// 7cout<<sum()<<endl;// 0cout<<sumLeft(1,2,3)<<endl;// 6return0;}2.3 折叠打印(对比递归)
#include<iostream>usingnamespacestd;// 用逗号运算符折叠打印template<typename...Args>voidprintFold(Args...args){// (cout << ... << args):左折叠// 逐个输出((cout<<args<<" "),...);// 逗号折叠cout<<endl;}// 更实用的版本:带分隔符template<typename...Args>voidprintWithSep(constchar*sep,Args...args){// 第一个直接输出,其余带分隔符((cout<<args),...);// 简化版cout<<endl;}intmain(){printFold(1,2.5,"hi",'x');// 1 2.5 hi xprintWithSep(", ",1,2,3);return0;}2.4 折叠判断(逻辑运算)
#include<iostream>usingnamespacestd;// 全部满足条件(&& 折叠)template<typename...Args>boolallPositive(Args...args){return((args>0)&&...);}// 任一满足条件(|| 折叠)template<typename...Args>boolanyZero(Args...args){return((args==0)||...);}intmain(){cout<<allPositive(1,2,3)<<endl;// 1cout<<allPositive(1,-2,3)<<endl;// 0cout<<anyZero(1,2,0,4)<<endl;// 1cout<<anyZero(1,2,3)<<endl;// 0return0;}三、SFINAE 基础
3.1 什么是 SFINAE
SFINAE(Substitution Failure Is Not An Error,替换失败不是错误):模板实例化时,如果某个候选替换失败(如类型不支持某操作),编译器不会报错,而是继续尝试其他候选。
3.2 利用 SFINAE 做类型判断
#include<iostream>#include<type_traits>usingnamespacestd;// 通用版本:不支持 operator<< 的类型走这里template<typenameT>voidprintValue(constT&value,...){cout<<"不支持输出: (未知类型)"<<endl;}// 精确版本:支持 operator<< 的类型走这里template<typenameT,typename=decltype(cout<<declval<constT&>())>voidprintValue(constT&value,int){cout<<"值: "<<value<<endl;}intmain(){printValue(42,0);// 值: 42printValue(3.14,0);// 值: 3.14printValue("hello",0);// 值: helloreturn0;}3.
3 enable_if 条件启用
#include<iostream>#include<type_traits>usingnamespacestd;// 仅当 T 是整数时启用template<typenameT>typenameenable_if<is_integral<T>::value>::typeprocess(T value){cout<<"整数处理: "<<value<<endl;}// 仅当 T 是浮点时启用template<typenameT>typenameenable_if<is_floating_point<T>::value>::typeprocess(T value){cout<<"浮点处理: "<<value<<endl;}intmain(){process(42);// 整数处理: 42process(3.14);// 浮点处理: 3.14// process("hi"); // 错误!string 不满足任何版本return0;}3.4 enable_if 的两种写法
#include<iostream>#include<type_traits>usingnamespacestd;// 写法一:返回类型中启用template<typenameT>typenameenable_if<is_integral<T>::value,T>::typesquare(T x){returnx*x;}// 写法二:模板参数中启用(C++11 更通用)template<typenameT,typename=typenameenable_if<is_floating_point<T>::value>::type>doublesquareDouble(T x){returnx*x;}intmain(){cout<<square(5)<<endl;// 25(int 版本)cout<<squareDouble(2.5)<<endl;// 6.25(double 版本)return0;}四、void_t 技巧
4.1 void_t 检测特性
#include<iostream>#include<type_traits>usingnamespacestd;// void_t:任何类型都映射为 void(C++17 标准库提供)template<typename...>usingvoid_t=void;// 检测 T 是否有成员函数 size()template<typenameT,typename=void>structHasSize:false_type{};template<typenameT>structHasSize<T,void_t<decltype(declval<T>().size())>>:true_type{};// 检测 T 是否有成员 typetemplate<typenameT,typename=void>structHasType:false_type{};template<typenameT>structHasType<T,void_t<typenameT::type>>:true_type{};intmain(){cout<<"string 有 size(): "<<HasSize<string>::value<<endl;// 1cout<<"int 有 size(): "<<HasSize<int>::value<<endl;// 0structWithType{usingtype=int;};cout<<"WithType 有 type: "<<HasType<WithType>::value<<endl;// 1cout<<"int 有 type: "<<HasType<int>::value<<endl;// 0return0;}4.2 void_t 检测可调用性
#include<iostream>#include<type_traits>usingnamespacestd;template<typename...>usingvoid_t=void;// 检测 T 是否支持 operator<<template<typenameT,typename=void>structIsPrintable:false_type{};template<typenameT>structIsPrintable<T,void_t<decltype(cout<<declval<constT&>())>>:true_type{};structMyStruct{};// 不支持输出intmain(){cout<<"int 可打印: "<<IsPrintable<int>::value<<endl;// 1cout<<"string 可打印: "<<IsPrintable<string>::value<<endl;// 1cout<<"MyStruct 可打印: "<<IsPrintable<MyStruct>::value<<endl;// 0return0;}五、变参模板实战
5.1 类型安全的 printf
#include<iostream>usingnamespacestd;// 基础版本:无参数voidmyPrintf(constchar*format){cout<<format<<endl;}// 递归展开版本template<typenameT,typename...Args>voidmyPrintf(constchar*format,T value,Args...args){while(*format){if(*format=='%'&&*(format+1)=='d'){cout<<value;// 输出参数format+=2;myPrintf(format,args...);// 递归剩余return;}cout<<*format++;}}intmain(){myPrintf("数字: %d",42);myPrintf("%d 加 %d 等于 %d",1,2,3);return0;}5.2 变参构造函数(完美转发预览)
#include<iostream>#include<vector>usingnamespacestd;// 自定义容器:支持任意参数构造template<typenameT>classMyContainer{private:vector<T>data;public:// 变参构造函数template<typename...Args>MyContainer(Args...args){data.reserve(sizeof...(Args));(data.push_back(args),...);// C++17 逗号折叠}voidshow()const{for(constauto&item:data)cout<<item<<" ";cout<<endl;}size_tsize()const{returndata.size();}};intmain(){MyContainer<int>c1(1,2,3,4,5);cout<<"大小: "<<c1.size()<<endl;// 5c1.show();// 1 2 3 4 5MyContainer<string>c2("C++","Python");c2.show();// C++ Pythonreturn0;}5.3 变参模板应用场景
- std::make_shared/make_unique:参数转发给构造函数。
- std::tuple:存储任意类型任意个数。
- printf 风格格式化。
- 事件系统:任意参数的通知。
- *工厂模式
*:参数转发。
六、C++20 concepts 预告
6.1 concepts 简化约束
C++20 的 concepts 让模板约束更简洁直观:
#include<iostream>#include<concepts>usingnamespacestd;// 定义概念:必须是整数类型template<typenameT>conceptIntegral=is_integral_v<T>;// 用概念约束模板参数template<Integral T>Tadd(T a,T b){returna+b;}// 简化写法template<typenameT>requiresIntegral<T>Tmultiply(T a,T b){returna*b;}intmain(){cout<<add(3,4)<<endl;// 7cout<<multiply(5,6)<<endl;// 30// add(3.5, 4.5); // 错误!double 不满足 Integralreturn0;}6.2 concepts vs enable_if
| 维度 | enable_if | concepts |
|---|---|---|
| 可读性 | 冗长晦涩 | 直观清晰 |
| 报错信息 | 深奥难懂 | 友好明确 |
| 语法 | 模板技巧 | 标准语法 |
| 标准 | C++11 | C++20 |
七、实战:任意类型最大值
综合本篇知识,实现支持任意参数个数的 max:
#include<iostream>#include<type_traits>usingnamespacestd;// 两个参数版本(递归终止)template<typenameT>TmyMax(T value){returnvalue;}// 变参版本:比较第一个和剩余的最大值template<typenameT,typename...Args>automyMax(T first,Args...rest){autorestMax=myMax(rest...);// 递归求剩余最大值returnfirst>restMax?first:restMax;}// 折叠表达式版本(C++17 更简洁)template<typename...Args>autofoldMax(Args...args){return(args>...);// 不适用}// 正确折叠版template<typenameFirst,typename...Rest>autofoldMax2(First first,Rest...rest){autoresult=first;((result=(rest>result?rest:result)),...);returnresult;}intmain(){cout<<myMax(3,7,2)<<endl;// 7cout<<myMax(1.5,3.2,2.8,4.1)<<endl;// 4.1cout<<foldMax2(10,20,5,15)<<endl;// 20cout<<foldMax2(3.5,2.5)<<endl;// 3.5return0;}总结
本篇讲解了变参模板(参数包、sizeof…、递归展开)、C++17 折叠表达式(求和、打印、逻辑判断)、SFINAE 与 enable_if(条件启用)、void_t 技巧(特性检测)、C++20 concepts 预告,并用任意类型最大值串联实战。重点掌握:参数包递归展开模式、折叠表达式的四种形式、enable_if 的两种写法、void_t 检测原理。
下一篇将讲解类型推导与完美转发(auto/decltype/右值引用/完美转发),敬请期待!