✨ (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:
parent
b2d2d3920b
commit
0743b2fe44
@ -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)
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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)>
|
||||
|
||||
@ -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. */
|
||||
|
||||
@ -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)>
|
||||
|
||||
@ -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&&
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
192
test/output-range-test.cc
Normal 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
|
||||
@ -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]");
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user