Introduce _Streambuf_sink that writes directly to basic_streambuf
via sputn, preferring zero-copy writes into the streambuf put area
(pptr/epptr/pbump) and falling back to the stack buffer and bulk
sputn.
libstdc++-v3/ChangeLog:
* include/bits/streambuf_iterator.h (ostreambuf_iterator):
Add internal _M_get_sbuf() and _M_set_failed() members.
* include/std/format: Include <bits/streambuf_iterator.h>.
(__format::_Streambuf_sink): New class template.
* include/std/streambuf: Forward-declare __format::_Streambuf_sink,
add friend declaration to basic_streambuf.
Signed-off-by: Anlai Lu <[email protected]>
---
.../include/bits/streambuf_iterator.h | 11 ++
libstdc++-v3/include/std/format | 163 ++++++++++++++++++
libstdc++-v3/include/std/streambuf | 4 +
3 files changed, 178 insertions(+)
diff --git a/libstdc++-v3/include/bits/streambuf_iterator.h
b/libstdc++-v3/include/bits/streambuf_iterator.h
index 095928ca4..d919fdbb8 100644
--- a/libstdc++-v3/include/bits/streambuf_iterator.h
+++ b/libstdc++-v3/include/bits/streambuf_iterator.h
@@ -318,6 +318,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
failed() const _GLIBCXX_USE_NOEXCEPT
{ return _M_failed; }
+ /// @cond internal
+ _GLIBCXX_NODISCARD
+ streambuf_type*
+ _M_get_sbuf() const _GLIBCXX_USE_NOEXCEPT
+ { return _M_sbuf; }
+
+ void
+ _M_set_failed() _GLIBCXX_USE_NOEXCEPT
+ { _M_failed = true; }
+ /// @endcond
+
ostreambuf_iterator&
_M_put(const _CharT* __ws, streamsize __len)
{
diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
index dd275b6b8..a449efd45 100644
--- a/libstdc++-v3/include/std/format
+++ b/libstdc++-v3/include/std/format
@@ -60,6 +60,7 @@
#include <bits/ranges_algobase.h> // ranges::copy
#include <bits/stl_iterator.h> // counted_iterator
#include <bits/stl_pair.h> // __is_pair
+#include <bits/streambuf_iterator.h> // ostreambuf_iterator, basic_streambuf
#include <bits/unicode.h> // __is_scalar_value, _Utf_view, etc.
#include <bits/utility.h> // tuple_size_v
#include <ext/numeric_traits.h> // __int_traits
@@ -3531,6 +3532,168 @@ namespace __format
{ }
};
+ // A format sink that writes directly to a basic_streambuf.
+ // Prefers zero-copy writes into the streambuf's put area
+ // (pptr/epptr/pbump), falling back to the stack buffer
+ // (_M_buf) and bulk sputn.
+ // _M_count tracks chars output (for _M_max truncation decisions);
+ // _M_total tracks all chars formatted (for format_to_n_result::size).
+ template<typename _CharT, typename _Traits = char_traits<_CharT>>
+ class _Streambuf_sink : public _Buf_sink<_CharT>
+ {
+ using _Buf_sink<_CharT>::_M_buf;
+ using __diff_t
+ = iter_difference_t<ostreambuf_iterator<_CharT, _Traits>>;
+ enum class _Sink_state { _S_stack, _S_put_area, _S_failed };
+
+ basic_streambuf<_CharT, _Traits>* _M_sbuf;
+ __diff_t _M_max = -1;
+ __diff_t _M_count = 0;
+ __diff_t _M_total = 0;
+ _Sink_state _M_state = _Sink_state::_S_stack;
+
+ void
+ _M_use_stackbuf()
+ {
+ this->_M_reset(_M_buf);
+ if (_M_state != _Sink_state::_S_failed)
+ _M_state = _Sink_state::_S_stack;
+ }
+
+ protected:
+ // Try to switch to the streambuf's put area for zero-copy writes.
+ bool
+ _M_use_put_area(size_t __n)
+ {
+ if (_M_has_failed())
+ return false;
+ if (auto __p = _M_sbuf->pptr())
+ if (auto __e = _M_sbuf->epptr();
+ __e && __e > __p
+ && static_cast<size_t>(__e - __p) >= __n)
+ {
+ size_t __avail = static_cast<size_t>(__e - __p);
+ if (_M_max >= 0)
+ {
+ auto __limit = _M_max - _M_count;
+ if (__limit <= 0
+ || static_cast<size_t>(__limit) < __n)
+ return false;
+ if (__avail > static_cast<size_t>(__limit))
+ __avail = static_cast<size_t>(__limit);
+ }
+ this->_M_reset(span<_CharT>{__p, __avail});
+ _M_state = _Sink_state::_S_put_area;
+ return true;
+ }
+ return false;
+ }
+
+ // Write __s to the streambuf via sputn (stack buffer)
+ // or pbump (put area), depending on _M_state.
+ void
+ _M_do_flush(span<_CharT> __s)
+ {
+ _M_total += __s.size();
+ if (_M_max >= 0)
+ {
+ auto __limit = _M_max - _M_count;
+ if (__limit <= 0)
+ return;
+ if (__s.size() > static_cast<size_t>(__limit))
+ __s = __s.first(static_cast<size_t>(__limit));
+ }
+
+ switch (_M_state)
+ {
+ case _Sink_state::_S_stack:
+ {
+ streamsize __written
+ = _M_sbuf->sputn(__s.data(), __s.size());
+ if (__written != __s.size()) [[unlikely]]
+ _M_state = _Sink_state::_S_failed;
+ _M_count += __written;
+ }
+ break;
+ case _Sink_state::_S_put_area:
+ _M_sbuf->__safe_pbump(__s.size());
+ _M_count += __s.size();
+ break;
+ case _Sink_state::_S_failed:
+ break;
+ }
+ }
+
+ void
+ _M_overflow() override
+ {
+ auto __s = this->_M_used();
+ if (!__s.empty()) [[likely]]
+ _M_do_flush(__s);
+ if (_M_max >= 0 && _M_count >= _M_max)
+ {
+ _M_use_stackbuf();
+ return;
+ }
+ if (!_M_use_put_area(0))
+ _M_use_stackbuf();
+ }
+
+ bool
+ _M_discarding() const override
+ {
+ return _M_state == _Sink_state::_S_failed
+ || (_M_max >= 0 && _M_count >= _M_max);
+ }
+
+ public:
+ explicit
+ _Streambuf_sink(basic_streambuf<_CharT, _Traits>* __sbuf,
+ __diff_t __max = -1) noexcept
+ : _M_sbuf(__sbuf), _M_max(__max)
+ { }
+
+ using _Sink<_CharT>::out;
+
+ typename _Sink<_CharT>::_Reservation
+ _M_reserve(size_t __n) override
+ {
+ if (_M_has_failed())
+ return { nullptr };
+
+ if (!this->_M_used().empty())
+ {
+ if(__n <= this->_M_unused().size())
+ return { this };
+
+ this->_M_do_flush(this->_M_used());
+ }
+
+ // Try to write directly into the streambuf's put area.
+ if (_M_use_put_area(__n))
+ return { this };
+
+ // Otherwise reset to the stack buffer.
+ _M_use_stackbuf();
+ if (__n <= this->_M_unused().size())
+ return { this };
+
+ return { nullptr };
+ }
+
+ bool
+ _M_has_failed() const noexcept
+ { return _M_state == _Sink_state::_S_failed; }
+
+ void
+ _M_set_failed() noexcept
+ { _M_state = _Sink_state::_S_failed; }
+
+ __diff_t
+ _M_total_chars() const noexcept
+ { return _M_total; }
+ };
+
using _GLIBCXX_STD_C::vector;
// A sink that fills a sequence (e.g. std::string, std::vector, std::deque).
diff --git a/libstdc++-v3/include/std/streambuf
b/libstdc++-v3/include/std/streambuf
index 616e44f74..22b505228 100644
--- a/libstdc++-v3/include/std/streambuf
+++ b/libstdc++-v3/include/std/streambuf
@@ -57,6 +57,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
__copy_streambufs_eof(basic_streambuf<_CharT, _Traits>*,
basic_streambuf<_CharT, _Traits>*, bool&);
+ namespace __format { template<typename, typename> class _Streambuf_sink; }
+
/**
* @brief The actual work of input and output (interface).
* @ingroup io
@@ -149,6 +151,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
friend class basic_ostream<char_type, traits_type>;
friend class istreambuf_iterator<char_type, traits_type>;
friend class ostreambuf_iterator<char_type, traits_type>;
+ template<typename, typename>
+ friend class __format::_Streambuf_sink;
friend streamsize
__copy_streambufs_eof<>(basic_streambuf*, basic_streambuf*, bool&);
--
2.34.1