mirror of
https://github.com/fmtlib/fmt.git
synced 2026-11-09 23:00:06 +08:00
Fix FMT_COMPILE with ranges, tuples and maps (#4974)
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
4ded41f186
commit
89a7f4a7ca
@ -610,7 +610,8 @@ template <typename T> class basic_appender;
|
|||||||
using appender = basic_appender<char>;
|
using appender = basic_appender<char>;
|
||||||
|
|
||||||
class context;
|
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;
|
template <typename Char> class parse_context;
|
||||||
|
|
||||||
// Longer aliases for C++20 compatibility.
|
// Longer aliases for C++20 compatibility.
|
||||||
|
|||||||
@ -4143,9 +4143,14 @@ template <typename Char> class nested_format_specs {
|
|||||||
handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, ctx);
|
handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, ctx);
|
||||||
if (specs.width == 0) return f.write_body(ctx, static_cast<T&&>(values)...);
|
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 buf = basic_memory_buffer<Char>();
|
||||||
auto buffer_ctx =
|
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)...);
|
f.write_body(buffer_ctx, static_cast<T&&>(values)...);
|
||||||
return detail::write<Char>(
|
return detail::write<Char>(
|
||||||
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
|
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
|
||||||
@ -4162,11 +4167,16 @@ FMT_BEGIN_EXPORT
|
|||||||
|
|
||||||
// A generic formatting context with custom output iterator and character
|
// A generic formatting context with custom output iterator and character
|
||||||
// (code unit) support. Char is the format string code unit type which can be
|
// (code unit) support. Char is the format string code unit type which can be
|
||||||
// different from OutputIt::value_type.
|
// different from OutputIt::value_type. ArgContext is the context the arguments
|
||||||
template <typename OutputIt, typename Char> class generic_context {
|
// are typed on; void means the context itself.
|
||||||
|
template <typename OutputIt, typename Char, typename ArgContext>
|
||||||
|
class generic_context {
|
||||||
private:
|
private:
|
||||||
|
using arg_context = conditional_t<std::is_void<ArgContext>::value,
|
||||||
|
generic_context, ArgContext>;
|
||||||
|
|
||||||
OutputIt out_;
|
OutputIt out_;
|
||||||
basic_format_args<generic_context> args_;
|
basic_format_args<arg_context> args_;
|
||||||
locale_ref loc_;
|
locale_ref loc_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -4174,27 +4184,24 @@ template <typename OutputIt, typename Char> class generic_context {
|
|||||||
using iterator = OutputIt;
|
using iterator = OutputIt;
|
||||||
enum { builtin_types = FMT_BUILTIN_TYPES };
|
enum { builtin_types = FMT_BUILTIN_TYPES };
|
||||||
|
|
||||||
constexpr generic_context(OutputIt out,
|
constexpr generic_context(OutputIt out, basic_format_args<arg_context> args,
|
||||||
basic_format_args<generic_context> args,
|
|
||||||
locale_ref loc = {})
|
locale_ref loc = {})
|
||||||
: out_(out), args_(args), loc_(loc) {}
|
: out_(out), args_(args), loc_(loc) {}
|
||||||
generic_context(generic_context&&) = default;
|
generic_context(generic_context&&) = default;
|
||||||
generic_context(const generic_context&) = delete;
|
generic_context(const generic_context&) = delete;
|
||||||
void operator=(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);
|
return args_.get(id);
|
||||||
}
|
}
|
||||||
auto arg(basic_string_view<Char> name) const
|
auto arg(basic_string_view<Char> name) const
|
||||||
-> basic_format_arg<generic_context> {
|
-> basic_format_arg<arg_context> {
|
||||||
return args_.get(name);
|
return args_.get(name);
|
||||||
}
|
}
|
||||||
constexpr auto arg_id(basic_string_view<Char> name) const -> int {
|
constexpr auto arg_id(basic_string_view<Char> name) const -> int {
|
||||||
return args_.get_id(name);
|
return args_.get_id(name);
|
||||||
}
|
}
|
||||||
auto args() const -> const basic_format_args<generic_context>& {
|
auto args() const -> const basic_format_args<arg_context>& { return args_; }
|
||||||
return args_;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr auto out() const -> iterator { return out_; }
|
constexpr auto out() const -> iterator { return out_; }
|
||||||
|
|
||||||
|
|||||||
@ -247,6 +247,20 @@ TEST(compile_test, output_iterators) {
|
|||||||
EXPECT_EQ("42.000043", s2.str());
|
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
|
# if FMT_USE_CONSTEVAL
|
||||||
TEST(compile_test, constexpr_formatted_size) {
|
TEST(compile_test, constexpr_formatted_size) {
|
||||||
FMT_CONSTEXPR20 size_t size = fmt::formatted_size(FMT_COMPILE("{}"), 42);
|
FMT_CONSTEXPR20 size_t size = fmt::formatted_size(FMT_COMPILE("{}"), 42);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user