如何为std::optional编写支持格式说明符的自定义fmt格式化器
问题:std::optional自定义fmt格式化器无法传递格式说明符导致崩溃
我正在尝试为std::optional编写自定义std::fmt格式化器,当前代码如下:
#include <fmt/core.h> #include <fmt/ranges.h> #include <vector> #include <optional> #include <cstdint> template <typename T> class fmt::formatter<std::optional<T>> { public: constexpr auto parse (format_parse_context& ctx) { return ctx.begin(); } template <typename Context> constexpr auto format (std::optional<T> const& op, Context& ctx) const { if(op) return format_to(ctx.out(), "{}", op.value()); else return format_to(ctx.out(), "-"); } }; int main () { std::vector<std::optional<uint64_t>> vec{{1}, {2}, {}}; fmt::print("{:}\n", vec); // fmt::print("{:x}\n", vec); <----- SIGSEGV }
当不指定格式说明符时,代码可正常运行,但使用{:x}尝试将uint64_t格式化为十六进制时,会触发段错误,错误信息如下:
Program returned: 139 terminate called after throwing an instance of 'fmt::v6::format_error' what(): unknown format specifier Program terminated with signal: SIGSEGV
解决方案
问题根源在于自定义格式化器没有正确处理格式说明符的传递:你的parse函数直接返回ctx.begin(),未将格式说明符传递给内部T类型的格式化器;同时format函数用硬编码的"{}"格式化值,未利用上下文的格式信息。
正确的做法是让std::optional<T>的格式化器复用T的格式化器逻辑,自动继承T支持的所有格式选项,修改后的代码如下:
#include <fmt/core.h> #include <fmt/ranges.h> #include <vector> #include <optional> #include <cstdint> template <typename T> class fmt::formatter<std::optional<T>> : public fmt::formatter<T> { public: template <typename Context> constexpr auto format(const std::optional<T>& op, Context& ctx) const { if (op) { // 复用T的格式化逻辑,传递当前上下文 return fmt::formatter<T>::format(*op, ctx); } else { return fmt::format_to(ctx.out(), "-"); } } }; int main () { std::vector<std::optional<uint64_t>> vec{{1}, {2}, {}}; fmt::print("{:}\n", vec); fmt::print("{:x}\n", vec); // 现在可正常输出十六进制格式 }
关键说明
- 继承
fmt::formatter<T>:无需手动实现parse函数,格式说明符会被基类正确解析,自动传递给T的格式化逻辑。 - 调用基类
format方法:当optional有值时,直接使用基类的format方法处理内部值,确保{:x}等格式说明符能正确作用于uint64_t。 - 空值处理:保留原逻辑,用
"-"表示空的std::optional。
此修改不仅解决了崩溃问题,还让格式化器兼容所有T类型原本支持的格式化选项,扩展性更强。
内容的提问来源于stack exchange,提问作者Setu
相关产品推荐
相关产品推荐

