Type erase output iterators

This commit is contained in:
Victor Zverovich 2020-07-18 06:44:44 -07:00
parent 9d3cd0afb2
commit eb90da2e82
2 changed files with 139 additions and 145 deletions

View File

@ -616,8 +616,24 @@ template <typename T, typename Context>
using has_formatter =
std::is_constructible<typename Context::template formatter_type<T>>;
// Checks whether T is a container with contiguous storage.
template <typename T> struct is_contiguous : std::false_type {};
template <typename Char>
struct is_contiguous<std::basic_string<Char>> : std::true_type {};
namespace detail {
// Extracts a reference to the container from back_insert_iterator.
template <typename Container>
inline Container& get_container(std::back_insert_iterator<Container> it) {
using bi_iterator = std::back_insert_iterator<Container>;
struct accessor : bi_iterator {
accessor(bi_iterator iter) : bi_iterator(iter) {}
using bi_iterator::container;
};
return *accessor(it).container;
}
/**
\rst
A contiguous memory buffer with an optional growing ability. It is an internal
@ -708,23 +724,6 @@ template <typename T> class buffer {
}
};
// A container-backed buffer.
template <typename Container>
class container_buffer : public buffer<typename Container::value_type> {
private:
Container& container_;
protected:
void grow(size_t capacity) FMT_OVERRIDE {
container_.resize(capacity);
this->set(&container_[0], capacity);
}
public:
explicit container_buffer(Container& c)
: buffer<typename Container::value_type>(c.size()), container_(c) {}
};
// A buffer that writes to an output iterator when flushed.
template <typename OutputIt, typename T>
class iterator_buffer : public buffer<T> {
@ -738,18 +737,20 @@ class iterator_buffer : public buffer<T> {
void grow(size_t) final {
if (this->size() == buffer_size) flush();
}
void flush();
public:
explicit iterator_buffer(OutputIt out)
: buffer<T>(data_, 0, buffer_size), out_(out) {}
: buffer<T>(data_, 0, buffer_size), out_(out) {}
~iterator_buffer() { flush(); }
OutputIt out() { return out_; }
void flush();
OutputIt out() {
flush();
return out_;
}
};
template <typename T>
class iterator_buffer<T*, T> : public buffer<T> {
template <typename T> class iterator_buffer<T*, T> : public buffer<T> {
protected:
void grow(size_t) final {}
@ -757,9 +758,37 @@ class iterator_buffer<T*, T> : public buffer<T> {
explicit iterator_buffer(T* out) : buffer<T>(out, 0, ~size_t()) {}
T* out() { return &*this->end(); }
void flush() {}
};
// A buffer that writes to a container with the contiguous storage.
template <typename Container>
class iterator_buffer<std::back_insert_iterator<Container>,
enable_if_t<is_contiguous<Container>::value,
typename Container::value_type>>
: public buffer<typename Container::value_type> {
private:
Container& container_;
protected:
void grow(size_t capacity) FMT_OVERRIDE {
container_.resize(capacity);
this->set(&container_[0], capacity);
}
public:
explicit iterator_buffer(Container& c)
: buffer<typename Container::value_type>(c.size()), container_(c) {}
explicit iterator_buffer(std::back_insert_iterator<Container> out)
: iterator_buffer(get_container(out)) {}
std::back_insert_iterator<Container> out() {
return std::back_inserter(container_);
}
};
template <typename Container>
using container_buffer = iterator_buffer<std::back_insert_iterator<Container>,
typename Container::value_type>;
// An output iterator that appends to the buffer.
// It is used to reduce symbol sizes for the common case.
template <typename T>
@ -771,15 +800,24 @@ class buffer_appender : public std::back_insert_iterator<buffer<T>> {
: std::back_insert_iterator<buffer<T>>(it) {}
};
// Extracts a reference to the container from back_insert_iterator.
template <typename Container>
inline Container& get_container(std::back_insert_iterator<Container> it) {
using bi_iterator = std::back_insert_iterator<Container>;
struct accessor : bi_iterator {
accessor(bi_iterator iter) : bi_iterator(iter) {}
using bi_iterator::container;
};
return *accessor(it).container;
// Maps an output iterator into a buffer.
template <typename T, typename OutputIt>
iterator_buffer<OutputIt, T> get_buffer(OutputIt);
template <typename T> buffer<T>& get_buffer(buffer_appender<T>);
template <typename OutputIt> OutputIt get_buffer_init(OutputIt out) {
return out;
}
template <typename T> buffer<T>& get_buffer_init(buffer_appender<T> out) {
return get_container(out);
}
template <typename Buffer>
auto get_iterator(Buffer& buf) -> decltype(buf.out()) {
return buf.out();
}
template <typename T> buffer_appender<T> get_iterator(buffer<T>& buf) {
return buffer_appender<T>(buf);
}
template <typename T, typename Char = char, typename Enable = void>
@ -1242,17 +1280,49 @@ FMT_CONSTEXPR_DECL FMT_INLINE auto visit_format_arg(
return vis(monostate());
}
// Checks whether T is a container with contiguous storage.
template <typename T> struct is_contiguous : std::false_type {};
template <typename Char>
struct is_contiguous<std::basic_string<Char>> : std::true_type {};
template <typename Char>
struct is_contiguous<detail::buffer<Char>> : std::true_type {};
template <typename T> struct formattable : std::false_type {};
namespace detail {
// A workaround for gcc 4.8 to make void_t work in a SFINAE context.
template <typename... Ts> struct void_t_impl { using type = void; };
template <typename... Ts>
using void_t = typename detail::void_t_impl<Ts...>::type;
// Detect the iterator category of *any* given type in a SFINAE-friendly way.
// Unfortunately, older implementations of std::iterator_traits are not safe
// for use in a SFINAE-context.
template <typename It, typename Enable = void>
struct iterator_category : std::false_type {};
template <typename T> struct iterator_category<T*> {
using type = std::random_access_iterator_tag;
};
template <typename It>
struct iterator_category<It, void_t<typename It::iterator_category>> {
using type = typename It::iterator_category;
};
// Detect if *any* given type models the OutputIterator concept.
template <typename It> class is_output_iterator {
// Check for mutability because all iterator categories derived from
// std::input_iterator_tag *may* also meet the requirements of an
// OutputIterator, thereby falling into the category of 'mutable iterators'
// [iterator.requirements.general] clause 4. The compiler reveals this
// property only at the point of *actually dereferencing* the iterator!
template <typename U>
static decltype(*(std::declval<U>())) test(std::input_iterator_tag);
template <typename U> static char& test(std::output_iterator_tag);
template <typename U> static const char& test(...);
using type = decltype(test<It>(typename iterator_category<It>::type{}));
public:
enum { value = !std::is_const<remove_reference_t<type>>::value };
};
template <typename OutputIt>
struct is_back_insert_iterator : std::false_type {};
template <typename Container>
@ -1848,29 +1918,33 @@ inline void vprint_mojibake(std::FILE*, string_view, format_args) {}
/** Formats a string and writes the output to ``out``. */
// GCC 8 and earlier cannot handle std::back_insert_iterator<Container> with
// vformat_to<ArgFormatter>(...) overload, so SFINAE on iterator type instead.
template <
typename OutputIt, typename S, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_contiguous_back_insert_iterator<OutputIt>::value)>
template <typename OutputIt, typename S, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value)>
OutputIt vformat_to(
OutputIt out, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
auto& c = detail::get_container(out);
using container = remove_reference_t<decltype(c)>;
conditional_t<std::is_same<container, detail::buffer<Char>>::value,
detail::buffer<Char>&, detail::container_buffer<container>>
buf(c);
decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out));
detail::vformat_to(buf, to_string_view(format_str), args);
return out;
return detail::get_iterator(buf);
}
template <typename Container, typename S, typename... Args,
FMT_ENABLE_IF(
is_contiguous<Container>::value&& detail::is_string<S>::value)>
inline std::back_insert_iterator<Container> format_to(
std::back_insert_iterator<Container> out, const S& format_str,
Args&&... args) {
return vformat_to(out, to_string_view(format_str),
detail::make_args_checked<Args...>(format_str, args...));
/**
\rst
Formats arguments, 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), "{}", 42);
\endrst
*/
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&&
detail::is_string<S>::value)>
inline OutputIt format_to(OutputIt out, const S& format_str, Args&&... args) {
const auto& vargs = detail::make_args_checked<Args...>(format_str, args...);
return vformat_to(out, to_string_view(format_str), vargs);
}
template <typename S, typename Char = char_t<S>>

