fmt 运行时格式字符串如何用 fmt::runtime 包装并通过类型安全检查
2026/9/13 17:43:49 网站建设 项目流程

fmt 运行时格式字符串如何用 fmt::runtime 包装并通过类型安全检查

【免费下载链接】fmtA modern formatting library项目地址: https://gitcode.com/GitHub_Trending/fm/fmt

在 {fmt} 中,格式字符串默认参与类型安全检查:字符串字面量在 C++20(编译器支持consteval)下于编译期检查,格式说明符与参数类型不匹配时直接编译失败。但格式字符串有时无法在编译期确定——比如运行时拼接出来的 chrono 说明符、配置里读出的模板串——这类值不能直接传给fmt::format等接受format_string<T...>的函数。{fmt} 为此提供了fmt::runtime:把运行时字符串包装成runtime_format_string,让同一组类型安全 API 接受它,检查从编译期推迟到运行期,不匹配时抛出fmt::format_error。本文基于 doc/api.md 与头文件中的定义,给出包装方式和验证手段。

为什么运行时字符串需要包装

doc/api.md 对格式参数的定义是:fmt::format_string是从字符串字面量或constexpr字符串隐式构造、并在 C++20 下做编译期检查的格式字符串;而 “To pass a runtime format string wrap it infmt::runtime”。

fmt::runtime的定义在 include/fmt/core.h:

template <typename Char = char> struct runtime_format_string { basic_string_view<Char> str; }; /** * Creates a runtime format string. * * **Example**: * * // Check format string at runtime instead of compile-time. * fmt::print(fmt::runtime("{:d}"), "I am not a number"); */ inline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; }

包装之所以可行,是因为fstringformat_string<T...>的底层类型)专门提供了从runtime_format_string<>构造的入口,见 include/fmt/core.h 的fstring(runtime_format_string<> fmt) : str(fmt.str) {}。也就是说fmt::runtime(s)是运行时字符串进入这套 API 的唯一通道,不包装的普通string_view/std::string无法通过format_string参数的类型推导。

使用 fmt::runtime 的完整示例

下面的程序演示两条路径:运行时拼接出的格式字符串被正常格式化;类型不匹配的运行时字符串在运行期抛出fmt::format_error并被捕获。"{:d}"fmt::print的组合直接取自 core.h 中的文档示例:

#include <fmt/core.h> #include <string> int main() { // 1. 运行时构造的格式字符串:拼好之后用 fmt::runtime 包装再传入 std::string spec = "{:"; // 等价于测试代码中的拼接方式 for (int i = 0; i < 30; ++i) spec += "%c"; spec += "}\n"; fmt::print(fmt::runtime(spec), *std::localtime(nullptr)); // 2. 文档示例:格式说明符与参数类型不匹配 // 检查发生在运行时,抛出 fmt::format_error try { fmt::print(fmt::runtime("{:d}"), "I am not a number"); } catch (const fmt::format_error& e) { std::printf("caught: %s\n", e.what()); } }

编译前提与文档一致:在支持 C++20consteval的编译器下编译时,字符串字面量走编译期检查,fmt::runtime包装的值走运行期检查。第二段在文档语义下预期被catch捕获;文档只标注了这是 “Check format string at runtime instead of compile-time” 的例子,没有给出固定输出,所以不要按某个确定文案判断成败,判断依据是“异常被捕获、程序未崩溃”。

运行期检查会报哪些错

fmt::format_error继承自std::runtime_error(见 include/fmt/format.h),core.h 中report_error的注释明确了两种时机:编译期报错,或通过format_error异常在运行期报错。

仓库测试套件对运行期错误消息有具体断言,可作为核对依据(以下消息来自 test/format-test.cc 的runtime_precision测试):

  • 括号不闭合或说明符非法:"invalid format string"
  • 动态宽度/精度引用了不存在的参数:"argument not found"
  • 负数或超大宽度/精度:"width/precision is out of range"
  • 说明符与参数类型不匹配,例如 test/format-test.cc 中断言fmt::format(fmt::runtime("{:x}"), std::string("test"))抛出fmt::format_error

这些测试同时是验证方式:如果你的调用链与上述用例同形(fmt::runtime包装 + 不匹配参数),应观察到fmt::format_error异常而不是未定义行为或静默输出。

非 C++20 编译器的替代检查方式

当编译器不支持 C++20consteval时,doc/api.md 的 “Compile-Time Checks” 与 “Legacy Compile-Time Checks” 给出替代路径:

  • FMT_STRING宏:在旧编译器上启用编译期检查,要求 C++14 或更高,在 C++11 下是无操作(no-op)。
  • 预定义FMT_ENFORCE_COMPILE_STRING可强制使用旧式检查:此时接受FMT_STRING的函数遇到普通字符串会编译失败,避免绕过检查。

这两者与fmt::runtime的关系是:FMT_STRING/字面量负责“能编译期查就编译期查”,fmt::runtime负责“确实只能运行期查”的字符串。

宽字符场景下fmt/xchar.h提供了对应重载inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t>(见 include/fmt/xchar.h),用法与char版本一致。

为自定义函数保留类型安全检查

如果自己在写接受格式字符串的函数(如日志),不要直接声明void log(std::string_view fmt, ...)了事——那会丢掉全部检查。doc/api.md 的 “Type Erasure” 一节给出的标准模式是双层结构:非模板底层接收string_view+format_args,模板顶层接收fmt::format_string<T...>,检查发生在顶层模板实例化时:

#include <fmt/format.h> void vlog(const char* file, int line, fmt::string_view fmt, fmt::format_args args) { fmt::print("{}: {}: {}", file, line, fmt::vformat(fmt, args)); } template <typename... T> void log(const char* file, int line, fmt::format_string<T...> fmt, T&&... args) { vlog(file, line, fmt, fmt::make_format_args(args...)); } #define MY_LOG(fmt, ...) log(__FILE__, __LINE__, fmt, __VA_ARGS__) MY_LOG("invalid squishiness: {}", 42);

文档指出vlog不按参数类型模板化,相比完全模板化的版本能改善编译时间并减小二进制体积。若格式字符串在调用方是运行期值(如配置读入),调用侧再按前文方式用fmt::runtime包装。

验证清单与限制

按以下顺序自证整条路径可用:

  1. 编译期:把"{:d}"字面量直接配一个非整型参数传入fmt::print(不加fmt::runtime),在 C++20 下应得到编译错误——fstring构造注释写明 “Reports a compile-time error if S is not a valid format string for T”。
  2. 运行期:对fmt::runtime("{:d}")配非整型参数,应抛出fmt::format_error(可用catch (const fmt::format_error&)捕获验证)。
  3. 可用路径:运行时拼接的合法字符串(如上文{:%ccc...}式 chrono 说明符)包装后正常输出,参考 test/chrono-test.cc 的grow_buffer用例构造方式。

需要记住的边界:

  • fmt::runtime只是把检查推迟到运行期,运行期不匹配仍是异常,不是降级为无检查;
  • 编译期检查默认在支持 C++20consteval的编译器上开启,旧编译器依赖FMT_STRING,C++11 下该宏无效;
  • 更多格式语法细节见 doc/syntax.md,接入与构建方式(CMake 目标、包管理器)见 doc/get-started.md。

【免费下载链接】fmtA modern formatting library项目地址: https://gitcode.com/GitHub_Trending/fm/fmt

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询