<div dir="ltr"><div dir="ltr"><br></div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">On Mon, Jun 29, 2026 at 2:32 PM Anlai Lu <<a href="mailto:agicy@qq.com">agicy@qq.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Add a partial specialization of _Iter_sink for ostreambuf_iterator that<br>
writes directly to the streambuf's put area for unbounded output (zero<br>
copy), and falls back to _Buf_sink's internal buffer with bulk sputn<br>
for bounded output or unbuffered streams. This avoids the per-character<br>
sputc overhead of the generic _Iter_sink::_M_overflow.<br>
<br>
The specialization extends _Buf_sink and overrides _M_overflow to flush<br>
the buffer using sputn (bulk write) or, when the output is unbounded<br>
and the streambuf has a put area, by directing _Buf_sink's span straight<br>
into the put area for zero-copy output. On overflow, previously written<br>
characters are committed via pointer advance instead of being copied.<br>
<br>
This addresses the suggestion in PR libstdc++/111052 comment 2 that<br>
_Iter_sink should recognize ostreambuf_iterator for direct streambuf<br>
writing.<br>
<br>
libstdc++-v3/ChangeLog:<br>
<br>
* include/bits/streambuf_iterator.h (ostreambuf_iterator):<br>
Add _M_get_sbuf(), _M_put_area_begin(), _M_put_area_end(),<br>
_M_put_area_advance(), and _M_set_failed() helpers.<br>
* include/std/format (_Iter_sink): Add partial specialization<br>
for ostreambuf_iterator with zero-copy put-area writing.<br>
<br>
Signed-off-by: Anlai Lu <<a href="mailto:agicy@qq.com" target="_blank">agicy@qq.com</a>><br>
---<br>
.../include/bits/streambuf_iterator.h | 28 +++++<br>
libstdc++-v3/include/std/format | 101 ++++++++++++++++++<br>
2 files changed, 129 insertions(+)<br>
<br>
diff --git a/libstdc++-v3/include/bits/streambuf_iterator.h b/libstdc++-v3/include/bits/streambuf_iterator.h<br>
index 095928ca4..ccd2d67c8 100644<br>
--- a/libstdc++-v3/include/bits/streambuf_iterator.h<br>
+++ b/libstdc++-v3/include/bits/streambuf_iterator.h<br>
@@ -318,6 +318,34 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION<br>
failed() const _GLIBCXX_USE_NOEXCEPT<br>
{ return _M_failed; }<br>
<br>
+ /// Return pointer to the underlying streambuf.<br>
+ _GLIBCXX_NODISCARD<br>
+ streambuf_type*<br>
+ _M_get_sbuf() const _GLIBCXX_USE_NOEXCEPT<br>
+ { return _M_sbuf; }<br></blockquote><div>I am not yet convinced this should be a public method. But for sure</div><div>we want to integreate it into one.</div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+<br>
+ /// Return pointer to start of streambuf's put area, or null.<br>
+ _GLIBCXX_NODISCARD<br>
+ _CharT*<br>
+ _M_put_area_begin() const _GLIBCXX_USE_NOEXCEPT<br>
+ { return _M_sbuf ? _M_sbuf->pptr() : 0; }<br>
+<br>
+ /// Return pointer to end of streambuf's put area, or null.<br>
+ _GLIBCXX_NODISCARD<br>
+ _CharT*<br>
+ _M_put_area_end() const _GLIBCXX_USE_NOEXCEPT<br>
+ { return _M_sbuf ? _M_sbuf->epptr() : 0; }<br>
+<br>
+ /// Advance streambuf's put area pointer by @a __n.<br>
+ void<br>
+ _M_put_area_advance(streamsize __n) const _GLIBCXX_USE_NOEXCEPT<br>
+ { if (_M_sbuf) _M_sbuf->__safe_pbump(__n); }<br>
+<br>
+ /// Set the failed flag after a write error.<br>
+ void<br>
+ _M_set_failed() _GLIBCXX_USE_NOEXCEPT<br>
+ { _M_failed = true; }<br>
+<br>
ostreambuf_iterator&<br>
_M_put(const _CharT* __ws, streamsize __len)<br>
{<br>
diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format<br>
index db226ecb0..e65647ab6 100644<br>
--- a/libstdc++-v3/include/std/format<br>
+++ b/libstdc++-v3/include/std/format<br>
@@ -3728,6 +3728,107 @@ namespace __format<br>
}<br>
};<br>
<br>
+ // Specialization for ostreambuf_iterator with zero-copy put-area<br>
+ // writing for unbounded output. Falls back to _Buf_sink's _M_buf<br>
+ // for bounded (_format_to_n) or unbuffered streams.<br>
+ template<typename _CharT, typename _Traits><br>
+ class _Iter_sink<_CharT, ostreambuf_iterator<_CharT, _Traits>><br>
+ : public _Buf_sink<_CharT><br></blockquote><div>Here, I would suggest introducing a separate _Streambuf_sink that</div><div>accepts an streambuf pointer, and then specialization ostream_iterator</div><div>to use it in separate commit (I have some concerns regarding impact</div><div>of this in general).</div><div><br></div>I wonder if that would make it possible to separate count/max</div><div class="gmail_quote gmail_quote_container"><div>(that are not needed for operator<<) in the ostream_iterator specialization.</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+ {<br>
+ using _OutIter = ostreambuf_iterator<_CharT, _Traits>;<br>
+<br>
+ _OutIter _M_out;<br>
+ iter_difference_t<_OutIter> _M_max;<br>
+ size_t _M_count = 0;<br>
+<br>
+ _GLIBCXX_CONSTEXPR_FORMAT void<br>
+ _M_sync()<br>
+ {<br>
+ if (_M_out._M_get_sbuf()) [[likely]]<br></blockquote><div>This checks are not needed, it is UB to construct ostreambuf_iterator,\</div><div>from null _M_get_buf. </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+ {<br>
+ _CharT* __p = _M_out._M_put_area_begin();<br>
+ _CharT* __e = _M_out._M_put_area_end();<br>
+ if (__p && __e > __p && _M_max < 0) [[likely]]<br>
+ {<br>
+ this->_M_reset(span<_CharT>(__p, __e));<br>
+ return;<br>
+ }<br>
+ }<br>
+ this->_M_reset(this->_M_buf);<br>
+ }<br>
+<br>
+ // Flush @a __s to the streambuf, checking sputn return values.<br>
+ _GLIBCXX_CONSTEXPR_FORMAT void<br>
+ _M_flush(span<_CharT> __s)<br>
+ {<br>
+ auto* __sbuf = _M_out._M_get_sbuf();<br>
+<br>
+ if (__s.data() == this->_M_buf)<br>
+ {<br>
+ if (__s.size() > 0 && __sbuf) [[likely]]<br>
+ {<br>
+ streamsize __n = __s.size();<br>
+ streamsize __written;<br>
+ if (_M_max < 0)<br>
+ __written = __sbuf->sputn(__s.data(), __n);<br>
+ else if (_M_count < static_cast<size_t>(_M_max))<br>
+ {<br>
+ auto __limit = _M_max - _M_count;<br>
+ if (__limit < __n)<br>
+ __n = static_cast<streamsize>(__limit);<br>
+ __written = __sbuf->sputn(__s.data(), __n);<br>
+ }<br>
+ else<br>
+ __written = 0;<br>
+ if (__written != __n)<br>
+ _M_out._M_set_failed();<br>
+ _M_count += __written;<br>
+ }<br>
+ }<br>
+ else<br>
+ {<br>
+ if (__s.size() > 0 && __sbuf) [[likely]]<br>
+ {<br>
+ _M_out._M_put_area_advance(__s.size());<br>
+ _M_count += __s.size();<br>
+ }<br>
+ }<br>
+<br>
+ }<br>
+<br>
+ protected:<br>
+ _GLIBCXX_CONSTEXPR_FORMAT void<br>
+ _M_overflow() override<br>
+ {<br>
+ auto __s = this->_M_used();<br>
+ _M_flush(__s);<br>
+ if (__s.data() == this->_M_buf)<br>
+ this->_M_rewind();<br>
+ _M_sync();<br>
+ }<br>
+<br>
+ _GLIBCXX_CONSTEXPR_FORMAT bool<br>
+ _M_discarding() const override<br>
+ { return false; }<br>
+<br>
+ public:<br>
+ [[__gnu__::__always_inline__]]<br>
+ _GLIBCXX_CONSTEXPR_FORMAT explicit<br>
+ _Iter_sink(_OutIter __out, iter_difference_t<_OutIter> __max = -1)<br>
+ : _M_out(__out), _M_max(__max)<br>
+ { _M_sync(); }<br>
+<br>
+ using _Sink<_CharT>::out;<br>
+<br>
+ _GLIBCXX_CONSTEXPR_FORMAT format_to_n_result<_OutIter><br>
+ _M_finish() &&<br>
+ {<br>
+ _M_flush(this->_M_used());<br>
+ return { _M_out,<br>
+ iter_difference_t<_OutIter>(_M_count) };<br>
+ }<br>
+ };<br>
+<br>
// Used for contiguous iterators.<br>
// No buffer is used, characters are written straight to the iterator.<br>
// We do not know the size of the output range, so the span size just grows<br>
-- <br>
2.34.1<br>
<br>
</blockquote></div></div>