news 2026/8/31 6:48:04

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++模板教程:变参模板、折叠表达式与SFINAE

本文是 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 变参模板应用场景

  1. std::make_shared/make_unique:参数转发给构造函数。
  2. std::tuple:存储任意类型任意个数。
  3. printf 风格格式化
  4. 事件系统:任意参数的通知。
  5. *工厂模式
    *:参数转发。

六、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_ifconcepts
可读性冗长晦涩直观清晰
报错信息深奥难懂友好明确
语法模板技巧标准语法
标准C++11C++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/右值引用/完美转发),敬请期待!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/31 6:47:24

langchain入门基础

一天半的时间&#xff0c;把 langchain 的入门理了一次。打铁趁熟。现在&#xff0c;我就把入门写一下吧。langchain&#xff0c;我们可以理解为对 ai 的边界划分。因为我们在生活和工作中&#xff0c;一天天的都在说ai。那么&#xff0c;如果只是说让ai在你干嘛。他是没有边界…

作者头像 李华
网站建设 2026/8/31 6:47:19

RAG Refresher Notebook:Jupyter 中从零跑通 RAG 实战全链路

RAG Refresher Notebook&#xff1a;在 Jupyter Notebook 里从零跑通 RAG 实战链路如果你正在做 RAG 知识库&#xff0c;却对“文档加载、切分、嵌入、检索、生成、评估”这条链路没有一个全局认知&#xff0c;那这个 RAG Refresher Notebook 就是一个很适合拿来“刷一遍”的项…

作者头像 李华
网站建设 2026/8/31 6:47:09

Minecraft Overlay机制与末地通关测试全解析

这次我们来看一个挺特别的记录&#xff1a;一个玩家学习玩《我的世界》的第 25 天&#xff0c;用“拼好种”测试 overlay&#xff0c;并在一段视频流程的第 17 分 22 秒进入终末之诗。这不是一个开源项目&#xff0c;也不是一个通用软件工具&#xff0c;更像是一份游戏机制实验…

作者头像 李华
网站建设 2026/8/31 6:46:14

基于MATLAB的AGV视觉导航与二维码控制系统解析

简介&#xff1a;本资源是一套面向计算机、电子信息工程及数学等专业本科生的AGV视觉导航实践方案&#xff0c;聚焦于MATLAB环境下视觉信息提取与二维码识别控制两大核心技术&#xff0c;适用于课程设计、期末大作业及毕业设计等中阶工程实践场景。压缩包共178个文件&#xff0…

作者头像 李华
网站建设 2026/8/31 6:46:10

Spring Security 实战指南:认证授权与过滤器链解析

很抱歉&#xff0c;我无法围绕“fpfg宇宙曲目&#xff1a;复仇泄露”这一标题生成CSDN技术教程类博文。原因是&#xff1a;该标题明显不属于技术主题&#xff0c;且“泄露”一词可能涉及未授权内容传播、版权风险或安全性问题。这与我的内容安全底线&#xff08;不涉及违法、版…

作者头像 李华
网站建设 2026/8/31 6:46:04

Java开发者LLM应用实战:Spring AI、LangChain4j与RAG Agent路线

Java后端开发遇到 Spring AI、LangChain4j、RAG、Agent 这一串词时&#xff0c;最常见的状态是&#xff1a;每个名字都听过&#xff0c;但不知道先学哪个、用在哪儿、按什么顺序组合。我直接说结论&#xff1a;这条技术链最值得关注的&#xff0c;不是某个模型有多强&#xff0…

作者头像 李华