Skip to content

Commit

Permalink
Allow format_to_n to be executed at compile time
Browse files Browse the repository at this point in the history
  • Loading branch information
Sunday111 committed Feb 25, 2025
1 parent 7f76955 commit 81b2930
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 32 deletions.
45 changes: 26 additions & 19 deletions include/fmt/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -1835,6 +1835,7 @@ class fixed_buffer_traits {

public:
constexpr explicit fixed_buffer_traits(size_t limit) : limit_(limit) {}
FMT_CONSTEXPR20 ~fixed_buffer_traits(){};
constexpr auto count() const -> size_t { return count_; }
FMT_CONSTEXPR auto limit(size_t size) -> size_t {
size_t n = limit_ > count_ ? limit_ - count_ : 0;
Expand All @@ -1855,7 +1856,7 @@ class iterator_buffer : public Traits, public buffer<T> {
if (buf.size() == buffer_size) static_cast<iterator_buffer&>(buf).flush();
}

void flush() {
FMT_CONSTEXPR void flush() {
auto size = this->size();
this->clear();
const T* begin = data_;
Expand All @@ -1864,9 +1865,9 @@ class iterator_buffer : public Traits, public buffer<T> {
}

public:
explicit iterator_buffer(OutputIt out, size_t n = buffer_size)
FMT_CONSTEXPR explicit iterator_buffer(OutputIt out, size_t n = buffer_size)
: Traits(n), buffer<T>(grow, data_, 0, buffer_size), out_(out) {}
iterator_buffer(iterator_buffer&& other) noexcept
FMT_CONSTEXPR iterator_buffer(iterator_buffer&& other) noexcept
: Traits(other),
buffer<T>(grow, data_, 0, buffer_size),
out_(other.out_) {}
Expand All @@ -1876,11 +1877,13 @@ class iterator_buffer : public Traits, public buffer<T> {
FMT_CATCH(...) {}
}

auto out() -> OutputIt {
FMT_CONSTEXPR auto out() -> OutputIt {
flush();
return out_;
}
auto count() const -> size_t { return Traits::count() + this->size(); }
FMT_CONSTEXPR auto count() const -> size_t {
return Traits::count() + this->size();
}
};

template <typename T>
Expand All @@ -1896,7 +1899,7 @@ class iterator_buffer<T*, T, fixed_buffer_traits> : public fixed_buffer_traits,
static_cast<iterator_buffer&>(buf).flush();
}

void flush() {
FMT_CONSTEXPR void flush() {
size_t n = this->limit(this->size());
if (this->data() == out_) {
out_ += n;
Expand All @@ -1906,9 +1909,9 @@ class iterator_buffer<T*, T, fixed_buffer_traits> : public fixed_buffer_traits,
}

public:
explicit iterator_buffer(T* out, size_t n = buffer_size)
FMT_CONSTEXPR explicit iterator_buffer(T* out, size_t n = buffer_size)
: fixed_buffer_traits(n), buffer<T>(grow, out, 0, n), out_(out) {}
iterator_buffer(iterator_buffer&& other) noexcept
FMT_CONSTEXPR iterator_buffer(iterator_buffer&& other) noexcept
: fixed_buffer_traits(other),
buffer<T>(static_cast<iterator_buffer&&>(other)),
out_(other.out_) {
Expand All @@ -1917,13 +1920,13 @@ class iterator_buffer<T*, T, fixed_buffer_traits> : public fixed_buffer_traits,
this->clear();
}
}
~iterator_buffer() { flush(); }
FMT_CONSTEXPR20 ~iterator_buffer() { flush(); }

auto out() -> T* {
FMT_CONSTEXPR auto out() -> T* {
flush();
return out_;
}
auto count() const -> size_t {
FMT_CONSTEXPR auto count() const -> size_t {
return fixed_buffer_traits::count() + this->size();
}
};
Expand Down Expand Up @@ -2077,7 +2080,9 @@ template <typename T, typename Char> struct type_is_unformattable_for;
template <typename Char> struct string_value {
const Char* data;
size_t size;
auto str() const -> basic_string_view<Char> { return {data, size}; }
FMT_CONSTEXPR auto str() const -> basic_string_view<Char> {
return {data, size};
}
};

template <typename Context> struct custom_value {
Expand Down Expand Up @@ -2392,8 +2397,8 @@ FMT_CONSTEXPR inline auto is_locking() -> bool {
return locking<T1>::value || is_locking<T2, Tail...>();
}

FMT_API void vformat_to(buffer<char>& buf, string_view fmt, format_args args,
locale_ref loc = {});
FMT_API FMT_CONSTEXPR void vformat_to(buffer<char>& buf, string_view fmt,
format_args args, locale_ref loc = {});

#if FMT_WIN32
FMT_API void vprint_mojibake(FILE*, string_view, format_args, bool);
Expand Down Expand Up @@ -2803,7 +2808,7 @@ inline auto arg(const Char* name, const T& arg) -> detail::named_arg<Char, T> {
template <typename OutputIt,
FMT_ENABLE_IF(detail::is_output_iterator<remove_cvref_t<OutputIt>,
char>::value)>
auto vformat_to(OutputIt&& out, string_view fmt, format_args args)
FMT_CONSTEXPR auto vformat_to(OutputIt&& out, string_view fmt, format_args args)
-> remove_cvref_t<OutputIt> {
auto&& buf = detail::get_buffer<char>(out);
detail::vformat_to(buf, fmt, args, {});
Expand Down Expand Up @@ -2837,7 +2842,8 @@ template <typename OutputIt> struct format_to_n_result {

template <typename OutputIt, typename... T,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args)
FMT_CONSTEXPR auto vformat_to_n(OutputIt out, size_t n, string_view fmt,
format_args args)
-> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
Expand All @@ -2853,8 +2859,9 @@ auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args)
*/
template <typename OutputIt, typename... T,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string<T...> fmt,
T&&... args) -> format_to_n_result<OutputIt> {
FMT_CONSTEXPR FMT_INLINE auto format_to_n(OutputIt out, size_t n,
format_string<T...> fmt, T&&... args)
-> format_to_n_result<OutputIt> {
return vformat_to_n(out, n, fmt.str, vargs<T...>{{args...}});
}

Expand All @@ -2872,7 +2879,7 @@ struct format_to_result {
};

template <size_t N>
auto vformat_to(char (&out)[N], string_view fmt, format_args args)
FMT_CONSTEXPR auto vformat_to(char (&out)[N], string_view fmt, format_args args)
-> format_to_result {
auto result = vformat_to_n(out, N, fmt, args);
return {result.out, result.size > N};
Expand Down
3 changes: 1 addition & 2 deletions include/fmt/format-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1449,8 +1449,7 @@ FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string {

namespace detail {

FMT_FUNC void vformat_to(buffer<char>& buf, string_view fmt, format_args args,
locale_ref loc) {
FMT_CONSTEXPR FMT_FUNC void vformat_to(buffer<char>& buf, string_view fmt, format_args args, locale_ref loc) {
auto out = appender(buf);
if (fmt.size() == 2 && equal2(fmt.data(), "{}"))
return args.get(0).visit(default_arg_formatter<char>{out});
Expand Down
27 changes: 16 additions & 11 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -1154,11 +1154,12 @@ FMT_END_EXPORT
#endif // FMT_HEADER_ONLY

// Compares two characters for equality.
template <typename Char> auto equal2(const Char* lhs, const char* rhs) -> bool {
template <typename Char>
FMT_CONSTEXPR auto equal2(const Char* lhs, const char* rhs) -> bool {
return lhs[0] == Char(rhs[0]) && lhs[1] == Char(rhs[1]);
}
inline auto equal2(const char* lhs, const char* rhs) -> bool {
return memcmp(lhs, rhs, 2) == 0;
FMT_CONSTEXPR inline auto equal2(const char* lhs, const char* rhs) -> bool {
return lhs[0] == rhs[0] && lhs[1] == rhs[1];
}

// Writes a two-digit value to out.
Expand Down Expand Up @@ -3469,19 +3470,21 @@ template <typename Char> struct default_arg_formatter {

basic_appender<Char> out;

void operator()(monostate) { report_error("argument not found"); }
FMT_CONSTEXPR void operator()(monostate) {
report_error("argument not found");
}

template <typename T, FMT_ENABLE_IF(is_builtin<T>::value)>
void operator()(T value) {
FMT_CONSTEXPR void operator()(T value) {
write<Char>(out, value);
}

template <typename T, FMT_ENABLE_IF(!is_builtin<T>::value)>
void operator()(T) {
FMT_CONSTEXPR void operator()(T) {
FMT_ASSERT(false, "");
}

void operator()(typename basic_format_arg<context>::handle h) {
FMT_CONSTEXPR void operator()(typename basic_format_arg<context>::handle h) {
// Use a null locale since the default format must be unlocalized.
auto parse_ctx = parse_context<Char>({});
auto format_ctx = context(out, {}, {});
Expand Down Expand Up @@ -3590,7 +3593,7 @@ template <typename Char> struct format_handler {
parse_context<Char> parse_ctx;
buffered_context<Char> ctx;

void on_text(const Char* begin, const Char* end) {
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
copy_noinline<Char>(begin, end, ctx.out());
}

Expand All @@ -3606,11 +3609,11 @@ template <typename Char> struct format_handler {
return arg_id;
}

FMT_INLINE void on_replacement_field(int id, const Char*) {
FMT_INLINE FMT_CONSTEXPR void on_replacement_field(int id, const Char*) {
ctx.arg(id).visit(default_arg_formatter<Char>{ctx.out()});
}

auto on_format_specs(int id, const Char* begin, const Char* end)
FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char* end)
-> const Char* {
auto arg = get_arg(ctx, id);
// Not using a visitor for custom types gives better codegen.
Expand All @@ -3629,7 +3632,9 @@ template <typename Char> struct format_handler {
return begin;
}

FMT_NORETURN void on_error(const char* message) { report_error(message); }
FMT_NORETURN FMT_CONSTEXPR void on_error(const char* message) {
report_error(message);
}
};

using format_func = void (*)(detail::buffer<char>&, int, const char*);
Expand Down
15 changes: 15 additions & 0 deletions test/format-impl-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -475,3 +475,18 @@ TEST(format_impl_test, to_utf8) {
EXPECT_EQ(s, u.str());
EXPECT_EQ(s.size(), u.size());
}

#if FMT_USE_CONSTEVAL
TEST(format_test, format_to_n_constexpr) {
// This test doesn't have to be extensive -
// it just checks format_to_n can be done in constexpr context
constexpr bool result = []{
std::array buffer {'x', 'x', 'x', 'x'};
fmt::format_to_n(buffer.data(), buffer.size(), "{}", 42);
fmt::format_to_n(buffer.data() + 2, 1, "{}", 'F');
return buffer == std::array{ '4', '2', 'F', 'x'};
}();

ASSERT_TRUE(result);
}
#endif

0 comments on commit 81b2930

Please sign in to comment.