背景

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. HMS Core新闻行业解决方案:让技术加上人文的温度

    开发者们,你希望用户如何获取新闻? 有的人靠手机弹窗知天下事,有的人则在新闻应用中尽览每一篇文章:有的人一目十行,有的人则喜欢细细咀嚼:有的人主动探索,有的人则想要应用投其所好. 科技在不断刷新着用户 ...

  2. CMU15445 之 Project#0 - C++ Primer 详解

    前言 这个实验主要用来测试大家对现代 C++ 的掌握程度,实验要求如下: 简单翻译一下上述要求,就是我们需要实现定义在 src/include/primer/p0_starter.h 中的三个类 Ma ...

  3. 面试突击61:说一下MySQL事务隔离级别?

    MySQL 事务隔离级别是为了解决并发事务互相干扰的问题的,MySQL 事务隔离级别总共有以下 4 种: READ UNCOMMITTED:读未提交. READ COMMITTED:读已提交. REP ...

  4. VS Code 调教日记(2022.6.26更新)

    VS Code 调教日记(2022.6.26更新) 基于msys2的MinGW-w64 GCC的环境配置 下载并安装msys2 到路径...msys2安装路径...\msys64\etc\pacman ...

  5. 一题多解,ASP.NET Core应用启动初始化的N种方案[上篇]

    ASP.NET Core应用本质上就是一个由中间件构成的管道,承载系统将应用承载于一个托管进程中运行起来,其核心任务就是将这个管道构建起来.在ASP.NET Core的发展历史上先后出现了三种应用承载 ...

  6. 《A Neural Algorithm of Artistic Style》理解

    在美术中,特别是绘画,人类掌握了通过在图像的内容和风格间建立复杂的相互作用从而创造独特的视觉体验的技巧.到目前为止,这个过程的算法基础是未知的,也没有现存的人工系统拥有这样的能力.然而在视觉感知的其他 ...

  7. 关于 用fscanf读文件,把文件中用##分割的内容分开

    今天呀,被学弟问了一个问题 文件里存的是"123##456##0##1644444.....##" 为什么用fscanf(fp, "%s##%s......", ...

  8. 最强人工智能 OpenAI 极简教程

    大家好哇,新同学都叫我张北海,老同学都叫我老胡,其实是一个人,只是我特别喜欢章北海这个<三体>中的人物,张是错别字. 上个月安利了一波:机器学习自动补全代(hán)码(shù)神器,然后就 ...

  9. python requests 使用代理池访问https站点返回乱码

    问题表现: 检查一下正常的请求头里面accept-encoding字段是否包含br,如果包含,果断pip install urllib3[brotli],详见ssl-warnings 记录另外一个问题 ...

  10. idea 内置tomcat jersey 上传文件报403错误

    Request processing failed; nested exception is com.sun.jersey.api.client.UniformInterfaceException: ...