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