Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow format_to_n to be executed at compile time #4365

Closed
wants to merge 3 commits into from
Closed
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
47 changes: 27 additions & 20 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() = default;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why FMT_CONSTEXPR20 and not FMT_CONSTEXPR?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that destructors cannot be marked constexpr before C++ 20

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,24 +1909,24 @@ 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
: fixed_buffer_traits(other),
FMT_CONSTEXPR iterator_buffer(iterator_buffer&& other) noexcept
: fixed_buffer_traits(static_cast<iterator_buffer&&>(other)),
buffer<T>(static_cast<iterator_buffer&&>(other)),
out_(other.out_) {
if (this->data() != out_) {
this->set(data_, buffer_size);
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be constexpr.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NVM, I thought you are trying to make a compiled version of format_to_n constexpr.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this forward declaration confused me at first and I thought an addition macro will be necessary (FMT_CONSTEXPR_IF_HEADERONLY 😄 ) but it is defined in the same header.

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
4 changes: 2 additions & 2 deletions include/fmt/format-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1449,8 +1449,8 @@ 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should go to compile-test.

// 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'};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Windows compilation is failing because it needs #include <array> for this.

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
Loading