ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C++模板教程:变参模板、折叠表达式与SFINAE

C++模板教程:变参模板、折叠表达式与SFINAE 本文是 C 系列教程的第 18 篇。上一篇讲解了特化与类型萃取本篇深入模板高级技巧变参模板参数包、sizeof…、递归展开、C17 折叠表达式、SFINAE 与 enable_if、void_t 技巧、C20 concepts 预告。一、变参模板1.1 什么是变参模板变参模板Variadic Templates允许模板接受任意数量的参数C11 引入// 参数包typename... Argstemplatetypename...Argsvoidprint(Args...args);Args...是类型参数包。args...是函数参数包。sizeof...(Args)获取参数个数。1.2 参数包展开与递归#includeiostreamusingnamespacestd;// 递归终止条件空参数版本voidprintAll(){coutendl;}// 递归展开取出第一个参数剩余继续递归templatetypenameT,typename...RestvoidprintAll(T first,Rest...rest){coutfirst ;printAll(rest...);// 递归调用剩余参数}intmain(){printAll(1,2.5,hello,a);// 1 2.5 hello aprintAll(10);// 10return0;}1.3 sizeof… 获取参数个数#includeiostreamusingnamespacestd;templatetypename...ArgsvoidcountArgs(Args...args){cout参数个数: sizeof...(Args)endl;cout参数个数: sizeof...(args)endl;// 等价}intmain(){countArgs();// 0countArgs(1);// 1countArgs(1,2.5,three);// 3return0;}二、折叠表达式C172.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 折叠求和#includeiostreamusingnamespacestd;// C17 折叠一行实现任意个数求和templatetypename...Argsautosum(Args...args){return(args...0);// 右折叠初始值 0}// 左折叠templatetypename...ArgsautosumLeft(Args...args){return(0...args);}intmain(){coutsum(1,2,3,4,5)end l;// 15coutsum(1.5,2.5,3.0)endl;// 7coutsum()endl;// 0coutsumLeft(1,2,3)endl;// 6return0;}2.3 折叠打印对比递归#includeiostreamusingnamespacestd;// 用逗号运算符折叠打印templatetypename...ArgsvoidprintFold(Args...args){// (cout ... args)左折叠// 逐个输出((coutargs ),...);// 逗号折叠coutendl;}// 更实用的版本带分隔符templatetypename...ArgsvoidprintWithSep(constchar*sep,Args...args){// 第一个直接输出其余带分隔符((coutargs),...);// 简化版coutendl;}intmain(){printFold(1,2.5,hi,x);// 1 2.5 hi xprintWithSep(, ,1,2,3);return0;}2.4 折叠判断逻辑运算#includeiostreamusingnamespacestd;// 全部满足条件 折叠templatetypename...ArgsboolallPositive(Args...args){return((args0)...);}// 任一满足条件|| 折叠templatetypename...ArgsboolanyZero(Args...args){return((args0)||...);}intmain(){coutallPositive(1,2,3)endl;// 1coutallPositive(1,-2,3)endl;// 0coutanyZero(1,2,0,4)endl;// 1coutanyZero(1,2,3)endl;// 0return0;}三、SFINAE 基础3.1 什么是 SFINAESFINAESubstitution Failure Is Not An Error替换失败不是错误模板实例化时如果某个候选替换失败如类型不支持某操作编译器不会报错而是继续尝试其他候选。3.2 利用 SFINAE 做类型判断#includeiostream#includetype_traitsusingnamespacestd;// 通用版本不支持 operator 的类型走这里templatetypenameTvoidprintValue(constTvalue,...){cout不支持输出: (未知类型)endl;}// 精确版本支持 operator 的类型走这里templatetypenameT,typenamedecltype(coutdeclvalconstT())voidprintValue(constTvalue,int){cout值: valueendl;}intmain(){printValue(42,0);// 值: 42printValue(3.14,0);// 值: 3.14printValue(hello,0);// 值: helloreturn0;}3.3 enable_if 条件启用#includeiostream#includetype_traitsusingnamespacestd;// 仅当 T 是整数时启用templatetypenameTtypenameenable_ifis_integralT::value::typeprocess(T value){cout整数处理: valueendl;}// 仅当 T 是浮点时启用templatetypenameTtypenameenable_ifis_floating_pointT::value::typeprocess(T value){cout浮点处理: valueendl;}intmain(){process(42);// 整数处理: 42process(3.14);// 浮点处理: 3.14// process(hi); // 错误string 不满足任何版本return0;}3.4 enable_if 的两种写法#includeiostream#includetype_traitsusingnamespacestd;// 写法一返回类型中启用templatetypenameTtypenameenable_ifis_integralT::value,T::typesquare(T x){returnx*x;}// 写法二模板参数中启用C11 更通用templatetypenameT,typenametypenameenable_ifis_floating_pointT::value::typedoublesquareDouble(T x){returnx*x;}intmain(){coutsquare(5)endl;// 25int 版本coutsquareDouble(2.5)endl;// 6.25double 版本return0;}四、void_t 技巧4.1 void_t 检测特性#includeiostream#includetype_traitsusingnamespacestd;// void_t任何类型都映射为 voidC17 标准库提供templatetypename...usingvoid_tvoid;// 检测 T 是否有成员函数 size()templatetypenameT,typenamevoidstructHasSize:false_type{};templatetypenameTstructHasSizeT,void_tdecltype(declvalT().size()):true_type{};// 检测 T 是否有成员 typetemplatetypenameT,typenamevoidstructHasType:false_type{};templatetypenameTstructHasTypeT,void_ttypenameT::type:true_type{};intmain(){coutstring 有 size(): HasSizestring::valueendl;// 1coutint 有 size(): HasSizeint::valueendl;// 0structWithType{usingtypeint;};coutWithType 有 type: HasTypeWithType::valueendl;// 1coutint 有 type: HasTypeint::valueendl;// 0return0;}4.2 void_t 检测可调用性#includeiostream#includetype_traitsusingnamespacestd;templatetypename...usingvoid_tvoid;// 检测 T 是否支持 operatortemplatetypenameT,typenamevoidstructIsPrintable:false_type{};templatetypenameTstructIsPrintableT,void_tdecltype(coutdeclvalconstT()):true_type{};structMyStruct{};// 不支持输出intmain(){coutint 可打印: IsPrintableint::valueendl;// 1coutstring 可打印: IsPrintablestring::valueendl;// 1coutMyStruct 可打印: IsPrintableMyStruct::valueendl;// 0return0;}五、变参模板实战5.1 类型安全的 printf#includeiostreamusingnamespacestd;// 基础版本无参数voidmyPrintf(constchar*format){coutformatendl;}// 递归展开版本templatetypenameT,typename...ArgsvoidmyPrintf(constchar*format,T value,Args...args){while(*format){if(*format%*(format1)d){coutvalue;// 输出参数format2;myPrintf(format,args...);// 递归剩余return;}cout*format;}}intmain(){myPrintf(数字: %d,42);myPrintf(%d 加 %d 等于 %d,1,2,3);return0;}5.2 变参构造函数完美转发预览#includeiostream#includevectorusingnamespacestd;// 自定义容器支持任意参数构造templatetypenameTclassMyContainer{private:vectorTdata;public:// 变参构造函数templatetypename...ArgsMyContainer(Args...args){data.reserve(sizeof...(Args));(data.push_back(args),...);// C17 逗号折叠}voidshow()const{for(constautoitem:data)coutitem ;coutendl;}size_tsize()const{returndata.size();}};intmain(){MyContainerintc1(1,2,3,4,5);cout大小: c1.size()endl;// 5c1.show();// 1 2 3 4 5MyContainerstringc2(C,Python);c2.show();// C Pythonreturn0;}5.3 变参模板应用场景std::make_shared/make_unique参数转发给构造函数。std::tuple存储任意类型任意个数。printf 风格格式化。事件系统任意参数的通知。*工厂模式*参数转发。六、C20 concepts 预告6.1 concepts 简化约束C20 的 concepts 让模板约束更简洁直观#includeiostream#includeconceptsusingnamespacestd;// 定义概念必须是整数类型templatetypenameTconceptIntegralis_integral_vT;// 用概念约束模板参数templateIntegral TTadd(T a,T b){returnab;}// 简化写法templatetypenameTrequiresIntegralTTmultiply(T a,T b){returna*b;}intmain(){coutadd(3,4)endl;// 7coutmultiply(5,6)endl;// 30// add(3.5, 4.5); // 错误double 不满足 Integralreturn0;}6.2 concepts vs enable_if维度enable_ifconcepts可读性冗长晦涩直观清晰报错信息深奥难懂友好明确语法模板技巧标准语法标准C11C20七、实战任意类型最大值综合本篇知识实现支持任意参数个数的 max#includeiostream#includetype_traitsusingnamespacestd;// 两个参数版本递归终止templatetypenameTTmyMax(T value){returnvalue;}// 变参版本比较第一个和剩余的最大值templatetypenameT,typename...ArgsautomyMax(T first,Args...rest){autorestMaxmyMax(rest...);// 递归求剩余最大值returnfirstrestMax?first:restMax;}// 折叠表达式版本C17 更简洁templatetypename...ArgsautofoldMax(Args...args){return(args...);// 不适用}// 正确折叠版templatetypenameFirst,typename...RestautofoldMax2(First first,Rest...rest){autoresultfirst;((result(restresult?rest:result)),...);returnresult;}intmain(){coutmyMax(3,7,2)endl;// 7coutmyMax(1.5,3.2,2.8,4.1)endl;// 4.1coutfoldMax2(10,20,5,15)endl;// 20coutfoldMax2(3.5,2.5)endl;// 3.5return0;}总结本篇讲解了变参模板参数包、sizeof…、递归展开、C17 折叠表达式求和、打印、逻辑判断、SFINAE 与 enable_if条件启用、void_t 技巧特性检测、C20 concepts 预告并用任意类型最大值串联实战。重点掌握参数包递归展开模式、折叠表达式的四种形式、enable_if 的两种写法、void_t 检测原理。下一篇将讲解类型推导与完美转发auto/decltype/右值引用/完美转发敬请期待
返回列表