From d0d9adaf6ee72213ad2f9925b7056d5eb2890467 Mon Sep 17 00:00:00 2001 From: Sahil Sinha Date: Sun, 23 Aug 2026 15:25:35 +0530 Subject: [PATCH 1/2] Document C++20 module usage with CMake --- doc/get-started.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/get-started.md b/doc/get-started.md index c37d4139940d..804dd0127bf8 100644 --- a/doc/get-started.md +++ b/doc/get-started.md @@ -47,6 +47,22 @@ In order to use the header-only target or the module target, simply substitute t `fmt::fmt` in the above steps with `fmt::fmt-header-only` or `fmt::fmt-module` accordingly. +### Using the C++20 Module + +Link your target to `fmt::fmt-module` and import `fmt` instead of including a +{fmt} header: + + target_link_libraries( PRIVATE fmt::fmt-module) + + import fmt; + + int main() { + fmt::print("Hello, world!\\n"); + } + +The module target requires C++20 module support in CMake and the compiler. For +GCC, use CMake 3.28 or newer, Ninja 1.11 or newer, and GCC 15 or newer. + ## Installation ### Debian/Ubuntu From ee0b93c72d2ecd933d24997304bf6e6de2488a0a Mon Sep 17 00:00:00 2001 From: Sahil Sinha Date: Sun, 23 Aug 2026 16:00:07 +0530 Subject: [PATCH 2/2] Fix dynamic specs in nested_formatter (#3860) --- ChangeLog.md | 4 ++ include/fmt/format.h | 96 +++++++++++++++++++++++++++++++++++++------- test/format-test.cc | 32 +++++++++++++++ 3 files changed, 117 insertions(+), 15 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 5657acb16530..523e50d59584 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -31,6 +31,10 @@ out-of-range ones (https://github.com/fmtlib/fmt/issues/4839). Thanks @igoloe. +- Fixed dynamic width and precision handling in nested formatters by + resolving nested format arguments against the enclosing format context + (https://github.com/fmtlib/fmt/issues/3860). + - Fixed an `FMT_COMPILE` failure for user-defined types formatted via `format_as` (https://github.com/fmtlib/fmt/issues/4794, https://github.com/fmtlib/fmt/pull/4836). diff --git a/include/fmt/format.h b/include/fmt/format.h index d74c3e93c14d..5ff4c53a72a2 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -4111,63 +4111,129 @@ template struct formatter> : formatter { } }; -template struct nested_view { +namespace detail { + +template +class nested_context { + private: + OutputIt out_; + const OuterContext& outer_; + + public: + using char_type = Char; + using iterator = OutputIt; + enum { builtin_types = FMT_BUILTIN_TYPES }; + + constexpr nested_context(OutputIt out, const OuterContext& outer) + : out_(out), outer_(outer) {} + + FMT_CONSTEXPR auto arg(int id) const -> decltype(outer_.arg(id)) { + return outer_.arg(id); + } + FMT_CONSTEXPR auto arg(basic_string_view name) const + -> decltype(outer_.arg(name)) { + return outer_.arg(name); + } + FMT_CONSTEXPR auto arg_id(basic_string_view name) const + -> decltype(outer_.arg_id(name)) { + return outer_.arg_id(name); + } + constexpr auto out() const -> iterator { return out_; } + FMT_CONSTEXPR void advance_to(iterator it) { out_ = it; } + constexpr auto locale() const -> locale_ref { return outer_.locale(); } +}; + +template +struct nested_view { const formatter* fmt; const T* value; + const OuterContext* outer; }; +} // namespace detail + template -struct formatter, Char> { +struct formatter, Char> { FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { return ctx.begin(); } template - auto format(nested_view view, FormatContext& ctx) const + auto format(detail::nested_view view, FormatContext& ctx) const -> decltype(ctx.out()) { return view.fmt->format(*view.value, ctx); } }; +template +struct formatter, Char> { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return ctx.begin(); + } + template + auto format(detail::nested_view view, + FormatContext& ctx) const + -> decltype(ctx.out()) { + static_assert( + std::is_same::value, ""); + using context = detail::nested_context; + auto nested_ctx = context(ctx.out(), *view.outer); + return view.fmt->format(*view.value, nested_ctx); + } +}; + template struct nested_formatter { private: - basic_specs specs_; - int width_; + detail::dynamic_format_specs specs_; formatter formatter_; public: - constexpr nested_formatter() : width_(0) {} + constexpr nested_formatter() = default; FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { auto it = ctx.begin(), end = ctx.end(); if (it == end) return it; - auto specs = format_specs(); + auto specs = detail::dynamic_format_specs(); it = detail::parse_align(it, end, specs); - specs_ = specs; + if (it == end) { + specs_ = specs; + return it; + } Char c = *it; - auto width_ref = detail::arg_ref(); if ((c >= '0' && c <= '9') || c == '{') { - it = detail::parse_width(it, end, specs, width_ref, ctx); - width_ = specs.width; + it = detail::parse_width(it, end, specs, specs.width_ref, ctx); } + specs_ = specs; ctx.advance_to(it); return formatter_.parse(ctx); } template auto write_padded(FormatContext& ctx, F write) const -> decltype(ctx.out()) { - if (width_ == 0) return write(ctx.out()); + auto width = specs_.width; + if (specs_.dynamic_width() != arg_id_kind::none) + detail::handle_dynamic_spec(specs_.dynamic_width(), width, + specs_.width_ref, ctx); + if (width == 0) return write(ctx.out()); auto buf = basic_memory_buffer(); write(basic_appender(buf)); auto specs = format_specs(); - specs.width = width_; + specs.width = width; specs.copy_fill_from(specs_); specs.set_align(specs_.align()); return detail::write( ctx.out(), basic_string_view(buf.data(), buf.size()), specs); } - auto nested(const T& value) const -> nested_view { - return nested_view{&formatter_, &value}; + auto nested(const T& value) const -> detail::nested_view { + return detail::nested_view{&formatter_, &value, nullptr}; + } + + template + auto nested(const T& value, const FormatContext& ctx) const + -> detail::nested_view { + return detail::nested_view{&formatter_, &value, + &ctx}; } }; diff --git a/test/format-test.cc b/test/format-test.cc index c16de4b2794d..fb675d5cdd76 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -2004,6 +2004,10 @@ struct point { double x, y; }; +struct dynamic_point { + double x, y; +}; + FMT_BEGIN_NAMESPACE template <> struct formatter : nested_formatter { auto format(point p, format_context& ctx) const -> decltype(ctx.out()) { @@ -2013,10 +2017,38 @@ template <> struct formatter : nested_formatter { }); } }; + +template <> struct formatter : nested_formatter { + template + auto format(dynamic_point p, FormatContext& ctx) const + -> decltype(ctx.out()) { + return write_padded(ctx, [this, p, &ctx](auto out) -> decltype(out) { + return fmt::format_to(out, "({}, {})", this->nested(p.x, ctx), + this->nested(p.y, ctx)); + }); + } +}; FMT_END_NAMESPACE TEST(format_test, nested_formatter) { EXPECT_EQ(fmt::format("{:>16.2f}", point{1, 2}), " (1.00, 2.00)"); + EXPECT_EQ(fmt::format("{:>16.2f}", dynamic_point{1, 2}), + " (1.00, 2.00)"); + EXPECT_EQ(fmt::format("{:.{}f}", dynamic_point{1, 2}, 2), + "(1.00, 2.00)"); + EXPECT_EQ(fmt::format("{:>16.{}f}", dynamic_point{1, 2}, 2), + " (1.00, 2.00)"); + EXPECT_EQ(fmt::format("{:>{}f}", dynamic_point{1, 2}, 24), + " (1.000000, 2.000000)"); + EXPECT_EQ(fmt::format("{:.{prec}f}", dynamic_point{1, 2}, + fmt::arg("prec", 2)), + "(1.00, 2.00)"); + EXPECT_EQ(fmt::format("{0:.{1}f}", dynamic_point{1, 2}, 2), + "(1.00, 2.00)"); + auto output = std::string(); + fmt::format_to(std::back_inserter(output), "{:.{}f}", dynamic_point{1, 2}, + 2); + EXPECT_EQ("(1.00, 2.00)", output); } #endif // __cpp_generic_lambdas