
如何用 fmt 的 ostream_formatter 让定义了 operator 的类型支持格式化【免费下载链接】fmtA modern formatting library项目地址: https://gitcode.com/GitHub_Trending/fm/fmt你的项目里已经用上了 {fmt}fmt 库某个自定义类型也早就为std::cout写过operator但把它传进fmt::format/fmt::print时编译器提示找不到匹配的formatter。解决方式是给这个类型写一个继承自ostream_formatter的fmt::formatter特化让 fmt 复用你已有的插入运算符来生成输出。fmt/ostream.h的 API 文档与仓库测试 test/ostream-test.cc 提供了完整的代码和可核对的期望输出本文按这套路径走一遍。哪些类型适合走这条扩展路径{fmt} 让自定义类型支持格式化有两条 API在同命名空间下提供format_as函数或者特化formatter模板见 doc/api.md 的 Formatting User-Defined Types 一节。ostream_formatter属于后一条当你的类型已经有std::ostream operator重载、且不想重新实现解析和格式化逻辑时直接复用插入运算符即可。实现上include/fmt/ostream.h 中的basic_ostream_formatter会先通过一个内部std::basic_ostream执行output value把值写进缓冲区再把缓冲区内容当作字符串继续走字符串格式化流程——这也解释了为什么它支持字符串类的格式说明符、但不支持数值类的。两个使用边界来自 doc/api.md非 void 指针类型的格式化被有意禁止不能通过任何扩展 API 使其可格式化同一个类型上同时提供formatter特化和format_as重载是不允许的。前置条件把 fmt 引入你的构建在写特化之前确认项目能编译#include fmt/ostream.h并链接到 fmt 目标。doc/get-started.md 给出了 CMake 下的三种接入方式任选其一# 方式一找到已安装的 fmt find_package(fmt) target_link_libraries(your-target fmt::fmt)# 方式二把 fmt 源码树作为子目录嵌入项目 add_subdirectory(fmt) target_link_libraries(your-target fmt::fmt)# 方式三FetchContent 在配置期自动下载CMake 3.11 include(FetchContent) FetchContent_Declare( fmt GIT_REPOSITORY https://github.com/fmtlib/fmt GIT_TAG e69e5f977d458f2650bb346dadf2ad30c5320281) # 10.2.1 FetchContent_MakeAvailable(fmt) target_link_libraries(your-target fmt::fmt)代码中的your-target是文档保留的占位符替换成你自己的 CMake 目标名。如果你不用编译库可以把fmt::fmt换成fmt::fmt-header-only目标get-started 同时推荐编译库或 module 库以获得更好的构建时间这里只保留与本场景相关的选项。定义继承 ostream_formatter 的 formatter 特化下面这段代码来自 doc/api.md 的 std::ostreamSupport 一节是文档给出的标准形态#include fmt/ostream.h struct date { int year, month, day; friend std::ostream operator(std::ostream os, const date d) { return os d.year - d.month - d.day; } }; template struct fmt::formatterdate : ostream_formatter {}; std::string s fmt::format(The date is {}, date{2012, 12, 9}); // s The date is 2012-12-9要点必须包含#include fmt/ostream.hostream_formatter由它定义即 include/fmt/ostream.h 中的basic_ostream_formattercharoperator写成friend函数即可特化本身只有一行template struct fmt::formatterdate : ostream_formatter {};parse逻辑从formatterstring_view继承不需要自己实现输出完全由你的operator决定——示例里operator直接拼接数字所以文档示例结果是The date is 2012-12-9月和日不补零。如果你的operator自带补零格式化结果会不同这不是ostream_formatter能改变的。一个容易踩的包含顺序问题在仓库测试里有专门验证test/ostream-test.cc 开头先包含fmt/format.h、之后再包含fmt/ostream.h并注释说明这是为了确认“先 format.h 后 ostream.h”时特化不冲突。也就是说如果你的项目里已经各处包含fmt/format.h直接在其后追加#include fmt/ostream.h即可不必调整包含顺序。验证输出对照仓库测试的断言doc/api.md 给出的示例输出s The date is 2012-12-9与仓库测试中的断言一致可以直接用同一组输入核对你的构建结果。test/ostream-test.cc 中对应的是TEST(ostream_test, format) { EXPECT_EQ(The date is 2012-12-9, fmt::format(The date is {0}, date(2012, 12, 9))); }在 C20 下格式字符串默认做编译期检查所以上面这种类型正确、字段数量正确的调用能编译通过本身就是第一层验证运行后比对输出字符串The date is 2012-12-9文档示例值完成第二层验证。同一个测试还展示了ostream_formatter对字符串格式说明符的支持因为最终走的是formatterstring_view的解析路径// 来自 test/ostream-test.cc 的 format_specs 用例 EXPECT_EQ(def , fmt::format({0:5}, test_string(def))); EXPECT_EQ( def, fmt::format({0:5}, test_string(def))); EXPECT_EQ( def , fmt::format({0:^5}, test_string(def))); EXPECT_EQ(def**, fmt::format({0:*5}, test_string(def))); EXPECT_EQ(te, fmt::format({0:.2}, test_string(test)));对齐、宽度、填充和精度都按字符串规则作用于operator生成的完整文本宽度还可以动态传入fmt::format({0:{1}}, test_string(test), 13)得到test 以上均为文档/测试中的示例输出。已知限制数值说明符会直接报错由于格式化过程按字符串处理数值类的格式说明符会抛出fmt::format_error。test/ostream-test.cc 中用运行时格式字符串fmt::runtime包装验证了这一点EXPECT_THROW_MSG((void)fmt::format(runtime({0:}), test_string()), format_error, invalid format specifier); EXPECT_THROW_MSG((void)fmt::format(runtime({0:05}), test_string()), format_error, format specifier requires numeric argument);即{:}、{:-}、{: }、{:#}报 invalid format specifier{:05}报 format specifier requires numeric argument。如果你的类型需要05这类补零数值语义说明ostream_formatter不是合适的载体应回头写完整的formatter特化。另外 include/fmt/ostream.h 中set_debug_format()被显式 deletedebug 格式同样不可用。不想写特化时的一次性替代fmt::streamed如果只是在某一处临时打印不想为类型维护一个 formatter 特化include/fmt/ostream.h 提供了fmt::streamed它返回一个视图格式化时直接走该类型的operatorfmt::print(Current thread id: {}\n, fmt::streamed(std::this_thread::get_id()));这个例子来自头文件中streamed的注释是文档示例。两条路径的取舍fmt::streamed(value)无需任何模板代码适合零散调用formatter特化则让该类型在所有 fmt 调用点都能直接用{}格式化适合被频繁使用的类型。同一个头文件还提供fmt::print(std::ostream, ...)/fmt::println把格式化结果直接写入std::cout、std::cerr等流例如 test/ostream-test.cc 中的fmt::println(os, Dont {}!, panic)向std::ostringstream写入Dont panic!\n。这与本主题的格式化扩展相互独立这里只作提示不在本文展开。小结与边界类型已有operator且输出语义就是想要的最终文本时template struct fmt::formatterT : ostream_formatter {};是最短路径验证方式C20 编译期检查通过 运行时输出与文档示例如The date is 2012-12-9或你的operator语义一致数值说明符、-、#、05等与 debug 格式不被支持会抛format_error同一类型不能再叠加format_as非 void 指针类型无法扩展为可格式化。需要更精细的控制如把operator语义替换成别的时回到 doc/api.md Formatting User-Defined Types 一节自己实现parse/format那是另一条扩展路径本文不覆盖。【免费下载链接】fmtA modern formatting library项目地址: https://gitcode.com/GitHub_Trending/fm/fmt创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考