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