C++23:如何实现带编译时格式符与参数数校验的sprintf类函数
C++23无宏实现格式化字符串编译时参数校验
需求
我正在学习C++23,计划编写一个类似sprintf()的函数,核心要求如下:
- 当传入字符串字面量作为格式串时,在编译时检查
%占位符的数量与传入参数数量是否匹配 - 校验通过后生成并返回格式化字符串,暂不考虑具体格式化逻辑
现有实现的问题
最初的尝试代码无法实现编译时校验,原因是Sprintf()中调用的std::to_string()并非常量表达式(使用GCC trunk,开启--std=c++2b编译选项)。如果移除std::to_string()并将两个函数标记为consteval,虽然能实现编译时校验,但会严重限制函数功能——比如无法使用std::to_string()、无法创建/操作并返回std::string。
我知道可以通过可变参数宏实现:先调用consteval的count_percents()完成参数数量校验,再调用非consteval的辅助函数执行格式化,但认为C++23应该有更优雅的无宏方案。
最初的问题代码:
#include <iostream> template <int N> constexpr size_t count_percents(const char (&s)[N]) { size_t count = 0; for (size_t i = 0; i < N; i++) if (s[i] == '%') ++count; return count; } template <int N, typename... Args> constexpr std::string Sprintf(const char (&format)[N], Args&&... args) { if (count_percents(format) != sizeof...(args)) throw std::invalid_argument("wrong number of placeholders in format string"); return (std::to_string(args) + ...); } int main() { std::cout << Sprintf("%%", 1, 2); // 输出"12",符合预期 // 期望编译失败,但实际会通过编译并在运行时抛出异常 std::cout << Sprintf("%", 1, 2); }
曾找到相关参考方案,但仅适用于校验逻辑仅依赖格式串的场景,无法满足我基于非consteval函数参数数量触发编译错误的需求。
自解方案
我自己实现了一个可完成编译时参数数量校验的方案,代码如下:
#include <cstddef> #include <iostream> consteval size_t count_percents(const char* s) { size_t i = 0; while (*s) if (*s++ == '%') ++i; return i; } template <size_t N> struct format_string { const char* str; consteval format_string(const char* s) : str(s) { if (N != count_percents(str)) // 若此代码路径被实例化,consteval函数中的throw会触发编译错误 throw std::invalid_argument("wrong number of placeholders"); } }; template <typename... T> void my_print(format_string<sizeof...(T)> format, T&&... args) { // 执行到此处说明占位符数量校验通过 } int main() { my_print("foo%%%", 1, 2, 3); // 正常编译 // my_print("foo%", 1, 2, 3); // 触发编译错误! }
不过存在更优的方案,该方案不仅能校验参数数量,还支持检查实际参数类型。
内容的提问来源于stack exchange,提问作者Crutch
相关产品推荐
相关产品推荐

