(Non-Appending )Output Ranges in fmt::format_(in)to!

- format_to(some_char_array, ...) will now do the right thing for the most part, and also supports appending to existing ranges (using the same conceptual ideals as as std::ranges::to)
- The API for format_to is a bit complicated since we do appending and also pass/support all the existing iterator-based use cases.
- format_into is a cleaner API that accepts ranges and output iterators, and does not do appending for those ranges that it recognizes (just fills in the `[begin, end)`). This is necessary to avoid strange behaviors on whether or not a container may or may not meet the appending requirements (e.g., more consistent in generic code).
- We lose a lot of compile-time performance with how we're checking things now, so not the best job we could be doing....
- Much of that compile-time performance can be gained back with more rigorous return-style enable_if and such, but that is beyond my work
- Actual performance for the calls themselves are only marginally smaller for pointer-based iterators (fmt is, internally, lacking continguous iterator support. Counterpoint; buffers are the only case that matters! (this is a bit of a lie lmaoo))
This commit is contained in:
ThePhD 2023-12-14 23:14:22 -05:00
parent b2d2d3920b
commit 0743b2fe44
No known key found for this signature in database
GPG Key ID: 1509DB1C0F702BFA
10 changed files with 820 additions and 40 deletions

View File

@ -451,3 +451,6 @@ if (FMT_MASTER_PROJECT AND EXISTS ${gitignore})
set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.md)
include(CPack)
endif ()
add_executable(scratch main.cpp)
target_link_libraries(scratch PRIVATE fmt::fmt)

View File

@ -538,6 +538,80 @@ inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
fmt::make_format_args<buffer_context<Char>>(args...));
}
#if FMT_OUTPUT_RANGES
/**
Formats a string with the given text_style and writes the output to ``out``.
*/
template <typename Output, typename Char,
FMT_ENABLE_IF(std::ranges::output_range<Output, Char> || std::output_iterator<remove_cvref_t<Output>, Char>)>
auto vformat_to(
Output&& out, const text_style& ts, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
auto&& buf = detail::get_appendable_buffer<Char>(std::forward<Output>(out));
detail::vformat_to(buf, ts, format_str, args);
return detail::get_iterator(buf, out);
}
/**
\rst
Formats arguments with the given text_style, writes the result to the output
iterator or range ``out`` and returns the iterator past the end of the output range.
**Example**::
std::vector<char> out;
fmt::format_to(std::back_inserter(out),
fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
\endrst
*/
template <typename Output, typename S, typename... Args,
bool enable = (std::ranges::output_range<Output, char_t<S>>
|| std::output_iterator<remove_cvref_t<Output>, char_t<S>>)
&& detail::is_string<S>::value,
FMT_ENABLE_IF(enable)>
inline auto format_to(Output&& out, const text_style& ts, const S& format_str,
Args&&... args) {
return vformat_to(std::forward<Output>(out), ts, detail::to_string_view(format_str),
fmt::make_format_args<buffer_context<char_t<S>>>(args...));
}
/**
Formats a string with the given text_style and writes the output to ``out``.
*/
template <typename Output, typename Char,
FMT_ENABLE_IF(std::ranges::output_range<Output, Char>
|| std::output_iterator<remove_cvref_t<Output>, Char>)>
auto vformat_into(
Output&& out, const text_style& ts, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
auto&& buf = detail::get_buffer<Char>(std::forward<Output>(out));
detail::vformat_to(buf, ts, format_str, args);
return detail::get_iterator(buf, out);
}
/**
\rst
Formats arguments with the given text_style, writes the result to the output
iterator ``out`` and returns the iterator past the end of the output range.
**Example**::
std::vector<char> out;
fmt::format_to(std::back_inserter(out),
fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
\endrst
*/
template <typename Output, typename S, typename... Args,
bool enable = (std::ranges::output_range<Output, char_t<S>>
|| std::output_iterator<remove_cvref_t<Output>, char_t<S>>)
&& detail::is_string<S>::value,
FMT_ENABLE_IF(enable)>
inline auto format_into(Output&& out, const text_style& ts, const S& format_str,
Args&&... args) {
return vformat_into(std::forward<Output>(out), ts, detail::to_string_view(format_str),
fmt::make_format_args<buffer_context<char_t<S>>>(args...));
}
#else
/**
Formats a string with the given text_style and writes the output to ``out``.
*/
@ -573,6 +647,7 @@ inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
return vformat_to(out, ts, detail::to_string_view(format_str),
fmt::make_format_args<buffer_context<char_t<S>>>(args...));
}
#endif
template <typename T, typename Char>
struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> {

View File

@ -471,6 +471,39 @@ FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
}
}
#if FMT_OUTPUT_RANGES
template <typename Output, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_CONSTEXPR auto format_to(Output&& out, const S&, Args&&... args) {
constexpr auto compiled = detail::compile<Args...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) {
return fmt::format_to(
std::forward<Output>(out),
static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...);
} else {
return fmt::format_to(std::forward<Output>(out),
compiled, std::forward<Args>(args)...);
}
}
template <typename Output, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_CONSTEXPR auto format_into(Output&& out, const S&, Args&&... args) {
constexpr auto compiled = detail::compile<Args...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) {
return fmt::format_into(
std::forward<Output>(out),
static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...);
} else {
return fmt::format_into(std::forward<Output>(out),
compiled, std::forward<Args>(args)...);
}
}
#else
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
@ -485,6 +518,7 @@ FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
}
}
#endif
#endif
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>

View File

