On Mon, Jun 29, 2026 at 2:32 PM Anlai Lu <[email protected]> wrote:

> 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; }
>
I am not yet convinced this should be a public method. But for sure
we want to integreate it into one.

> +
> +      /// 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>
>
Here, I would suggest introducing a separate _Streambuf_sink that
accepts an streambuf pointer, and then specialization ostream_iterator
to use it in separate commit (I have some concerns regarding impact
of this in general).

I wonder if that would make it possible to separate count/max
(that are not needed for operator<<) in the ostream_iterator specialization.


> +    {
> +      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]]
>
This checks are not needed, it is UB to construct ostreambuf_iterator,\
from null _M_get_buf.

> +         {
> +           _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