如何为std::optional编写自定义格式化器

后端开发 2026-07-09

我正在尝试为 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
}

Godbolt

The formatter works so long as I don't specify how the elements of the vector vec, i.e. the std::optional<uint64_t>s, should be formatted. What I want to do is to specify how the uint64_ts should be formatted. In this example, I want them to formatted to hex strings. However, when I try to use my formatter with {:x}, I get a segfault。

Program returned: 139
terminate called after throwing an instance of 'fmt::v6::format_error'
  what():  unknown format specifier
Program terminated with signal: SIGSEGV

我不确定 std::format API的工作原理,但据我所知,我需要为自定义类型指定两个函数:parseformatparse 函数负责解析格式说明符,即 x}。它应该消耗格式说明中与之相关的所有标记,然后把剩余的格式说明返回。在我的情况下,我还没有指定 parse 函数,所以它应该保持原样返回 x},这是 parse 的默认行为。

这份剩余的说明随后被 format 中的 ctx 捕获。如果 op 具有一个值,那么就返回 ctx 的值以及 op 的值。这就是 format_to(ctx.out(), "{}", op.value()); 的作用。

这套逻辑在我看来是合理的,所以我不知道为什么会出现段错误。应该如何修改我的格式化器,才能让它实现我想要的效果?

解决方案

实现格式化代码(parse 函数)的一种非常简单的方法,是从已经具备你想要使用的格式化代码的格式化器继承。

根本不用写 parse。直接使用继承自的那个。

当你想要使用这些代码时,把调用转发给基类的 format 函数。

template <typename T>
struct fmt::formatter<std::optional<T>> : fmt::formatter<T>
{
  constexpr auto format (std::optional<T> const& op, auto& ctx) const 
  {
    if(op) return fmt::formatter<T>::format(op.value(), ctx);
    return format_to(ctx.out(), "-");
  }
};

现在,如果你想让向量的元素以十六进制显示,你应该使用 "{::x}",因为 "{:x}" 要求向量本身也用十六进制表示。

    fmt::println("{::x}", vec);

在Compiler Explorer中查看其运行效果


正如注释中所指出的,可能已经为每个 std::optional 存在一个格式化器,在这种情况下你会希望让自定义格式化器更加专门化,以防止ODR违规。

template <typename T>
requires std::integral<T>   // <-- ADD THIS
struct fmt::formatter<std::optional<T>> : fmt::formatter<T>
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章