Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
16 changes: 16 additions & 0 deletions doc/get-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(<your-target> 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
Expand Down
96 changes: 81 additions & 15 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -4111,63 +4111,129 @@ template <typename T> struct formatter<group_digits_view<T>> : formatter<T> {
}
};

template <typename T, typename Char> struct nested_view {
namespace detail {

template <typename OutputIt, typename Char, typename OuterContext>
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<Char> name) const
-> decltype(outer_.arg(name)) {
return outer_.arg(name);
}
FMT_CONSTEXPR auto arg_id(basic_string_view<Char> 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 <typename T, typename Char, typename OuterContext = void>
struct nested_view {
const formatter<T, Char>* fmt;
const T* value;
const OuterContext* outer;
};

} // namespace detail

template <typename T, typename Char>
struct formatter<nested_view<T, Char>, Char> {
struct formatter<detail::nested_view<T, Char, void>, Char> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return ctx.begin();
}
template <typename FormatContext>
auto format(nested_view<T, Char> view, FormatContext& ctx) const
auto format(detail::nested_view<T, Char, void> view, FormatContext& ctx) const
-> decltype(ctx.out()) {
return view.fmt->format(*view.value, ctx);
}
};

template <typename T, typename Char, typename OuterContext>
struct formatter<detail::nested_view<T, Char, OuterContext>, Char> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return ctx.begin();
}
template <typename FormatContext>
auto format(detail::nested_view<T, Char, OuterContext> view,
FormatContext& ctx) const
-> decltype(ctx.out()) {
static_assert(
std::is_same<typename OuterContext::char_type, Char>::value, "");
using context = detail::nested_context<decltype(ctx.out()), Char,
OuterContext>;
auto nested_ctx = context(ctx.out(), *view.outer);
return view.fmt->format(*view.value, nested_ctx);
}
};

template <typename T, typename Char = char> struct nested_formatter {
private:
basic_specs specs_;
int width_;
detail::dynamic_format_specs<Char> specs_;
formatter<T, Char> formatter_;

public:
constexpr nested_formatter() : width_(0) {}
constexpr nested_formatter() = default;

FMT_CONSTEXPR auto parse(parse_context<Char>& 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<Char>();
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<Char>();
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 <typename FormatContext, typename F>
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<Char>();
write(basic_appender<Char>(buf));
auto specs = format_specs();
specs.width = width_;
specs.width = width;
specs.copy_fill_from(specs_);
specs.set_align(specs_.align());
return detail::write<Char>(
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
}

auto nested(const T& value) const -> nested_view<T, Char> {
return nested_view<T, Char>{&formatter_, &value};
auto nested(const T& value) const -> detail::nested_view<T, Char> {
return detail::nested_view<T, Char>{&formatter_, &value, nullptr};
}

template <typename FormatContext>
auto nested(const T& value, const FormatContext& ctx) const
-> detail::nested_view<T, Char, FormatContext> {
return detail::nested_view<T, Char, FormatContext>{&formatter_, &value,
&ctx};
}
};

Expand Down
32 changes: 32 additions & 0 deletions test/format-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2004,6 +2004,10 @@ struct point {
double x, y;
};

struct dynamic_point {
double x, y;
};

FMT_BEGIN_NAMESPACE
template <> struct formatter<point> : nested_formatter<double> {
auto format(point p, format_context& ctx) const -> decltype(ctx.out()) {
Expand All @@ -2013,10 +2017,38 @@ template <> struct formatter<point> : nested_formatter<double> {
});
}
};

template <> struct formatter<dynamic_point> : nested_formatter<double> {
template <typename FormatContext>
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

Expand Down
Loading