View File

@ -295,50 +295,11 @@ FMT_INLINE void assume(bool condition) {
#endif
}
// A workaround for gcc 4.8 to make void_t work in a SFINAE context.
template <typename... Ts> struct void_t_impl { using type = void; };
template <typename... Ts>
using void_t = typename detail::void_t_impl<Ts...>::type;
// An approximation of iterator_t for pre-C++20 systems.
template <typename T>
using iterator_t = decltype(std::begin(std::declval<T&>()));
template <typename T> using sentinel_t = decltype(std::end(std::declval<T&>()));
// Detect the iterator category of *any* given type in a SFINAE-friendly way.
// Unfortunately, older implementations of std::iterator_traits are not safe
// for use in a SFINAE-context.
template <typename It, typename Enable = void>
struct iterator_category : std::false_type {};
template <typename T> struct iterator_category<T*> {
using type = std::random_access_iterator_tag;
};
template <typename It>
struct iterator_category<It, void_t<typename It::iterator_category>> {
using type = typename It::iterator_category;
};
// Detect if *any* given type models the OutputIterator concept.
template <typename It> class is_output_iterator {
// Check for mutability because all iterator categories derived from
// std::input_iterator_tag *may* also meet the requirements of an
// OutputIterator, thereby falling into the category of 'mutable iterators'
// [iterator.requirements.general] clause 4. The compiler reveals this
// property only at the point of *actually dereferencing* the iterator!
template <typename U>
static decltype(*(std::declval<U>())) test(std::input_iterator_tag);
template <typename U> static char& test(std::output_iterator_tag);
template <typename U> static const char& test(...);
using type = decltype(test<It>(typename iterator_category<It>::type{}));
public:
enum { value = !std::is_const<remove_reference_t<type>>::value };
};
// A workaround for std::string not having mutable data() until C++17.
template <typename Char> inline Char* get_data(std::basic_string<Char>& s) {
return &s[0];
@ -3482,8 +3443,7 @@ detail::buffer_appender<Char> detail::vformat_to(
detail::buffer<Char>& buf, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
using af = arg_formatter<typename buffer_context<Char>::iterator, Char>;
return vformat_to<af>(buffer_appender<Char>(buf), to_string_view(format_str),
args);
return vformat_to<af>(buffer_appender<Char>(buf), format_str, args);
}
#ifndef FMT_HEADER_ONLY
@ -3537,43 +3497,6 @@ using format_context_t = basic_format_context<OutputIt, Char>;
template <typename OutputIt, typename Char = char>
using format_args_t = basic_format_args<format_context_t<OutputIt, Char>>;
template <
typename S, typename OutputIt, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value &&
!detail::is_contiguous_back_insert_iterator<OutputIt>::value)>
inline OutputIt vformat_to(
OutputIt out, const S& format_str,
format_args_t<type_identity_t<OutputIt>, char_t<S>> args) {
using af = detail::arg_formatter<OutputIt, char_t<S>>;
return vformat_to<af>(out, to_string_view(format_str), args);
}
/**
\rst
Formats arguments, 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), "{}", 42);
\endrst
*/
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(
detail::is_output_iterator<OutputIt>::value &&
!detail::is_contiguous_back_insert_iterator<OutputIt>::value &&
detail::is_string<S>::value)>
inline OutputIt format_to(OutputIt out, const S& format_str, Args&&... args) {
detail::check_format_string<Args...>(format_str);
using Char = char_t<S>;
detail::iterator_buffer<OutputIt, Char> buf(out);
detail::vformat_to(buf, to_string_view(format_str),
make_format_args<buffer_context<Char>>(args...));
buf.flush();
return buf.out();
}
template <typename OutputIt> struct format_to_n_result {
/** Iterator past the end of the output range. */
OutputIt out;
@ -3582,24 +3505,23 @@ template <typename OutputIt> struct format_to_n_result {
};
template <typename OutputIt, typename Char = typename OutputIt::value_type>
using format_to_n_context =
format_context_t<detail::truncating_iterator<OutputIt>, Char>;
using format_to_n_context FMT_DEPRECATED_ALIAS = buffer_context<Char>;
template <typename OutputIt, typename Char = typename OutputIt::value_type>
using format_to_n_args = basic_format_args<format_to_n_context<OutputIt, Char>>;
using format_to_n_args FMT_DEPRECATED_ALIAS =
basic_format_args<buffer_context<Char>>;
template <typename OutputIt, typename Char, typename... Args>
inline format_arg_store<format_to_n_context<OutputIt, Char>, Args...>
FMT_DEPRECATED format_arg_store<buffer_context<Char>, Args...>
make_format_to_n_args(const Args&... args) {
return format_arg_store<format_to_n_context<OutputIt, Char>, Args...>(
args...);
return format_arg_store<buffer_context<Char>, Args...>(args...);
}
template <typename OutputIt, typename Char, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value)>
inline format_to_n_result<OutputIt> vformat_to_n(
OutputIt out, size_t n, basic_string_view<Char> format_str,
format_to_n_args<type_identity_t<OutputIt>, type_identity_t<Char>> args) {
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
auto it = vformat_to(detail::truncating_iterator<OutputIt>(out, n),
format_str, args);
return {it.base(), it.count()};
@ -3618,10 +3540,8 @@ template <typename OutputIt, typename S, typename... Args,
inline format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
const S& format_str,
const Args&... args) {
detail::check_format_string<Args...>(format_str);
using context = format_to_n_context<OutputIt, char_t<S>>;
return vformat_to_n(out, n, to_string_view(format_str),
make_format_args<context>(args...));
const auto& vargs = detail::make_args_checked<Args...>(format_str, args...);
return vformat_to_n(out, n, to_string_view(format_str), vargs);
}
template <typename Char, enable_if_t<(!std::is_same<Char, char>::value), int>>