@ -16,6 +16,9 @@
#include <memory> // std::addressof
#include <string>
#include <type_traits>
#if defined(__cpp_lib_ranges) && defined(__cpp_if_constexpr)
# include <ranges>
#endif
// The fmt library version in the form major * 10000 + minor * 100 + patch.
#define FMT_VERSION 100102
@ -138,9 +141,10 @@
# endif
#endif
// Only on if we know we can do the begin/end range checking properly
// Only on if we know we can do the begin/end range checking properly,
// that we have `if constexpr`, and that we have a std::ranges::subrange type
#ifndef FMT_OUTPUT_RANGES
# if FMT_RANGE_CHECKS
# if FMT_RANGE_CHECKS && defined(__cpp_if_constexpr) && defined( __cpp_lib_ranges)
# define FMT_OUTPUT_RANGES 1
# else
# define FMT_OUTPUT_RANGES 0
@ -809,8 +813,8 @@ inline auto get_container(std::back_insert_iterator<Container> it)
return *accessor(it).container;
}
template <typename Char, typename InputIt, typename OutputIt>
FMT_CONSTEXPR auto copy_str(InputIt begin, InputIt end, OutputIt out)
template <typename Char, typename InputIt, typename InputSen, typename OutputIt>
FMT_CONSTEXPR auto copy_str(InputIt begin, InputSen end, OutputIt out)
-> OutputIt {
while (begin != end) *out++ = static_cast<Char>(*begin++);
return out;
@ -818,14 +822,44 @@ FMT_CONSTEXPR auto copy_str(InputIt begin, InputIt end, OutputIt out)
template <typename Char, typename T, typename U,
FMT_ENABLE_IF(
std::is_same<remove_const_t<T>, U>::value&& is_char<U>::value)>
std::is_same<remove_const_t<T>, U>::value && is_char<U>::value)>
FMT_CONSTEXPR auto copy_str(T* begin, T* end, U* out) -> U* {
if (is_constant_evaluated()) return copy_str<Char, T*, U*>(begin, end, out);
if (is_constant_evaluated()) return copy_str<Char, T*, T*, U*>(begin, end, out);
auto size = to_unsigned(end - begin);
if (size > 0) memcpy(out, begin, size * sizeof(U));
return out + size;
}
#if FMT_OUTPUT_RANGES
template <typename Char, typename InputIt, typename InputSen, typename OutputIt, typename OutputSen>
FMT_CONSTEXPR auto copy_str(InputIt begin, InputSen end, OutputIt out, OutputSen out_end)
-> std::ranges::subrange<OutputIt, OutputSen> {
if constexpr (std::is_same<OutputSen, std::unreachable_sentinel_t>::value) {
// minor speed optimization
auto out_current = copy_str<Char>(std::move(begin), std::move(end), std::move(out));
return { std::move(out_current), std::move(out_end) };
}
else {
while (begin != end && out != out_end) *out++ = static_cast<Char>(*begin++);
return { std::move(out), std::move(out_end) };
}
}
template <typename Char, typename T, typename U,
FMT_ENABLE_IF(
std::is_same<remove_const_t<T>, U>::value && is_char<U>::value)>
FMT_CONSTEXPR auto copy_str(T* begin, T* end, U* out, U* out_end) -> std::ranges::subrange<U*, U*> {
if (is_constant_evaluated()) return copy_str<Char, T*, U*>(begin, end, out, out_end);
auto size = to_unsigned(end - begin);
auto out_size = to_unsigned(out_end - out);
if (size > out_size) size = out_size;
if (size > 0) memcpy(out, begin, size * sizeof(U));
return { out + size, out_end };
}
#endif
/**
\rst
A contiguous memory buffer with an optional growing ability. It is an internal
@ -970,6 +1004,114 @@ class iterator_buffer final : public Traits, public buffer<T> {
auto count() const -> size_t { return Traits::count() + this->size(); }
};
#if FMT_OUTPUT_RANGES
// A buffer that writes to an output range when flushed.
template <typename Output, typename T, typename Traits = buffer_traits>
class range_buffer final : public Traits, public buffer<T> {
private:
using Subrange_ = std::ranges::subrange<std::ranges::iterator_t<Output>, std::ranges::sentinel_t<Output>>;
Subrange_ out_;
enum { buffer_size = 256 };
T data_[buffer_size];
protected:
FMT_CONSTEXPR20 void grow(size_t) override {
if (this->size() == buffer_size) flush();
}
void flush() {
if (std::ranges::empty(out_))
return;
auto size = this->size();
this->clear();
auto it_end = copy_str<T>(data_, data_ + this->limit(size),
std::ranges::begin(out_), std::ranges::end(out_));
out_ = Subrange_(std::move(it_end).begin(), std::move(it_end).end());
}
public:
template <typename Out>
explicit range_buffer(Out&& out, size_t n = buffer_size)
: Traits(n), buffer<T>(data_, 0, buffer_size), out_(std::forward<Out>(out)) {}
range_buffer(range_buffer&& other)
: Traits(other), buffer<T>(data_, 0, buffer_size), out_(std::move(other.out_)) {}
~range_buffer() { flush(); }
auto out() -> Subrange_ {
flush();
return out_;
}
auto count() const -> size_t { return Traits::count() + this->size(); }
};
template <typename Output, typename T, typename Traits>
class range_buffer<std::back_insert_iterator<Output>, T, Traits> final : public Traits, public buffer<T> {
private:
using Subrange_ = std::ranges::subrange<std::ranges::iterator_t<Output>, std::ranges::sentinel_t<Output>>;
Output& out_;
enum { buffer_size = 256 };
T data_[buffer_size];
protected:
FMT_CONSTEXPR20 void grow(size_t) override {
if (this->size() == buffer_size) flush();
}
void flush() {
auto size = this->size();
this->clear();
copy_str<T>(data_, data_ + this->limit(size), std::back_inserter(out_));
}
public:
explicit range_buffer(Output& out, size_t n = buffer_size)
: Traits(n), buffer<T>(data_, 0, buffer_size), out_(out) {}
range_buffer(range_buffer&& other)
: Traits(other), buffer<T>(data_, 0, buffer_size), out_(std::move(other.out_)) {}
~range_buffer() { flush(); }
auto out() -> Subrange_ {
flush();
return Subrange_(out_.end(), out_.end());
}
auto count() const -> size_t { return Traits::count() + this->size(); }
};
template <typename Output, typename T, typename Traits>
class range_buffer<std::insert_iterator<Output>, T, Traits> final : public Traits, public buffer<T> {
private:
using Subrange_ = std::ranges::subrange<std::ranges::iterator_t<Output>, std::ranges::sentinel_t<Output>>;
Output& out_;
enum { buffer_size = 256 };
T data_[buffer_size];
protected:
FMT_CONSTEXPR20 void grow(size_t) override {
if (this->size() == buffer_size) flush();
}
void flush() {
auto size = this->size();
this->clear();
copy_str<T>(data_, data_ + this->limit(size), std::inserter(out_, out_.end()));
}
public:
explicit range_buffer(Output& out, size_t n = buffer_size)
: Traits(n), buffer<T>(data_, 0, buffer_size), out_(out) {}
range_buffer(range_buffer&& other)
: Traits(other), buffer<T>(data_, 0, buffer_size), out_(std::move(other.out_)) {}
~range_buffer() { flush(); }
auto out() -> Subrange_ {
flush();
return Subrange_(out_.end(), out_.end());
}
auto count() const -> size_t { return Traits::count() + this->size(); }
};
#endif
// A buffer that writes to an output iterator when flushed.
template <typename T>
class iterator_buffer<T*, T, fixed_buffer_traits> final
: public fixed_buffer_traits,
@ -1149,26 +1291,6 @@ template <typename T>
using buffer_appender = conditional_t<std::is_same<T, char>::value, appender,
std::back_insert_iterator<buffer<T>>>;
// Maps an output iterator to a buffer.
template <typename T, typename OutputIt>
auto get_buffer(OutputIt out) -> iterator_buffer<OutputIt, T> {
return iterator_buffer<OutputIt, T>(out);
}
template <typename T, typename Buf,
FMT_ENABLE_IF(std::is_base_of<buffer<char>, Buf>::value)>
auto get_buffer(std::back_insert_iterator<Buf> out) -> buffer<char>& {
return get_container(out);
}
template <typename Buf, typename OutputIt>
FMT_INLINE auto get_iterator(Buf& buf, OutputIt) -> decltype(buf.out()) {
return buf.out();
}
template <typename T, typename OutputIt>
auto get_iterator(buffer<T>&, OutputIt out) -> OutputIt {
return out;
}
struct view {};
template <typename Char, typename T> struct named_arg : view {
@ -1643,6 +1765,95 @@ template <typename Container>
struct is_back_insert_iterator<std::back_insert_iterator<Container>>
: std::true_type {};
#if FMT_OUTPUT_RANGES
template <typename Container, typename T, typename Enable = void>
struct is_container_push_backable : std::false_type {};
template <typename Container, typename T>
struct is_container_push_backable<
Container, T,
void_t<decltype(std::declval<Container>().push_back(std::declval<T>()))>
> : std::true_type {};
template <typename Container, typename T, typename Enable = void>
struct is_container_insertable : std::false_type {};
template <typename Container, typename T>
struct is_container_insertable<
Container, T,
void_t<decltype(std::declval<Container>().insert(
std::declval<std::ranges::iterator_t<Container>>(),
std::declval<T>()
))>
> : std::integral_constant<bool,
std::is_lvalue_reference<Container>::value
> {};
#endif
// Maps an output iterator to a buffer.
#if FMT_OUTPUT_RANGES
template <typename T, typename Output>
decltype(auto) get_buffer(Output&& out) {
if constexpr (std::ranges::output_range<Output, T&>) {
return range_buffer<remove_cvref_t<Output>, T>(std::forward<Output>(out));
}
else if constexpr (is_back_insert_iterator<remove_cvref_t<Output>>::value) {
using Container_ = typename remove_cvref_t<Output>::container_type;
if constexpr (std::is_base_of<buffer<char>, Container_>::value) {
return get_container(out);
}
else {
return iterator_buffer<remove_cvref_t<Output>, T>(std::forward<Output>(out));
}
}
else {
return iterator_buffer<remove_cvref_t<Output>, T>(std::forward<Output>(out));
}
}
template <typename T, typename Output>
decltype(auto) get_appendable_buffer(Output&& out) {
if constexpr (std::ranges::output_range<Output, T&>) {
if constexpr (is_container_insertable<Output, T>::value) {
// appendable container: give a back_inserter-based buffer
if constexpr (is_container_push_backable<Output, T>::value) {
using Iterator_ = std::back_insert_iterator<remove_cvref_t<Output>>;
return range_buffer<Iterator_, T>(std::forward<Output>(out));
}
else {
using Iterator_ = std::insert_iterator<remove_cvref_t<Output>>;
return range_buffer<Iterator_, T>(std::forward<Output>(out));
}
}
else {
return range_buffer<remove_cvref_t<Output>, T>(std::forward<Output>(out));
}
}
else {
return get_buffer<T>(std::forward<Output>(out));
}
}
#else
template <typename T, typename OutputIt>
auto get_buffer(OutputIt out) -> iterator_buffer<OutputIt, T> {
return iterator_buffer<OutputIt, T>(out);
}
template <typename T, typename Buf,
FMT_ENABLE_IF(std::is_base_of<buffer<char>, Buf>::value)>
auto get_buffer(std::back_insert_iterator<Buf> out) -> buffer<char>& {
return get_container(out);
}
#endif
template <typename Buf, typename Output>
FMT_INLINE auto get_iterator(Buf& buf, Output&&) -> decltype(buf.out()) {
return buf.out();
}
template <typename T, typename Output>
auto get_iterator(buffer<T>&, Output&& out) {
return out;
}
// A type-erased reference to an std::locale to avoid a heavy <locale> include.
class locale_ref {
private:
@ -1716,14 +1927,38 @@ FMT_CONSTEXPR inline auto make_arg(T& val) -> basic_format_arg<Context> {
template <typename Out, bool IsRange = is_range_<Out>::value>
struct out_storage {
using iterator = remove_cvref_t<Out>;
using range = void;
Out out_;
template <typename Value,
FMT_ENABLE_IF(!std::is_same<remove_cvref_t<Value>, out_storage>::value)>
FMT_CONSTEXPR out_storage(Value&& value) : out_(std::forward<Value>(value)) {}
out_storage(out_storage&&) = default;
out_storage(const out_storage&) = default;
out_storage& operator=(out_storage&&) = default;
out_storage& operator=(const out_storage&) = default;
};
#if FMT_OUTPUT_RANGES
template <typename OutRange>
struct out_storage<OutRange, true> {
decltype(range_begin(std::declval<OutRange>())) out_;
decltype(range_end(std::declval<OutRange>())) out_last_;
using iterator = std::ranges::iterator_t<OutRange>;
using sentinel = std::ranges::sentinel_t<OutRange>;
using range = std::ranges::subrange<iterator, sentinel>;
range out_;
template <typename Value,
FMT_ENABLE_IF(!std::is_same<remove_cvref_t<Value>, out_storage>::value)>
FMT_CONSTEXPR out_storage(Value&& value) : out_(std::forward<Value>(value)) {}
out_storage(out_storage&&) = default;
out_storage(const out_storage&) = default;
out_storage& operator=(out_storage&&) = default;
out_storage& operator=(const out_storage&) = default;
};
#endif
} // namespace detail
FMT_BEGIN_EXPORT
@ -1834,12 +2069,18 @@ FMT_CONSTEXPR FMT_INLINE auto visit_format_arg(
// Formatting context.
template <typename OutputIt, typename Char> class basic_format_context {
private:
OutputIt out_;
using Storage_ = detail::out_storage<OutputIt>;
Storage_ out_storage_;
basic_format_args<basic_format_context> args_;
detail::locale_ref loc_;
using ItOrRange_ = conditional_t<
std::is_void<typename Storage_::range>::value,
typename Storage_::iterator,
typename Storage_::range>;
public:
using iterator = OutputIt;
using iterator = typename Storage_::iterator;
using range = typename Storage_::range;
using format_arg = basic_format_arg<basic_format_context>;
using format_args = basic_format_args<basic_format_context>;
using parse_context_type = basic_format_parse_context<Char>;
@ -1857,7 +2098,7 @@ template <typename OutputIt, typename Char> class basic_format_context {
*/
constexpr basic_format_context(OutputIt out, format_args ctx_args,
detail::locale_ref loc = {})
: out_(out), args_(ctx_args), loc_(loc) {}
: out_storage_(out), args_(ctx_args), loc_(loc) {}
constexpr auto arg(int id) const -> format_arg { return args_.get(id); }
FMT_CONSTEXPR auto arg(basic_string_view<Char> name) -> format_arg {
@ -1871,12 +2112,17 @@ template <typename OutputIt, typename Char> class basic_format_context {
FMT_CONSTEXPR auto error_handler() -> detail::error_handler { return {}; }
void on_error(const char* message) { error_handler().on_error(message); }
// Returns an iterator to the beginning of the output range.
FMT_CONSTEXPR auto out() -> iterator { return out_; }
// Returns an iterator to the beginning of the
// output range, or the whole output range.
FMT_CONSTEXPR auto out() -> ItOrRange_ { return out_storage_.out_; }
// Advances the begin iterator to ``it``.
void advance_to(iterator it) {
if (!detail::is_back_insert_iterator<iterator>()) out_ = it;
void advance_to(ItOrRange_ it_or_range) {
#if FMT_OUTPUT_RANGES
if (!detail::is_back_insert_iterator<iterator>()) out_storage_.out_ = std::move(it_or_range);
#else
if (!detail::is_back_insert_iterator<iterator>()) out_storage_.out_ = it_or_range;
#endif
}
FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; }
@ -2930,6 +3176,30 @@ FMT_NODISCARD FMT_INLINE auto format(format_string<T...> fmt, T&&... args)
return vformat(fmt, fmt::make_format_args(args...));
}
#if FMT_OUTPUT_RANGES
/** Formats a string and writes the output to ``out``, appending
rather than directly writing if it is a typical insertable
contianer type. */
template <typename Output,
FMT_ENABLE_IF(std::ranges::output_range<Output, char>
|| std::output_iterator<remove_cvref_t<Output>, char>)>
auto vformat_to(Output&& out, string_view fmt, format_args args) {
auto&& buf = detail::get_appendable_buffer<char>(std::forward<Output>(out));
detail::vformat_to(buf, fmt, args, {});
return detail::get_iterator(buf, out);
}
/** Formats a string and writes the output to ``out``, writing
directly into the range [begin, end). */
template <typename Output,
FMT_ENABLE_IF(std::ranges::output_range<Output, char>
|| std::output_iterator<remove_cvref_t<Output>, char>)>
auto vformat_into(Output&& out, string_view fmt, format_args args) {
auto&& buf = detail::get_buffer<char>(std::forward<Output>(out));
detail::vformat_to(buf, fmt, args, {});
return detail::get_iterator(buf, out);
}
#else
/** Formats a string and writes the output to ``out``. */
template <typename OutputIt,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
@ -2938,7 +3208,54 @@ auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt {
detail::vformat_to(buf, fmt, args, {});
return detail::get_iterator(buf, out);
}
#endif
#if FMT_OUTPUT_RANGES
/**
\rst
Formats ``args`` according to specifications in ``fmt``, writes the result to
the output range or iterator ``out`` and returns the subrange past the end of
the output range. `format_to` will write to the end of a container (if it is an
insertable container). `format_to` does not append a terminating null character.
**Example**::
auto out = std::vector<char>();
fmt::format_to(out, "{}", 42);
**Example**::
auto out = std::vector<char>();
fmt::format_to(std::back_inserter(out), "{}", 42);
\endrst
*/
template <typename Output, typename... T,
FMT_ENABLE_IF(std::ranges::output_range<Output, char>
|| std::output_iterator<remove_cvref_t<Output>, char>)>
FMT_INLINE auto format_to(Output&& out, format_string<T...> fmt, T&&... args) {
return vformat_to(std::forward<Output>(out), fmt, fmt::make_format_args(args...));
}
/**
\rst
Formats ``args`` according to specifications in ``fmt``, writes the result into
the output range or iterator ``out`` and returns the subrange past the end of
the output range. `format_into` does not append into a contianer and only writes
into it. `format_into` does not append or write a terminating null character.
**Example**::
auto out = std::vector<char>(2, '\0');
fmt::format_into(out, "{}", 42);
\endrst
*/
template <typename Output, typename... T,
FMT_ENABLE_IF(std::ranges::output_range<Output, char>
|| std::output_iterator<remove_cvref_t<Output>, char>)>
FMT_INLINE auto format_into(Output&& out, format_string<T...> fmt, T&&... args) {
return vformat_into(std::forward<Output>(out), fmt, fmt::make_format_args(args...));
}
#else
/**
\rst
Formats ``args`` according to specifications in ``fmt``, writes the result to
@ -2957,6 +3274,7 @@ FMT_INLINE auto format_to(OutputIt out, format_string<T...> fmt, T&&... args)
-> OutputIt {
return vformat_to(out, fmt, fmt::make_format_args(args...));
}
#endif
template <typename OutputIt> struct format_to_n_result {
/** Iterator past the end of the output range. */

View File

@ -2326,7 +2326,7 @@ class counting_iterator {
FMT_UNCHECKED_ITERATOR(counting_iterator);
struct value_type {
template <typename T> FMT_CONSTEXPR void operator=(const T&) {}
template <typename T> FMT_CONSTEXPR void operator=(const T&) const {}
};
FMT_CONSTEXPR counting_iterator() : count_(0) {}
@ -4541,6 +4541,51 @@ inline auto format(const Locale& loc, format_string<T...> fmt, T&&... args)
return fmt::vformat(loc, string_view(fmt), fmt::make_format_args(args...));
}
#if FMT_OUTPUT_RANGES
template <typename Output, typename Locale,
FMT_ENABLE_IF(
(std::ranges::output_range<Output, char>
|| std::output_iterator<Output, char>)
&& detail::is_locale<Locale>::value)>
auto vformat_to(Output&& out, const Locale& loc, string_view fmt,
format_args args) {
auto&& buf = detail::get_appendable_buffer<char>(std::forward<Output>(out));
detail::vformat_to(buf, fmt, args, detail::locale_ref(loc));
return detail::get_iterator(buf, out);
}
template <typename Output, typename Locale,
FMT_ENABLE_IF(
(std::ranges::output_range<Output, char>
|| std::output_iterator<Output, char>)
&& detail::is_locale<Locale>::value)>
auto vformat_into(Output&& out, const Locale& loc, string_view fmt,
format_args args) {
auto&& buf = detail::get_buffer<char>(std::forward<Output>(out));
detail::vformat_to(buf, fmt, args, detail::locale_ref(loc));
return detail::get_iterator(buf, out);
}
template <typename Output, typename Locale, typename... T,
FMT_ENABLE_IF(
(std::ranges::output_range<Output, char>
|| std::output_iterator<remove_cvref_t<Output>, char>)
&& detail::is_locale<Locale>::value)>
FMT_INLINE auto format_to(Output&& out, const Locale& loc,
format_string<T...> fmt, T&&... args) {
return vformat_to(std::forward<Output>(out), loc, fmt, fmt::make_format_args(args...));
}
template <typename Output, typename Locale, typename... T,
FMT_ENABLE_IF(
(std::ranges::output_range<Output, char>
|| std::output_iterator<remove_cvref_t<Output>, char>)
&& detail::is_locale<Locale>::value)>
FMT_INLINE auto format_into(Output&& out, const Locale& loc,
format_string<T...> fmt, T&&... args) {
return vformat_into(std::forward<Output>(out), loc, fmt, fmt::make_format_args(args...));
}
#else
template <typename OutputIt, typename Locale,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value&&
detail::is_locale<Locale>::value)>
@ -4559,6 +4604,7 @@ FMT_INLINE auto format_to(OutputIt out, const Locale& loc,
format_string<T...> fmt, T&&... args) -> OutputIt {
return vformat_to(out, loc, fmt, fmt::make_format_args(args...));
}
#endif
template <typename Locale, typename... T,
FMT_ENABLE_IF(detail::is_locale<Locale>::value)>

