From 378a5ab3c1fbb16873596de5215db5aa55bd5b09 Mon Sep 17 00:00:00 2001 From: Harry Denholm Date: Fri, 17 Oct 2025 15:10:56 +0000 Subject: [PATCH 1/9] value-initialise the buffer array member of detail::ansi_color_escape so that it can be used in a constexpr context in MSVC; compiler rejects as non-constant due to 'uninitialized symbol' otherwise (#4581) Co-authored-by: Harry Denholm --- include/fmt/color.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/color.h b/include/fmt/color.h index b69c1488..2cbc53ca 100644 --- a/include/fmt/color.h +++ b/include/fmt/color.h @@ -429,7 +429,7 @@ template struct ansi_color_escape { private: static constexpr size_t num_emphases = 8; - Char buffer[7u + 4u * num_emphases]; + Char buffer[7u + 4u * num_emphases] = {}; size_t size = 0; static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, From 85f6ecc7a01dcd9428dd365cbabc568b96c4815e Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Sun, 19 Oct 2025 02:05:21 +0500 Subject: [PATCH 2/9] 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"); From e2aa06cd0a289985159f775600f82fcb4e006d4a Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 18 Oct 2025 07:39:31 -0700 Subject: [PATCH 3/9] Workaround ABI incompatibility between clang ang gcc --- include/fmt/base.h | 5 ++++- src/format.cc | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/include/fmt/base.h b/include/fmt/base.h index d7679757..e58a5d72 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -924,7 +924,10 @@ class locale_ref { constexpr locale_ref() : locale_(nullptr) {} template - locale_ref(const Locale& loc); + locale_ref(const Locale& loc) : locale_(&loc) { + // Check if std::isalpha is found via ADL to reduce the chance of misuse. + isalpha('x', loc); + } inline explicit operator bool() const noexcept { return locale_ != nullptr; } #endif // FMT_USE_LOCALE diff --git a/src/format.cc b/src/format.cc index 05d0105b..e583446e 100644 --- a/src/format.cc +++ b/src/format.cc @@ -10,7 +10,7 @@ FMT_BEGIN_NAMESPACE #if FMT_USE_LOCALE -template FMT_API locale_ref::locale_ref(const std::locale& loc); +template FMT_API locale_ref::locale_ref(const std::locale& loc); // DEPRECATED! template FMT_API auto locale_ref::get() const -> std::locale; #endif From e8da5ba2757f566c30b23c9aa2a2a6223b63362a Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 19 Oct 2025 10:22:39 -0700 Subject: [PATCH 4/9] Fix formatting --- src/format.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/format.cc b/src/format.cc index e583446e..526082e3 100644 --- a/src/format.cc +++ b/src/format.cc @@ -10,7 +10,7 @@ FMT_BEGIN_NAMESPACE #if FMT_USE_LOCALE -template FMT_API locale_ref::locale_ref(const std::locale& loc); // DEPRECATED! +template FMT_API locale_ref::locale_ref(const std::locale& loc); // DEPRECATED! template FMT_API auto locale_ref::get() const -> std::locale; #endif From a2289b8593d22e2b791f137c996cd575238ca5db Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 19 Oct 2025 10:30:42 -0700 Subject: [PATCH 5/9] Fix the build --- include/fmt/format-inl.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 227dd9bb..945cb912 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -47,11 +47,6 @@ using std::locale; using std::numpunct; using std::use_facet; } // namespace detail - -template > -locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { - static_assert(std::is_same::value, ""); -} #else namespace detail { struct locale {}; From 08d38d6e78e370768ebb7c0fb6b547a2db71d92c Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 19 Oct 2025 10:35:45 -0700 Subject: [PATCH 6/9] Make error_code formatter debug-enabled --- include/fmt/std.h | 2 ++ test/std-test.cc | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/include/fmt/std.h b/include/fmt/std.h index 3fe795bf..bfa13fa0 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -513,6 +513,8 @@ template <> struct formatter { bool debug_ = false; public: + FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; } + FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { auto it = ctx.begin(), end = ctx.end(); if (it == end) return it; diff --git a/test/std-test.cc b/test/std-test.cc index 3fdf0532..18f6bd3f 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -13,6 +13,7 @@ #include #include "fmt/os.h" // fmt::system_category +#include "fmt/ranges.h" #include "gtest-extra.h" // StartsWith #ifdef __cpp_lib_filesystem @@ -335,6 +336,10 @@ TEST(std_test, error_code) { EXPECT_EQ(fmt::format("{:s}", ec), ec.message()); EXPECT_EQ(fmt::format("{:?}", std::error_code(42, generic)), "\"generic:42\""); + EXPECT_EQ(fmt::format("{}", + std::map{ + {std::error_code(42, generic), 0}}), + "{\"generic:42\": 0}"); } template void exception_test() { From d6bdb69c62da37f13f5593cec10142f17d8e190f Mon Sep 17 00:00:00 2001 From: Fatih BAKIR Date: Mon, 20 Oct 2025 07:49:23 -0700 Subject: [PATCH 7/9] Move FMT_API from ostream class to members (#4584) Putting FMT_API on the class definition propagates it to the base class detail::buffer's members. However, MSVC not emit definitions for inline members unless it sees the symbols as FMT_API when compiling. This fix removes the FMT_API declaration from the class itself and marks individual non-inline members as FMT_API to address the issue. Fixes https://github.com/fmtlib/fmt/issues/4576 --- include/fmt/os.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/fmt/os.h b/include/fmt/os.h index 40cdcdd4..9455d9d3 100644 --- a/include/fmt/os.h +++ b/include/fmt/os.h @@ -365,17 +365,17 @@ FMT_INLINE_VARIABLE constexpr auto buffer_size = detail::buffer_size(); /// A fast buffered output stream for writing from a single thread. Writing from /// multiple threads without external synchronization may result in a data race. -class FMT_API ostream : private detail::buffer { +class ostream : private detail::buffer { private: file file_; - ostream(cstring_view path, const detail::ostream_params& params); + FMT_API ostream(cstring_view path, const detail::ostream_params& params); - static void grow(buffer& buf, size_t); + FMT_API static void grow(buffer& buf, size_t); public: - ostream(ostream&& other) noexcept; - ~ostream(); + FMT_API ostream(ostream&& other) noexcept; + FMT_API ~ostream(); operator writer() { detail::buffer& buf = *this; From 9721d974fcd88cd522251568730d8de542cc8c4e Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 22 Oct 2025 12:34:47 -0700 Subject: [PATCH 8/9] Workaround ABI compatibility between clang and gcc --- include/fmt/base.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/base.h b/include/fmt/base.h index e58a5d72..f407888e 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -923,7 +923,7 @@ class locale_ref { public: constexpr locale_ref() : locale_(nullptr) {} - template + template locale_ref(const Locale& loc) : locale_(&loc) { // Check if std::isalpha is found via ADL to reduce the chance of misuse. isalpha('x', loc); From 9395ef5fcb817c6145c7f0d274be87629c360bf2 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 22 Oct 2025 13:48:13 -0700 Subject: [PATCH 9/9] Don't include std::locale::collate value in the symbol --- include/fmt/base.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/base.h b/include/fmt/base.h index f407888e..b9fe4e8f 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -923,7 +923,7 @@ class locale_ref { public: constexpr locale_ref() : locale_(nullptr) {} - template + template locale_ref(const Locale& loc) : locale_(&loc) { // Check if std::isalpha is found via ADL to reduce the chance of misuse. isalpha('x', loc);