From eb90da2e82d9626ad28017d289d303e740a182bc Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 18 Jul 2020 06:44:44 -0700 Subject: [PATCH] Type erase output iterators --- include/fmt/core.h | 186 ++++++++++++++++++++++++++++++------------- include/fmt/format.h | 98 +++-------------------- 2 files changed, 139 insertions(+), 145 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 08032412..bf144a35 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -616,8 +616,24 @@ template using has_formatter = std::is_constructible>; +// Checks whether T is a container with contiguous storage. +template struct is_contiguous : std::false_type {}; +template +struct is_contiguous> : std::true_type {}; + namespace detail { +// Extracts a reference to the container from back_insert_iterator. +template +inline Container& get_container(std::back_insert_iterator it) { + using bi_iterator = std::back_insert_iterator; + 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 class buffer { } }; -// A container-backed buffer. -template -class container_buffer : public buffer { - 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(c.size()), container_(c) {} -}; - // A buffer that writes to an output iterator when flushed. template class iterator_buffer : public buffer { @@ -738,18 +737,20 @@ class iterator_buffer : public buffer { void grow(size_t) final { if (this->size() == buffer_size) flush(); } + void flush(); public: explicit iterator_buffer(OutputIt out) - : buffer(data_, 0, buffer_size), out_(out) {} + : buffer(data_, 0, buffer_size), out_(out) {} ~iterator_buffer() { flush(); } - OutputIt out() { return out_; } - void flush(); + OutputIt out() { + flush(); + return out_; + } }; -template -class iterator_buffer : public buffer { +template class iterator_buffer : public buffer { protected: void grow(size_t) final {} @@ -757,9 +758,37 @@ class iterator_buffer : public buffer { explicit iterator_buffer(T* out) : buffer(out, 0, ~size_t()) {} T* out() { return &*this->end(); } - void flush() {} }; +// A buffer that writes to a container with the contiguous storage. +template +class iterator_buffer, + enable_if_t::value, + typename Container::value_type>> + : public buffer { + 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(c.size()), container_(c) {} + explicit iterator_buffer(std::back_insert_iterator out) + : iterator_buffer(get_container(out)) {} + std::back_insert_iterator out() { + return std::back_inserter(container_); + } +}; + +template +using container_buffer = iterator_buffer, + typename Container::value_type>; + // An output iterator that appends to the buffer. // It is used to reduce symbol sizes for the common case. template @@ -771,15 +800,24 @@ class buffer_appender : public std::back_insert_iterator> { : std::back_insert_iterator>(it) {} }; -// Extracts a reference to the container from back_insert_iterator. -template -inline Container& get_container(std::back_insert_iterator it) { - using bi_iterator = std::back_insert_iterator; - 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 +iterator_buffer get_buffer(OutputIt); +template buffer& get_buffer(buffer_appender); + +template OutputIt get_buffer_init(OutputIt out) { + return out; +} +template buffer& get_buffer_init(buffer_appender out) { + return get_container(out); +} + +template +auto get_iterator(Buffer& buf) -> decltype(buf.out()) { + return buf.out(); +} +template buffer_appender get_iterator(buffer& buf) { + return buffer_appender(buf); } template @@ -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 struct is_contiguous : std::false_type {}; -template -struct is_contiguous> : std::true_type {}; -template -struct is_contiguous> : std::true_type {}; - template struct formattable : std::false_type {}; namespace detail { +// A workaround for gcc 4.8 to make void_t work in a SFINAE context. +template struct void_t_impl { using type = void; }; + +template +using void_t = typename detail::void_t_impl::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 +struct iterator_category : std::false_type {}; + +template struct iterator_category { + using type = std::random_access_iterator_tag; +}; + +template +struct iterator_category> { + using type = typename It::iterator_category; +}; + +// Detect if *any* given type models the OutputIterator concept. +template 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 + static decltype(*(std::declval())) test(std::input_iterator_tag); + template static char& test(std::output_iterator_tag); + template static const char& test(...); + + using type = decltype(test(typename iterator_category::type{})); + + public: + enum { value = !std::is_const>::value }; +}; + template struct is_back_insert_iterator : std::false_type {}; template @@ -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 with // vformat_to(...) overload, so SFINAE on iterator type instead. -template < - typename OutputIt, typename S, typename Char = char_t, - FMT_ENABLE_IF(detail::is_contiguous_back_insert_iterator::value)> +template , + FMT_ENABLE_IF(detail::is_output_iterator::value)> OutputIt vformat_to( OutputIt out, const S& format_str, basic_format_args>> args) { - auto& c = detail::get_container(out); - using container = remove_reference_t; - conditional_t>::value, - detail::buffer&, detail::container_buffer> - buf(c); + decltype(detail::get_buffer(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 ::value&& detail::is_string::value)> -inline std::back_insert_iterator format_to( - std::back_insert_iterator out, const S& format_str, - Args&&... args) { - return vformat_to(out, to_string_view(format_str), - detail::make_args_checked(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 out; + fmt::format_to(std::back_inserter(out), "{}", 42); + \endrst + */ +template ::value&& + detail::is_string::value)> +inline OutputIt format_to(OutputIt out, const S& format_str, Args&&... args) { + const auto& vargs = detail::make_args_checked(format_str, args...); + return vformat_to(out, to_string_view(format_str), vargs); } template > diff --git a/include/fmt/format.h b/include/fmt/format.h index 2a82e2e5..decbe756 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -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 struct void_t_impl { using type = void; }; - -template -using void_t = typename detail::void_t_impl::type; - // An approximation of iterator_t for pre-C++20 systems. template using iterator_t = decltype(std::begin(std::declval())); template using sentinel_t = decltype(std::end(std::declval())); -// 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 -struct iterator_category : std::false_type {}; - -template struct iterator_category { - using type = std::random_access_iterator_tag; -}; - -template -struct iterator_category> { - using type = typename It::iterator_category; -}; - -// Detect if *any* given type models the OutputIterator concept. -template 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 - static decltype(*(std::declval())) test(std::input_iterator_tag); - template static char& test(std::output_iterator_tag); - template static const char& test(...); - - using type = decltype(test(typename iterator_category::type{})); - - public: - enum { value = !std::is_const>::value }; -}; - // A workaround for std::string not having mutable data() until C++17. template inline Char* get_data(std::basic_string& s) { return &s[0]; @@ -3482,8 +3443,7 @@ detail::buffer_appender detail::vformat_to( detail::buffer& buf, basic_string_view format_str, basic_format_args>> args) { using af = arg_formatter::iterator, Char>; - return vformat_to(buffer_appender(buf), to_string_view(format_str), - args); + return vformat_to(buffer_appender(buf), format_str, args); } #ifndef FMT_HEADER_ONLY @@ -3537,43 +3497,6 @@ using format_context_t = basic_format_context; template using format_args_t = basic_format_args>; -template < - typename S, typename OutputIt, typename... Args, - FMT_ENABLE_IF(detail::is_output_iterator::value && - !detail::is_contiguous_back_insert_iterator::value)> -inline OutputIt vformat_to( - OutputIt out, const S& format_str, - format_args_t, char_t> args) { - using af = detail::arg_formatter>; - return vformat_to(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 out; - fmt::format_to(std::back_inserter(out), "{}", 42); - \endrst - */ -template ::value && - !detail::is_contiguous_back_insert_iterator::value && - detail::is_string::value)> -inline OutputIt format_to(OutputIt out, const S& format_str, Args&&... args) { - detail::check_format_string(format_str); - using Char = char_t; - detail::iterator_buffer buf(out); - detail::vformat_to(buf, to_string_view(format_str), - make_format_args>(args...)); - buf.flush(); - return buf.out(); -} - template struct format_to_n_result { /** Iterator past the end of the output range. */ OutputIt out; @@ -3582,24 +3505,23 @@ template struct format_to_n_result { }; template -using format_to_n_context = - format_context_t, Char>; +using format_to_n_context FMT_DEPRECATED_ALIAS = buffer_context; template -using format_to_n_args = basic_format_args>; +using format_to_n_args FMT_DEPRECATED_ALIAS = + basic_format_args>; template -inline format_arg_store, Args...> +FMT_DEPRECATED format_arg_store, Args...> make_format_to_n_args(const Args&... args) { - return format_arg_store, Args...>( - args...); + return format_arg_store, Args...>(args...); } template ::value)> inline format_to_n_result vformat_to_n( OutputIt out, size_t n, basic_string_view format_str, - format_to_n_args, type_identity_t> args) { + basic_format_args>> args) { auto it = vformat_to(detail::truncating_iterator(out, n), format_str, args); return {it.base(), it.count()}; @@ -3618,10 +3540,8 @@ template format_to_n(OutputIt out, size_t n, const S& format_str, const Args&... args) { - detail::check_format_string(format_str); - using context = format_to_n_context>; - return vformat_to_n(out, n, to_string_view(format_str), - make_format_args(args...)); + const auto& vargs = detail::make_args_checked(format_str, args...); + return vformat_to_n(out, n, to_string_view(format_str), vargs); } template ::value), int>>