View File

@ -138,6 +138,109 @@ inline auto format(const Locale& loc, const S& format_str, T&&... args)
fmt::make_format_args<buffer_context<Char>>(args...));
}
#if FMT_OUTPUT_RANGES
template <typename Output, typename S, typename Char = char_t<S>,
FMT_ENABLE_IF((std::ranges::output_range<Output, Char>
|| std::output_iterator<remove_cvref_t<Output>, Char>)
&& detail::is_exotic_char<Char>::value)>
auto vformat_to(Output&& out, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
{
auto&& buf = detail::get_appendable_buffer<Char>(std::forward<Output>(out));
detail::vformat_to(buf, detail::to_string_view(format_str), args);
return detail::get_iterator(buf, out);
}
template <typename Output, typename S, typename Char = char_t<S>,
FMT_ENABLE_IF((std::ranges::output_range<Output, Char>
|| std::output_iterator<remove_cvref_t<Output>, Char>)
&& detail::is_exotic_char<Char>::value)>
auto vformat_into(Output&& out, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
{
auto&& buf = detail::get_buffer<Char>(std::forward<Output>(out));
detail::vformat_to(buf, detail::to_string_view(format_str), args);
return detail::get_iterator(buf, out);
}
template <typename Output, typename S, typename... T,
typename Char = char_t<S>,
FMT_ENABLE_IF((std::ranges::output_range<Output, Char>
|| std::output_iterator<remove_cvref_t<Output>, Char>)
&& detail::is_exotic_char<Char>::value)>
inline auto format_to(Output&& out, const S& fmt, T&&... args) {
return vformat_to(std::forward<Output>(out), detail::to_string_view(fmt),
fmt::make_format_args<buffer_context<Char>>(args...));
}
template <typename Output, typename S, typename... T,
typename Char = char_t<S>,
FMT_ENABLE_IF((std::ranges::output_range<Output, Char>
|| std::output_iterator<remove_cvref_t<Output>, Char>)
&& detail::is_exotic_char<Char>::value)>
inline auto format_into(Output&& out, const S& fmt, T&&... args) {
return vformat_into(std::forward<Output>(out), detail::to_string_view(fmt),
fmt::make_format_args<buffer_context<Char>>(args...));
}
template <typename Locale, typename S, typename Output, typename... Args,
typename Char = char_t<S>,
FMT_ENABLE_IF((std::ranges::output_range<Output, Char>
|| std::output_iterator<remove_cvref_t<Output>, Char>)
&& detail::is_locale<Locale>::value
&& detail::is_exotic_char<Char>::value)>
inline auto vformat_to(
Output&& out, const Locale& loc, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
auto&& buf = detail::get_appendable_buffer<Char>(std::forward<Output>(out));
vformat_to(buf, detail::to_string_view(format_str), args,
detail::locale_ref(loc));
return detail::get_iterator(buf, out);
}
template <typename Locale, typename S, typename Output, typename... Args,
typename Char = char_t<S>,
FMT_ENABLE_IF((std::ranges::output_range<Output, Char>
|| std::output_iterator<remove_cvref_t<Output>, Char>)
&& detail::is_locale<Locale>::value
&& detail::is_exotic_char<Char>::value)>
inline auto vformat_into(
Output&& out, const Locale& loc, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
auto&& buf = detail::get_buffer<Char>(std::forward<Output>(out));
vformat_to(buf, detail::to_string_view(format_str), args,
detail::locale_ref(loc));
return detail::get_iterator(buf, out);
}
template <
typename Output, typename Locale, typename S, typename... T,
typename Char = char_t<S>,
bool enable = (std::ranges::output_range<Output, Char>
|| std::output_iterator<remove_cvref_t<Output>, Char>)
&& detail::is_locale<Locale>::value
&& detail::is_exotic_char<Char>::value,
FMT_ENABLE_IF(enable)>
inline auto format_to(Output&& out, const Locale& loc, const S& format_str,
T&&... args) {
return vformat_to(std::forward<Output>(out), loc, detail::to_string_view(format_str),
fmt::make_format_args<buffer_context<Char>>(args...));
}
template <
typename Output, typename Locale, typename S, typename... T,
typename Char = char_t<S>,
bool enable = (std::ranges::output_range<Output, Char>
|| std::output_iterator<remove_cvref_t<Output>, Char>)
&& detail::is_locale<Locale>::value
&& detail::is_exotic_char<Char>::value,
FMT_ENABLE_IF(enable)>
inline auto format_into(Output&& out, const Locale& loc, const S& format_str,
T&&... args) {
return vformat_into(std::forward<Output>(out), loc, detail::to_string_view(format_str),
fmt::make_format_args<buffer_context<Char>>(args...));
}
#else
template <typename OutputIt, typename S, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
@ -183,6 +286,7 @@ inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
return vformat_to(out, loc, detail::to_string_view(format_str),
fmt::make_format_args<buffer_context<Char>>(args...));
}
#endif
template <typename OutputIt, typename Char, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&

View File

@ -73,6 +73,14 @@ if (NOT (MSVC AND BUILD_SHARED_LIBS))
add_fmt_test(format-impl-test HEADER_ONLY header-only-test.cc)
endif ()
add_fmt_test(ostream-test)
if (CMAKE_CXX_STANDARD GREATER_EQUAL 20)
add_fmt_test(output-range-test)
target_compile_features(output-range-test PRIVATE cxx_std_20)
if (MSVC)
# Without this option, MSVC returns 199711L for the __cplusplus macro.
target_compile_options(output-range-test PRIVATE /Zc:__cplusplus)
endif()
endif()
add_fmt_test(compile-test)
add_fmt_test(compile-fp-test HEADER_ONLY)
if (MSVC)

View File

@ -1696,8 +1696,8 @@ TEST(format_test, format_custom) {
TEST(format_test, format_to_custom) {
char buf[10] = {};
auto end = fmt::format_to(buf, "{}", Answer());
EXPECT_EQ(end, buf + 2);
auto result_range = fmt::format_to(buf, "{}", Answer());
EXPECT_EQ(result_range.begin(), buf + 2);
EXPECT_STREQ(buf, "42");
}

192
test/output-range-test.cc Normal file
View File

@ -0,0 +1,192 @@
// Formatting library for C++ - std::ostream support tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include "fmt/format.h"
#if FMT_OUTPUT_RANGES
#include "gmock/gmock.h"
#include "gtest-extra.h"
#include "util.h"
#include <vector>
#include <list>
#include <deque>
#include <array>
#include <span>
TEST(output_range_c_array_char_test, format_into) {
char buffer[4];
buffer[3] = 'x';
auto result = fmt::format_into(buffer, "{}", "abc");
EXPECT_EQ(buffer + 3, result.begin());
EXPECT_EQ("abc", fmt::string_view(buffer, 3));
EXPECT_EQ("abcx", fmt::string_view(buffer, 4));
result = fmt::format_into(buffer, "x{}y", "abc");
EXPECT_EQ(buffer + 4, result.begin());
EXPECT_EQ("xabc", fmt::string_view(buffer, 4));
}
TEST(output_range_array_char_test, format_into) {
std::array<char, 4> buffer;
buffer[3] = 'x';
auto result = fmt::format_into(buffer, "{}", "abc");
EXPECT_EQ(buffer.begin() + 3, result.begin());
EXPECT_EQ("abc", fmt::string_view(buffer.data(), 3));
EXPECT_EQ("abcx", fmt::string_view(buffer.data(), 4));
result = fmt::format_into(buffer, "x{}y", "abc");
EXPECT_EQ(buffer.begin() + 4, result.begin());
EXPECT_EQ("xabc", fmt::string_view(buffer.data(), 4));
}
TEST(output_range_span_array_char_test, format_into) {
std::array<char, 4> storage;
std::span<char> buffer(storage);
buffer[3] = 'x';
auto result = fmt::format_into(buffer, "{}", "abc");
EXPECT_EQ(buffer.begin() + 3, result.begin());
EXPECT_EQ("abc", fmt::string_view(buffer.data(), 3));
EXPECT_EQ("abcx", fmt::string_view(buffer.data(), 4));
result = fmt::format_into(buffer, "x{}y", "abc");
EXPECT_EQ(buffer.begin() + 4, result.begin());
EXPECT_EQ("xabc", fmt::string_view(buffer.data(), 4));
}
TEST(output_range_vector_char_test, format_into) {
std::vector<char> buffer(4, '\0');
buffer[3] = 'x';
auto result = fmt::format_into(buffer, "{}", "abc");
EXPECT_EQ(buffer.begin() + 3, result.begin());
EXPECT_EQ("abc", fmt::string_view(buffer.data(), 3));
EXPECT_EQ("abcx", fmt::string_view(buffer.data(), 4));
result = fmt::format_into(buffer, "x{}y", "abc");
EXPECT_EQ(buffer.begin() + 4, result.begin());
EXPECT_EQ("xabc", fmt::string_view(buffer.data(), 4));
}
TEST(output_range_list_char_test, format_into) {
std::list<char> buffer(4, '\0');
using subrng = std::ranges::subrange<typename decltype(buffer)::iterator>;
auto fourth_it = std::ranges::next(buffer.begin(), 3);
auto end = buffer.end();
subrng first3_subrange(buffer.begin(), fourth_it);
*fourth_it = 'x';
auto result = fmt::format_into(buffer, "{}", "abc");
EXPECT_EQ(fourth_it, result.begin());
EXPECT_TRUE(std::ranges::equal(fmt::string_view("abc"), first3_subrange));
EXPECT_TRUE(std::ranges::equal(fmt::string_view("abcx"), buffer));
result = fmt::format_into(buffer, "x{}y", "abc");
EXPECT_EQ(end, result.begin());
EXPECT_TRUE(std::ranges::equal(fmt::string_view("xabc"), buffer));
}
TEST(output_range_deque_char_test, format_into) {
std::deque<char> buffer(4, '\0');
using subrng = std::ranges::subrange<typename decltype(buffer)::iterator>;
auto fourth_it = std::ranges::next(buffer.begin(), 3);
auto end = buffer.end();
subrng first3_subrange(buffer.begin(), fourth_it);
*fourth_it = 'x';
auto result = fmt::format_into(buffer, "{}", "abc");
EXPECT_EQ(fourth_it, result.begin());
EXPECT_TRUE(std::ranges::equal(fmt::string_view("abc"), first3_subrange));
EXPECT_TRUE(std::ranges::equal(fmt::string_view("abcx"), buffer));
result = fmt::format_into(buffer, "x{}y", "abc");
EXPECT_EQ(end, result.begin());
EXPECT_TRUE(std::ranges::equal(fmt::string_view("xabc"), buffer));
}
TEST(output_range_c_array_char_test, format_to) {
char buffer[4];
buffer[3] = 'x';
auto result = fmt::format_to(buffer, "{}", "abc");
EXPECT_EQ(buffer + 3, result.begin());
EXPECT_EQ("abc", fmt::string_view(buffer, 3));
EXPECT_EQ("abcx", fmt::string_view(buffer, 4));
result = fmt::format_to(buffer, "x{}y", "abc");
EXPECT_EQ(buffer + 4, result.begin());
EXPECT_EQ("xabc", fmt::string_view(buffer, 4));
}
TEST(output_range_array_char_test, format_to) {
std::array<char, 4> buffer;
buffer[3] = 'x';
auto result = fmt::format_to(buffer, "{}", "abc");
EXPECT_EQ(buffer.begin() + 3, result.begin());
EXPECT_EQ("abc", fmt::string_view(buffer.data(), 3));
EXPECT_EQ("abcx", fmt::string_view(buffer.data(), 4));
result = fmt::format_to(buffer, "x{}y", "abc");
EXPECT_EQ(buffer.begin() + 4, result.begin());
EXPECT_EQ("xabc", fmt::string_view(buffer.data(), 4));
}
TEST(output_range_span_array_char_test, format_to) {
std::array<char, 4> storage;
std::span<char> buffer(storage);
buffer[3] = 'x';
auto result = fmt::format_to(buffer, "{}", "abc");
EXPECT_EQ(buffer.begin() + 3, result.begin());
EXPECT_EQ("abc", fmt::string_view(buffer.data(), 3));
EXPECT_EQ("abcx", fmt::string_view(buffer.data(), 4));
result = fmt::format_to(buffer, "x{}y", "abc");
EXPECT_EQ(buffer.begin() + 4, result.begin());
EXPECT_EQ("xabc", fmt::string_view(buffer.data(), 4));
}
TEST(output_range_vector_char_test, format_to) {
std::vector<char> buffer{};
auto result = fmt::format_to(buffer, "{}", "abc");
EXPECT_EQ(buffer.begin() + 3, result.begin());
EXPECT_EQ("abc", fmt::string_view(buffer.data(), 3));
result = fmt::format_to(buffer, "x{}y", "abc");
EXPECT_EQ(buffer.begin() + 8, result.begin());
EXPECT_EQ(buffer.begin() + buffer.size(), result.begin());
EXPECT_EQ(buffer.end(), result.begin());
EXPECT_EQ("abcxabcy", fmt::string_view(buffer.data(), 8));
}
TEST(output_range_basic_string_char_test, format_to) {
std::string buffer{};
auto result = fmt::format_to(buffer, "{}", "abc");
EXPECT_EQ(buffer.begin() + 3, result.begin());
EXPECT_EQ("abc", buffer);
result = fmt::format_to(buffer, "x{}y", "abc");
EXPECT_EQ(buffer.begin() + 8, result.begin());
EXPECT_EQ(buffer.begin() + buffer.size(), result.begin());
EXPECT_EQ(buffer.end(), result.begin());
EXPECT_EQ("abcxabcy", buffer);
}
TEST(output_range_list_char_test, format_to) {
std::list<char> buffer{};
auto result = fmt::format_to(buffer, "{}", "abc");
EXPECT_EQ(buffer.end(), result.begin());
EXPECT_EQ(std::ranges::next(buffer.begin(), 3), result.begin());
EXPECT_EQ(std::ranges::next(buffer.begin(), buffer.size()), result.begin());
EXPECT_TRUE(std::ranges::equal(fmt::string_view("abc"), buffer));
result = fmt::format_to(buffer, "x{}y", "abc");
EXPECT_EQ(buffer.end(), result.begin());
EXPECT_EQ(std::ranges::next(buffer.begin(), 8), result.begin());
EXPECT_EQ(std::ranges::next(buffer.begin(), buffer.size()), result.begin());
EXPECT_TRUE(std::ranges::equal(fmt::string_view("abcxabcy"), buffer));
}
TEST(output_range_deque_char_test, format_to) {
std::deque<char> buffer{};
auto result = fmt::format_to(buffer, "{}", "abc");
EXPECT_EQ(buffer.end(), result.begin());
EXPECT_EQ(std::ranges::next(buffer.begin(), 3), result.begin());
EXPECT_EQ(std::ranges::next(buffer.begin(), buffer.size()), result.begin());
EXPECT_TRUE(std::ranges::equal(fmt::string_view("abc"), buffer));
result = fmt::format_to(buffer, "x{}y", "abc");
EXPECT_EQ(buffer.end(), result.begin());
EXPECT_EQ(std::ranges::next(buffer.begin(), 8), result.begin());
EXPECT_EQ(std::ranges::next(buffer.begin(), buffer.size()), result.begin());
EXPECT_TRUE(std::ranges::equal(fmt::string_view("abcxabcy"), buffer));
}
#endif

View File

@ -205,8 +205,8 @@ TEST(ranges_test, format_struct) {
TEST(ranges_test, format_to) {
char buf[10];
auto end = fmt::format_to(buf, "{}", std::vector<int>{1, 2, 3});
*end = '\0';
auto result_range = fmt::format_to(buf, "{}", std::vector<int>{1, 2, 3});
*result_range.begin() = '\0';
EXPECT_STREQ(buf, "[1, 2, 3]");
}