mirror of
https://github.com/fmtlib/fmt.git
synced 2026-10-29 02:17:28 +08:00
Fix FMT_COMPILE with ranges, tuples and maps
nested_format_specs::write formatted the body of a padded value into a temporary buffer using a context of the same type as the outer one. This only works when the outer context writes to an appender, so FMT_COMPILE and format_to with other output iterators failed to compile even when no width was given. Use a buffer context that takes arguments and locale from the outer context when the outer context doesn't write to an appender. Fixes #4949.
This commit is contained in:
parent
fdf31252e6
commit
b903e4fcbf
@ -610,7 +610,8 @@ template <typename T> class basic_appender;
|
||||
using appender = basic_appender<char>;
|
||||
|
||||
class context;
|
||||
template <typename OutputIt, typename Char> class generic_context;
|
||||
template <typename OutputIt, typename Char, typename ArgContext = void>
|
||||
class generic_context;
|
||||
template <typename Char> class parse_context;
|
||||
|
||||
// Longer aliases for C++20 compatibility.
|
||||
|
||||
@ -4149,9 +4149,14 @@ template <typename Char> 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<T&&>(values)...);
|
||||
|
||||
// FormatContext can only be reused if it writes to an appender (#4949).
|
||||
using buffer_context = conditional_t<
|
||||
std::is_same<decltype(ctx.out()), basic_appender<Char>>::value,
|
||||
FormatContext,
|
||||
generic_context<basic_appender<Char>, Char, FormatContext>>;
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
auto buffer_ctx =
|
||||
FormatContext(basic_appender<Char>(buf), ctx.args(), ctx.locale());
|
||||
buffer_context(basic_appender<Char>(buf), ctx.args(), ctx.locale());
|
||||
f.write_body(buffer_ctx, static_cast<T&&>(values)...);
|
||||
return detail::write<Char>(
|
||||
ctx.out(), basic_string_view<Char>(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 <typename OutputIt, typename Char> class generic_context {
|
||||
// different from OutputIt::value_type. ArgContext is the context the arguments
|
||||
// are typed on; void means the context itself.
|
||||
template <typename OutputIt, typename Char, typename ArgContext>
|
||||
class generic_context {
|
||||
private:
|
||||
using arg_context = conditional_t<std::is_void<ArgContext>::value,
|
||||
generic_context, ArgContext>;
|
||||
|
||||
OutputIt out_;
|
||||
basic_format_args<generic_context> args_;
|
||||
basic_format_args<arg_context> args_;
|
||||
locale_ref loc_;
|
||||
|
||||
public:
|
||||
@ -4180,27 +4190,24 @@ template <typename OutputIt, typename Char> class generic_context {
|
||||
using iterator = OutputIt;
|
||||
enum { builtin_types = FMT_BUILTIN_TYPES };
|
||||
|
||||
constexpr generic_context(OutputIt out,
|
||||
basic_format_args<generic_context> args,
|
||||
constexpr generic_context(OutputIt out, basic_format_args<arg_context> 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<generic_context> {
|
||||
constexpr auto arg(int id) const -> basic_format_arg<arg_context> {
|
||||
return args_.get(id);
|
||||
}
|
||||
auto arg(basic_string_view<Char> name) const
|
||||
-> basic_format_arg<generic_context> {
|
||||
-> basic_format_arg<arg_context> {
|
||||
return args_.get(name);
|
||||
}
|
||||
constexpr auto arg_id(basic_string_view<Char> name) const -> int {
|
||||
return args_.get_id(name);
|
||||
}
|
||||
auto args() const -> const basic_format_args<generic_context>& {
|
||||
return args_;
|
||||
}
|
||||
auto args() const -> const basic_format_args<arg_context>& { return args_; }
|
||||
|
||||
constexpr auto out() const -> iterator { return out_; }
|
||||
|
||||
|
||||
@ -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<int, 3> 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<char> 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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user