diff --git a/include/fmt/core.h b/include/fmt/core.h index 5748381c..04983ee5 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -610,7 +610,8 @@ template class basic_appender; using appender = basic_appender; class context; -template class generic_context; +template +class generic_context; template class parse_context; // Longer aliases for C++20 compatibility. diff --git a/include/fmt/format.h b/include/fmt/format.h index 6064d4c9..300a395a 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -4149,9 +4149,14 @@ template class nested_format_specs { handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, ctx); if (specs.width == 0) return f.write_body(ctx, static_cast(values)...); + // FormatContext can only be reused if it writes to an appender (#4949). + using buffer_context = conditional_t< + std::is_same>::value, + FormatContext, + generic_context, Char, FormatContext>>; auto buf = basic_memory_buffer(); auto buffer_ctx = - FormatContext(basic_appender(buf), ctx.args(), ctx.locale()); + buffer_context(basic_appender(buf), ctx.args(), ctx.locale()); f.write_body(buffer_ctx, static_cast(values)...); return detail::write( ctx.out(), basic_string_view(buf.data(), buf.size()), specs); @@ -4168,11 +4173,16 @@ FMT_BEGIN_EXPORT // A generic formatting context with custom output iterator and character // (code unit) support. Char is the format string code unit type which can be -// different from OutputIt::value_type. -template class generic_context { +// different from OutputIt::value_type. ArgContext is the context the arguments +// are typed on; void means the context itself. +template +class generic_context { private: + using arg_context = conditional_t::value, + generic_context, ArgContext>; + OutputIt out_; - basic_format_args args_; + basic_format_args args_; locale_ref loc_; public: @@ -4180,27 +4190,24 @@ template class generic_context { using iterator = OutputIt; enum { builtin_types = FMT_BUILTIN_TYPES }; - constexpr generic_context(OutputIt out, - basic_format_args args, + constexpr generic_context(OutputIt out, basic_format_args args, locale_ref loc = {}) : out_(out), args_(args), loc_(loc) {} generic_context(generic_context&&) = default; generic_context(const generic_context&) = delete; void operator=(const generic_context&) = delete; - constexpr auto arg(int id) const -> basic_format_arg { + constexpr auto arg(int id) const -> basic_format_arg { return args_.get(id); } auto arg(basic_string_view name) const - -> basic_format_arg { + -> basic_format_arg { return args_.get(name); } constexpr auto arg_id(basic_string_view name) const -> int { return args_.get_id(name); } - auto args() const -> const basic_format_args& { - return args_; - } + auto args() const -> const basic_format_args& { return args_; } constexpr auto out() const -> iterator { return out_; } diff --git a/test/compile-test.cc b/test/compile-test.cc index 44e2f0c9..12a7f33c 100644 --- a/test/compile-test.cc +++ b/test/compile-test.cc @@ -247,6 +247,20 @@ TEST(compile_test, output_iterators) { EXPECT_EQ("42.000043", s2.str()); } +TEST(compile_test, format_range) { + // https://github.com/fmtlib/fmt/issues/4949 + std::array values{1, 2, 3}; + EXPECT_EQ("array=[1, 2, 3]", fmt::format(FMT_COMPILE("array={}"), values)); + EXPECT_EQ("***********[1, 2, 3]", + fmt::format(FMT_COMPILE("{:*>20}"), values)); + EXPECT_EQ("*****[ 1, 2, 3]", + fmt::format(FMT_COMPILE("{:*>20:{}}"), values, 3)); + + std::list out; + fmt::format_to(std::back_inserter(out), FMT_COMPILE("{:*>20}"), values); + EXPECT_EQ("***********[1, 2, 3]", std::string(out.begin(), out.end())); +} + # if FMT_USE_CONSTEVAL TEST(compile_test, constexpr_formatted_size) { FMT_CONSTEXPR20 size_t size = fmt::formatted_size(FMT_COMPILE("{}"), 42);