Add a partial specialization of _Iter_sink for ostreambuf_iterator that
writes directly to the streambuf's put area for unbounded output (zero
copy), and falls back to _Buf_sink's internal buffer with bulk sputn
for bounded output or unbuffered streams.  This avoids the per-character
sputc overhead of the generic _Iter_sink::_M_overflow.

The specialization extends _Buf_sink and overrides _M_overflow to flush
the buffer using sputn (bulk write) or, when the output is unbounded
and the streambuf has a put area, by directing _Buf_sink's span straight
into the put area for zero-copy output.  On overflow, previously written
characters are committed via pointer advance instead of being copied.

This addresses the suggestion in PR libstdc++/111052 comment 2 that
_Iter_sink should recognize ostreambuf_iterator for direct streambuf
writing.

libstdc++-v3/ChangeLog:

        * include/bits/streambuf_iterator.h (ostreambuf_iterator):
        Add _M_get_sbuf(), _M_put_area_begin(), _M_put_area_end(),
        _M_put_area_advance(), and _M_set_failed() helpers.
        * include/std/format (_Iter_sink): Add partial specialization
        for ostreambuf_iterator with zero-copy put-area writing.

Signed-off-by: Anlai Lu <[email protected]>
---
 .../include/bits/streambuf_iterator.h         |  28 +++++
 libstdc++-v3/include/std/format               | 101 ++++++++++++++++++
 2 files changed, 129 insertions(+)

diff --git a/libstdc++-v3/include/bits/streambuf_iterator.h 
b/libstdc++-v3/include/bits/streambuf_iterator.h
index 095928ca4..ccd2d67c8 100644
--- a/libstdc++-v3/include/bits/streambuf_iterator.h
+++ b/libstdc++-v3/include/bits/streambuf_iterator.h
@@ -318,6 +318,34 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       failed() const _GLIBCXX_USE_NOEXCEPT
       { return _M_failed; }
 
+      /// Return pointer to the underlying streambuf.
+      _GLIBCXX_NODISCARD
+      streambuf_type*
+      _M_get_sbuf() const _GLIBCXX_USE_NOEXCEPT
+      { return _M_sbuf; }
+
+      /// Return pointer to start of streambuf's put area, or null.
+      _GLIBCXX_NODISCARD
+      _CharT*
+      _M_put_area_begin() const _GLIBCXX_USE_NOEXCEPT
+      { return _M_sbuf ? _M_sbuf->pptr() : 0; }
+
+      /// Return pointer to end of streambuf's put area, or null.
+      _GLIBCXX_NODISCARD
+      _CharT*
+      _M_put_area_end() const _GLIBCXX_USE_NOEXCEPT
+      { return _M_sbuf ? _M_sbuf->epptr() : 0; }
+
+      /// Advance streambuf's put area pointer by @a __n.
+      void
+      _M_put_area_advance(streamsize __n) const _GLIBCXX_USE_NOEXCEPT
+      { if (_M_sbuf) _M_sbuf->__safe_pbump(__n); }
+
+      /// Set the failed flag after a write error.
+      void
+      _M_set_failed() _GLIBCXX_USE_NOEXCEPT
+      { _M_failed = true; }
+
       ostreambuf_iterator&
       _M_put(const _CharT* __ws, streamsize __len)
       {
diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
index db226ecb0..e65647ab6 100644
--- a/libstdc++-v3/include/std/format
+++ b/libstdc++-v3/include/std/format
@@ -3728,6 +3728,107 @@ namespace __format
       }
     };
 
+  // Specialization for ostreambuf_iterator with zero-copy put-area
+  // writing for unbounded output.  Falls back to _Buf_sink's _M_buf
+  // for bounded (_format_to_n) or unbuffered streams.
+  template<typename _CharT, typename _Traits>
+    class _Iter_sink<_CharT, ostreambuf_iterator<_CharT, _Traits>>
+    : public _Buf_sink<_CharT>
+    {
+      using _OutIter = ostreambuf_iterator<_CharT, _Traits>;
+
+      _OutIter _M_out;
+      iter_difference_t<_OutIter> _M_max;
+      size_t _M_count = 0;
+
+      _GLIBCXX_CONSTEXPR_FORMAT void
+      _M_sync()
+      {
+       if (_M_out._M_get_sbuf()) [[likely]]
+         {
+           _CharT* __p = _M_out._M_put_area_begin();
+           _CharT* __e = _M_out._M_put_area_end();
+           if (__p && __e > __p && _M_max < 0) [[likely]]
+             {
+               this->_M_reset(span<_CharT>(__p, __e));
+               return;
+             }
+         }
+       this->_M_reset(this->_M_buf);
+      }
+
+      // Flush @a __s to the streambuf, checking sputn return values.
+      _GLIBCXX_CONSTEXPR_FORMAT void
+      _M_flush(span<_CharT> __s)
+      {
+       auto* __sbuf = _M_out._M_get_sbuf();
+
+       if (__s.data() == this->_M_buf)
+         {
+           if (__s.size() > 0 && __sbuf) [[likely]]
+             {
+               streamsize __n = __s.size();
+               streamsize __written;
+               if (_M_max < 0)
+                 __written = __sbuf->sputn(__s.data(), __n);
+               else if (_M_count < static_cast<size_t>(_M_max))
+                 {
+                   auto __limit = _M_max - _M_count;
+                   if (__limit < __n)
+                     __n = static_cast<streamsize>(__limit);
+                   __written = __sbuf->sputn(__s.data(), __n);
+                 }
+               else
+                 __written = 0;
+               if (__written != __n)
+                 _M_out._M_set_failed();
+               _M_count += __written;
+             }
+         }
+       else
+         {
+           if (__s.size() > 0 && __sbuf) [[likely]]
+             {
+               _M_out._M_put_area_advance(__s.size());
+               _M_count += __s.size();
+             }
+         }
+
+      }
+
+    protected:
+      _GLIBCXX_CONSTEXPR_FORMAT void
+      _M_overflow() override
+      {
+       auto __s = this->_M_used();
+       _M_flush(__s);
+       if (__s.data() == this->_M_buf)
+         this->_M_rewind();
+       _M_sync();
+      }
+
+      _GLIBCXX_CONSTEXPR_FORMAT bool
+      _M_discarding() const override
+      { return false; }
+
+    public:
+      [[__gnu__::__always_inline__]]
+      _GLIBCXX_CONSTEXPR_FORMAT explicit
+      _Iter_sink(_OutIter __out, iter_difference_t<_OutIter> __max = -1)
+      : _M_out(__out), _M_max(__max)
+      { _M_sync(); }
+
+      using _Sink<_CharT>::out;
+
+      _GLIBCXX_CONSTEXPR_FORMAT format_to_n_result<_OutIter>
+      _M_finish() &&
+      {
+       _M_flush(this->_M_used());
+       return { _M_out,
+                iter_difference_t<_OutIter>(_M_count) };
+      }
+    };
+
   // Used for contiguous iterators.
   // No buffer is used, characters are written straight to the iterator.
   // We do not know the size of the output range, so the span size just grows
-- 
2.34.1

Reply via email to