使用format_as适配范围类型时编译失败的问题排查
问题原因
fmt的format_as机制要求返回的类型必须是fmt已经内置支持格式化、或者有对应fmt::formatter特化的类型。你返回的std::views::filter生成的是std::ranges::filter_view对象,而fmt 10.1.1并没有为C++23的这类范围适配器视图提供默认格式化支持——这就是编译失败的核心原因。
之前自定义fmt::formatter<CodecMask>能正常工作,是因为你直接在formatter里实现了遍历过滤和格式化的完整逻辑,而format_as仅负责类型转换,把自定义类型转成fmt“懂怎么处理”的类型,显然视图不在这个列表里。
解决办法
1. 把过滤结果转成fmt支持的容器返回
把过滤后的Codec元素收集到std::vector或者固定大小的std::array里,fmt对标准容器有内置的格式化支持,配合Codec的format_as就能直接输出列表:
#include <fmt/ranges.h> #include <ranges> #include <vector> enum class Codec { A, B, C, D }; struct CodecMask { bool has_a = false; bool has_b = false; bool has_c = false; bool has_d = false; }; // 先为Codec实现format_as auto format_as(Codec c) { switch(c) { case Codec::A: return "A"; case Codec::B: return "B"; case Codec::C: return "C"; case Codec::D: return "D"; default: return "Unknown"; } } // 为CodecMask实现format_as,返回fmt支持的vector容器 auto format_as(const CodecMask& mask) { std::vector<Codec> active_codecs; if (mask.has_a) active_codecs.push_back(Codec::A); if (mask.has_b) active_codecs.push_back(Codec::B); if (mask.has_c) active_codecs.push_back(Codec::C); if (mask.has_d) active_codecs.push_back(Codec::D); return active_codecs; }
调用fmt::format("{}", mask)就能输出类似[A, C]的结果,符合预期。
2. 为filter_view特化fmt::formatter(不推荐)
如果不想额外分配容器,可以为你用到的filter_view特化fmt::formatter,但这种方式耦合性高,不同视图类型要单独处理:
#include <fmt/core.h> #include <ranges> #include <array> enum class Codec { A, B, C, D }; // 为特定类型的filter_view特化formatter template <typename Pred> struct fmt::formatter<std::ranges::filter_view<std::array<Codec,4>, Pred>, char> { template <typename FormatContext> auto format(const auto& view, FormatContext& ctx) { auto out = ctx.out(); *out++ = '['; bool first = true; for (const auto& codec : view) { if (!first) { *out++ = ','; *out++ = ' '; } fmt::format_to(out, "{}", codec); first = false; } *out++ = ']'; return out; } };
这种方法需要明确filter_view的具体底层范围类型,通用性差,除非必要不推荐使用。
3. 继续用自定义fmt::formatter(最可靠)
既然之前的自定义formatter能正常工作,这其实是最稳妥的方案——直接在formatter里处理过滤和格式化逻辑,完全不受fmt对视图支持的限制:
#include <fmt/core.h> enum class Codec { A, B, C, D }; struct CodecMask { bool has_a = false; bool has_b = false; bool has_c = false; bool has_d = false; }; template <> struct fmt::formatter<CodecMask> { // 可选:若需支持格式说明符,可在此添加解析逻辑,默认无需处理 constexpr auto parse(fmt::format_parse_context& ctx) { return ctx.begin(); } template <typename FormatContext> auto format(const CodecMask& mask, FormatContext& ctx) { auto out = ctx.out(); *out++ = '['; bool first = true; auto add_codec = [&](const char* name) { if (!first) { *out++ = ','; *out++ = ' '; } fmt::format_to(out, "{}", name); first = false; }; if (mask.has_a) add_codec("A"); if (mask.has_b) add_codec("B"); if (mask.has_c) add_codec("C"); if (mask.has_d) add_codec("D"); *out++ = ']'; return out; } };
这种方式完全掌控格式化过程,兼容性最好,不会因为fmt版本或视图类型的问题出故障。
内容的提问来源于stack exchange,提问作者Carson
相关产品推荐
相关产品推荐

