通过宏封装实现std::format编译期检查参数数量是否一致

博客 动态
0 145
羽尘
羽尘 2022-08-29 16:03:44
悬赏:0 积分 收藏

通过宏封装实现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

所用技术

  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;}
posted @ 2022-08-29 15:58 hchlqlz 阅读(0) 评论(0) 编辑 收藏 举报
回帖
    羽尘

    羽尘 (王者 段位)

    2335 积分 (2)粉丝 (11)源码

     

    温馨提示

    亦奇源码

    最新会员