From 85f6ecc7a01dcd9428dd365cbabc568b96c4815e Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Sun, 19 Oct 2025 02:05:21 +0500 Subject: [PATCH] Add format_as support for std::variant and std::expected formatters (#4575) Signed-off-by: Vladislav Shchapov --- include/fmt/format.h | 8 ++++++++ include/fmt/ranges.h | 8 -------- include/fmt/std.h | 28 ++++++++++++--------------- test/std-test.cc | 46 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 24 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 0cd3a232..4a653007 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -763,6 +763,14 @@ template struct allocator : private std::decay { } }; +template +FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set) + -> decltype(f.set_debug_format(set)) { + f.set_debug_format(set); +} +template +FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {} + } // namespace detail FMT_BEGIN_EXPORT diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index 0823cbf2..36b38e29 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -241,14 +241,6 @@ using range_reference_type = template using uncvref_type = remove_cvref_t>; -template -FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set) - -> decltype(f.set_debug_format(set)) { - f.set_debug_format(set); -} -template -FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {} - template struct range_format_kind_ : std::integral_constant& quoted, #endif // FMT_CPP_LIB_FILESYSTEM #if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT -template -auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt { + +template +auto write_escaped_alternative(OutputIt out, const T& v, FormatContext& ctx) + -> OutputIt { if constexpr (has_to_string_view::value) return write_escaped_string(out, detail::to_string_view(v)); if constexpr (std::is_same_v) return write_escaped_char(out, v); - return write(out, v); + + formatter, Char> underlying; + maybe_set_debug_format(underlying, true); + return underlying.format(v, ctx); } #endif @@ -382,18 +387,9 @@ struct formatter, Char, static constexpr basic_string_view none = detail::string_literal{}; - template - FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set) - -> decltype(u.set_debug_format(set)) { - u.set_debug_format(set); - } - - template - FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {} - public: FMT_CONSTEXPR auto parse(parse_context& ctx) { - maybe_set_debug_format(underlying_, true); + detail::maybe_set_debug_format(underlying_, true); return underlying_.parse(ctx); } @@ -429,10 +425,10 @@ struct formatter, Char, if (value.has_value()) { out = detail::write(out, "expected("); if constexpr (!std::is_void::value) - out = detail::write_escaped_alternative(out, *value); + out = detail::write_escaped_alternative(out, *value, ctx); } else { out = detail::write(out, "unexpected("); - out = detail::write_escaped_alternative(out, value.error()); + out = detail::write_escaped_alternative(out, value.error(), ctx); } *out++ = ')'; return out; @@ -496,7 +492,7 @@ struct formatter(out, v); + out = detail::write_escaped_alternative(out, v, ctx); }, value); } diff --git a/test/std-test.cc b/test/std-test.cc index 4bd8abce..3fdf0532 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -197,7 +197,33 @@ class my_class { return fmt::to_string(elm.av); } }; + +class my_class_int { + public: + int av; + + private: + friend auto format_as(const my_class_int& elm) -> int { return elm.av; } +}; } // namespace my_nso + +TEST(std_test, expected_format_as) { +#ifdef __cpp_lib_expected + EXPECT_EQ( + fmt::format( + "{}", std::expected{my_nso::my_number::one}), + "expected(\"first\")"); + EXPECT_EQ( + fmt::format("{}", + std::expected{my_nso::my_class{7}}), + "expected(\"7\")"); + EXPECT_EQ(fmt::format("{}", + std::expected{ + my_nso::my_class_int{8}}), + "expected(8)"); +#endif +} + TEST(std_test, optional_format_as) { #ifdef __cpp_lib_optional EXPECT_EQ(fmt::format("{}", std::optional{}), "none"); @@ -206,6 +232,8 @@ TEST(std_test, optional_format_as) { EXPECT_EQ(fmt::format("{}", std::optional{}), "none"); EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class{7}}), "optional(\"7\")"); + EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class_int{8}}), + "optional(8)"); #endif } @@ -275,6 +303,24 @@ TEST(std_test, variant) { #endif } +TEST(std_test, variant_format_as) { +#ifdef __cpp_lib_variant + + EXPECT_EQ(fmt::format("{}", std::variant{}), + "variant(\"first\")"); + EXPECT_EQ(fmt::format( + "{}", std::variant{my_nso::my_number::one}), + "variant(\"first\")"); + EXPECT_EQ( + fmt::format("{}", std::variant{my_nso::my_class{7}}), + "variant(\"7\")"); + EXPECT_EQ( + fmt::format("{}", + std::variant{my_nso::my_class_int{8}}), + "variant(8)"); +#endif +} + TEST(std_test, error_code) { auto& generic = std::generic_category(); EXPECT_EQ(fmt::format("{}", std::error_code(42, generic)), "generic:42");