背景

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

所用技术

  1. 静态断言: static_assert

  2. 格式串参数数量获取: GetFormatStringArgsNum,该接口声明为constexpr,从而获得编译期执行的能力。其实现大致为遍历字符串,检查其中{}的数量。

  3. 传参数量的获取: 由于使用宏进行封装,最后其实就是需要获得__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编译期检查参数数量是否一致的更多相关文章

  1. static_assert enable_if 模板编译期检查

    conceptC++ http://www.generic-programming.org/faq/?category=conceptcxx Checking Concept Without Conc ...

  2. C++17尝鲜:编译期 if 语句

    Constexpr If(编译期 if 语句) 以 if constexpr 打头的 if 语句被称为 Constexpr If. Constexpr If 是C++17所引入的新的语法特性.它为C+ ...

  3. 数值类型中JDk的编译期检查和编译期优化

    byte b1 = 5;//编译期检查,判断是否在byte范围内 byte b2 = 5+4;//编译期优化,相当于b2=9 byte b3 = 127;//编译通过,在byte范围内 byte b4 ...

  4. C++ 编译期封装-Pimpl技术

    Pimpl技术——编译期封装 Pimpl 意思为“具体实现的指针”(Pointer to Implementation), 它通过一个私有的成员指针,将指针所指向的类的内部实现数据进行隐藏, 是隐藏实 ...

  5. c++ 编译期与运行期

    分享到 一键分享 QQ空间 新浪微博 百度云收藏 人人网 腾讯微博 百度相册 开心网 腾讯朋友 百度贴吧 豆瓣网 搜狐微博 百度新首页 QQ好友 和讯微博 更多... 百度分享 转自:http://h ...

  6. Java编译期注解处理器详细使用方法

    目录 Java编译期注解处理器 启用注解处理器 遍历语法树 语法树中的源节点 语法树节点的操作 给类增加注解 给类增加import语句 构建一个内部类 使用方法 chainDots方法 总结 Java ...

  7. c++ 编译期计算 (一)

    编译期就是编译器进行编译,产生.obj文件的所处的那一段时间(如果是广义的编译期,那么一般还包括了链接期,因为现在很多编译器都会自动调用链接器进行链接)执行期就是你执行某个已经链接好的程序的那段时间. ...

  8. 读书笔记 effective c++ Item 41 理解隐式接口和编译期多态

    1. 显示接口和运行时多态 面向对象编程的世界围绕着显式接口和运行时多态.举个例子,考虑下面的类(无意义的类), class Widget { public: Widget(); virtual ~W ...

  9. Java注解(3)-注解处理器(编译期|RetentionPolicy.SOURCE)

    注解的处理除了可以在运行时通过反射机制处理外,还可以在编译期进行处理.在编译期处理注解时,会处理到不再产生新的源文件为止,之后再对所有源文件进行编译. Java5中提供了apt工具来进行编译期的注解处 ...

随机推荐

  1. Kafka 的稳定性

    一.事务 1. 事务简介 1.1 事务场景 producer发的多条消息组成⼀个事务这些消息需要对consumer同时可⻅或者同时不可⻅ producer可能会给多个topic,多个partition ...

  2. 2.2.1 用户态、内核态的形成 -《zobolの操作系统学习札记》

    内核态的出现,让计算机系统的权力向操作系统高度集中了. 操作系统分出内核态和用户态,就是为了进行不同等级的权限管理, 从而更好的适应多用户多任务并发的工作环境. 用户态和内核态的来源 在早期的单进程单 ...

  3. 记一次APP渗透登录验证绕过思路

    前言: 起初是抓包时候查看返回状态码不一致,所以觉得是否可以通过修改状态码来达到绕过的目的,但是拦截响应包再替换手速不够,技术大哥就去搜了下,找到了一个方法,可以自动替换响应包内容. 在偏下方一点的地 ...

  4. JavaScript 防盗链的原理以及破解方法

    先说说防盗链的原理,http 协议中,如果从一个网页跳到另一个网页,http 头字段里面会带个 Referer.这里的Referer是由于历史原因导致了拼写错误 后来也就一直沿用.图片服务器通过检测 ...

  5. nifi从入门到实战(保姆级教程)——身份认证

    上一篇我们搭建好了nifi的运行环境了 但是每次登陆那一串随机字符串的用户名和密码是不是让人很头疼,那是人类能记住的吗?当然不是!!!! 那么今天我们就来消灭这些难看又难记的字符串. windows( ...

  6. RPA人力资源简历筛选机器人

    简历自动筛选及分析机器人,支持前程无忧.猎聘 1.自动登录招聘网站 2.自动填充简历筛选条件 3.RPA依次读取所筛选的简历信息 4.自动将简历数据复制到本地文档中 5.完成简历信息收集及分析表 6. ...

  7. 函数式编程思想概述和冗余的Runnable代码

    函数式编程思想概述 在数学中,函数就是有输入量.输出量的一套计算方法 相对而言,面向对象过分强调必须通过对象的形式来做事情,而函数式的思想是尽量忽略复杂的面向对象的复杂语法--是强调做什么而不是以什么 ...

  8. HMS Core安全检测服务如何帮助大学新生防范电信诈骗?

    一年一度的高考结束了,很多学生即将离开父母,一个人踏入大学生活,但由于人生阅历较少,容易被不法分子盯上. 每年开学季也是大一新生遭受诈骗的高峰期,以下是一些常见的案例.有的骗子会让新生下载注册一些恶意 ...

  9. Restarting network (via systemctl): Job for network.service failed because the control process exited with error code. See "systemctl status network.service" and "journalctl -xe" for details.

    编辑完 ip地址,要重启网络 sudo service network restart 结果返回错误,错误如下 Restarting network (via systemctl): Job for ...

  10. HackerRank第一趴--Basic Select

    CITY表: Field Type ID number NAME VARCHAR2(17) COUNTRYCODE VARCHAR2(3) DISTRICT VARCHAR2(20) POPULATI ...