通过宏封装实现std::format编译期检查参数数量是否一致
背景
std::format在传参数量少于格式串所需参数数量时,会抛出异常。而在大部分的应用场景下,参数数量不一致提供编译报错更加合适,可以促进我们更早发现问题并进行改正。
最终效果
// 测试输出接口。
template <typename... T>
void Print(const std::string& _Fmt, const T&... _Args)
{
cout << std::vformat(_Fmt, std::make_format_args(_Args...)) << endl;
}
// 封装宏,实现参数数量一致的检查
#define PRINT(fmt, ...) \
do { static_assert(GetFormatStringArgsNum(fmt) == decltype(VariableArgsNumHelper(__VA_ARGS__))::value, "Invalid format string or mismatched number of arguments"); Print(fmt, __VA_ARGS__); } while(0)
int main()
{
PRINT("{}", "hello");
PRINT("{} {}", "hello");
return 0;
}
上例代码中,使用PRINT宏封装了Print函数,后续使用PRINT进行控制台输出,如果出现参数数量不一致,将产生编译报错:Invalid format string or mismatched number of arguments。
所用技术
静态断言:
static_assert格式串参数数量获取:
GetFormatStringArgsNum,该接口声明为constexpr,从而获得编译期执行的能力。其实现大致为遍历字符串,检查其中{}的数量。传参数量的获取: 由于使用宏进行封装,最后其实就是需要获得
__VA_ARGS__中附带了几个参数,网上可以搜到各种解决方案,这里采用的是声明一个模板函数,模板函数返回integral_constant结构体,其对不同的参数数量,自动生成不同的结构体类型,之后使用decltype(VariableArgsNumHelper(__VA_ARGS__))获得返回值类型,并从返回值类型中获得代表参数数量的常量值,由于运行期用不到该函数,因此只提供声明,不提供实现。
整体代码
#include <iostream>
#include <string>
#include <format>
using namespace std;
constexpr int GetFormatStringArgsNum(const std::string& fmt)
{
enum STATE
{
NORMAL, // 正在解析普通串
REPLACEMENT, // 正在解析大括号中的内容
};
// 按标准规定,格式串中要么都指定参数编号,要么都不指定
// 原文:
// The arg-ids in a format string must all be present or all be omitted.
// Mixing manual and automatic indexing is an error.
enum RULE
{
UNKNOWN, // 格式串规则
SPECIFIEDID, // 指定编号,如{0}
UNSPECIFIEDID, // 不指定编号,如{}
};
// 指定参数编号的最大值
const int MAX_ARGS_NUM = 10000;
// 初始状态
STATE state = NORMAL;
// 初始规则
RULE rule = UNKNOWN;
// 当前参数编号
int nIndex = -1;
// 参数数量
int nArgsNum = 0;
for (int i = 0; i < fmt.size(); ++i)
{
switch (state)
{
case NORMAL:
{
// 普通串解析时,遇到左大括号或右大括号,才有可能改变状态
if (fmt[i] == '{')
{
if (i + 1 < fmt.size() && fmt[i + 1] == '{')
{
// 遇到 {{,则将他们视为普通字符
++i;
}
else
{
// 进入替换串状态
state = REPLACEMENT;
}
}
else if (fmt[i] == '}')
{
++i;
if (i >= fmt.size() || fmt[i] != '}')
{
// 普通串解析状态,遇上右大括号时,只有当接下来也是右大括号时,才属于合法串
return -1;
}
}
}
break;
case REPLACEMENT:
{
// 替换串状态下,正常只会遇到右大括号、数字、冒号,其他符号均为错误
if (fmt[i] == '}')
{
// 遇到右大括号,则进入普通串解析状态,这里不考虑}},正常{} 中间不应该出现}
state = NORMAL;
// 如果之前某个{} 已经指定参数编号,则所有参数都应该指定编号
if (rule == SPECIFIEDID)
{
// 如果这个{} 不指定编号,则视为非法格式串
if (nIndex == -1)
{
return -1;
}
// 在指定编号的情况下,可变参数的数量至少要比编号大1
nArgsNum = std::max(nArgsNum, nIndex + 1);
// 重置当前编号
nIndex = -1;
}
else
{
// 如果当前规则未明或者当前规则为不指定编号,则参数数量进行自增。
state = NORMAL;
rule = UNSPECIFIEDID;
++nArgsNum;
}
}
else if (fmt[i] >= '0' && fmt[i] <= '9')
{
// 遇到数字,说明指定了参数编号
if (rule == UNSPECIFIEDID)
{
// 如果当前规则已明确为不指定编号,则视为非法格式串
return -1;
}
else
{
// 否则,将当前规则改为指定编号,并维护当前编号
rule = SPECIFIEDID;
if (nIndex == -1)
{
nIndex = 0;
}
nIndex = nIndex * 10 + (fmt[i] - '0');
if (nIndex >= MAX_ARGS_NUM)
{
// 当前编号大于最大上限,则直接视为非法格式串
return -1;
}
}
}
else if (fmt[i] == ':')
{
// 遇到冒号,说明接下来是格式串规则,直接跳过
for (; i + 1 < fmt.size() && fmt[i + 1] != '}'; ++i)
{
;
}
}
else
{
// 解析替换串时,遇上其他字符,均将格式串视为非法。
return -1;
}
}
break;
}
}
// 最终状态必须为普通串解析状态。
return state == NORMAL ? nArgsNum : -1;
}
// 可变参数数量辅助器
template <typename ... Args>
std::integral_constant<std::size_t, sizeof...(Args)> VariableArgsNumHelper(const Args & ...);
// 测试输出接口。
template <typename... T>
void Print(const std::string& _Fmt, const T&... _Args)
{
cout << std::vformat(_Fmt, std::make_format_args(_Args...)) << endl;
}
// 封装宏,实现参数数量一致的检查
#define PRINT(fmt, ...) \
do { static_assert(GetFormatStringArgsNum(fmt) == decltype(VariableArgsNumHelper(__VA_ARGS__))::value, "Invalid format string or mismatched number of arguments"); Print(fmt, __VA_ARGS__); } while(0)
int main()
{
PRINT("{} {}", "hello");
return 0;
}
通过宏封装实现std::format编译期检查参数数量是否一致的更多相关文章
- static_assert enable_if 模板编译期检查
conceptC++ http://www.generic-programming.org/faq/?category=conceptcxx Checking Concept Without Conc ...
- C++17尝鲜:编译期 if 语句
Constexpr If(编译期 if 语句) 以 if constexpr 打头的 if 语句被称为 Constexpr If. Constexpr If 是C++17所引入的新的语法特性.它为C+ ...
- 数值类型中JDk的编译期检查和编译期优化
byte b1 = 5;//编译期检查,判断是否在byte范围内 byte b2 = 5+4;//编译期优化,相当于b2=9 byte b3 = 127;//编译通过,在byte范围内 byte b4 ...
- C++ 编译期封装-Pimpl技术
Pimpl技术——编译期封装 Pimpl 意思为“具体实现的指针”(Pointer to Implementation), 它通过一个私有的成员指针,将指针所指向的类的内部实现数据进行隐藏, 是隐藏实 ...
- c++ 编译期与运行期
分享到 一键分享 QQ空间 新浪微博 百度云收藏 人人网 腾讯微博 百度相册 开心网 腾讯朋友 百度贴吧 豆瓣网 搜狐微博 百度新首页 QQ好友 和讯微博 更多... 百度分享 转自:http://h ...
- Java编译期注解处理器详细使用方法
目录 Java编译期注解处理器 启用注解处理器 遍历语法树 语法树中的源节点 语法树节点的操作 给类增加注解 给类增加import语句 构建一个内部类 使用方法 chainDots方法 总结 Java ...
- c++ 编译期计算 (一)
编译期就是编译器进行编译,产生.obj文件的所处的那一段时间(如果是广义的编译期,那么一般还包括了链接期,因为现在很多编译器都会自动调用链接器进行链接)执行期就是你执行某个已经链接好的程序的那段时间. ...
- 读书笔记 effective c++ Item 41 理解隐式接口和编译期多态
1. 显示接口和运行时多态 面向对象编程的世界围绕着显式接口和运行时多态.举个例子,考虑下面的类(无意义的类), class Widget { public: Widget(); virtual ~W ...
- Java注解(3)-注解处理器(编译期|RetentionPolicy.SOURCE)
注解的处理除了可以在运行时通过反射机制处理外,还可以在编译期进行处理.在编译期处理注解时,会处理到不再产生新的源文件为止,之后再对所有源文件进行编译. Java5中提供了apt工具来进行编译期的注解处 ...
随机推荐
- C语言学习之我见-strcmp()字符串比较函数
strcmp()函数,用于两个字符串的比较. (1)函数原型 int strcmp(const char *_Str1,const char *_Str2); (2)头文件 string.h (3)功 ...
- 使用阿里云RDS for SQL Server性能洞察优化数据库负载-初识性能洞察
简介 数据库性能调优通常需要较高数据库水平,并伴随较多的前期准备工作,比如收集各种性能基线.不同种类的性能指标.慢SQL日志等,这通常费时费力且效果一般,当面对多个数据库时总体拥有成本会大幅增加.今天 ...
- Prometheus安装教程
Prometheus安装教程 欢迎关注H寻梦人公众号 参考目录 docker安装Prometheus 基于docker 搭建Prometheus+Grafana prometheus官方文档 dock ...
- cve-2021-42287和cve-2021-42278漏洞复现
一.漏洞概述 cve-2021-42287 : 由于Active Directory没有对域中计算机与服务器账号进行验证,经过身份验证的攻击 者利用该漏洞绕过完全限制,可将域中普通用户权限提升为域管理 ...
- Tensorflow2 深度学习十必知
博主根据自身多年的深度学习算法研发经验,整理分享以下十条必知. 含参考资料链接,部分附上相关代码实现. 独乐乐不如众乐乐,希望对各位看客有所帮助. 待回头有时间再展开细节说一说深度学习里的那些道道. ...
- 图文带你看懂JavaScritpt引擎V8与JS执行过程
浏览器原理 浏览器内核与js引擎 浏览器内核又称"排版引擎","渲染引擎","浏览器引擎",叫法很多,简单来说干的活就是将代码(HTML,X ...
- 普通 Docker 与 Kubernetes 对比
Docker提供基本容器管理 API 和容器镜像文件格式Kubernetes 管理运行容器的(物理或虚拟)主机群集,如果 Docker 是 OCP 的"内核",Kubernetes ...
- 如何将 DevSecOps 引入企业?
前 言 现如今,大部分企业已经在内部实现了 DevOps 实践.DevOps 为团队提供了交付可靠软件和快速更新的方法论.这种方法让团队更专注于质量而不是将时间浪费在运维上.然而,结果是,安全实践往往 ...
- android stdio开发抖音自动点赞案例
最近做了一个安卓开发自动刷抖音. 点赞. 评论等等养号行为. 总结一下知识点和遇到的一些问题: 知识点: 1. 使用acessibility mode 对抖音自动化操作. android stdio中 ...
- UI自动化测试执行问题总结
------------恢复内容开始------------ ![image](https://img2022.cnblogs.com/blog/1510476/202206/1510476-2022 ...