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>>