[PATCH 2/4] libstdc++: Add _Streambuf_sink for direct streambuf formatting
Anlai Lu
agicy@qq.com
Wed Aug 12 02:59:58 GMT 2026
> On Aug 11, 2026, at 18:08, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>
>
>
> On Thu, Jul 16, 2026 at 2:04 PM Anlai Lu <agicy@qq.com <mailto:agicy@qq.com>> wrote:
>> Introduce _Streambuf_sink that writes directly to basic_streambuf
>> via sputn, preferring zero-copy writes into the streambuf put area
>> (pptr/epptr/pbump) and falling back to the stack buffer and bulk
>> sputn.
>>
>> Streambuf I/O errors are tracked internally (_M_write_failed) and
>> reported after formatting completes, so that format-phase exceptions
>> (format_error, bad_alloc) propagate without setting badbit per
>> [ostream.formatted.print]/(4.2).
>>
>> The _Sink_state enum is omitted in favor of _M_on_stack() which
>> tests whether the current span points at the stack buffer.
>>
>> libstdc++-v3/ChangeLog:
>>
>> * include/bits/streambuf_iterator.h (ostreambuf_iterator):
>> Add internal _M_get_sbuf() and _M_set_failed() members.
>> * include/std/format: Include <bits/streambuf_iterator.h>.
>> (__format::_Streambuf_sink): New class template.
>> * include/std/streambuf: Forward-declare __format::_Streambuf_sink,
>> add friend declaration to basic_streambuf.
>>
>> Signed-off-by: Anlai Lu <agicy@qq.com <mailto:agicy@qq.com>>
>> ---
>> .../include/bits/streambuf_iterator.h | 11 ++
>> libstdc++-v3/include/std/format | 132 ++++++++++++++++++
>> libstdc++-v3/include/std/streambuf | 4 +
>> 3 files changed, 147 insertions(+)
>>
>> diff --git a/libstdc++-v3/include/bits/streambuf_iterator.h b/libstdc++-v3/include/bits/streambuf_iterator.h
>> index 095928ca4..d919fdbb8 100644
>> --- a/libstdc++-v3/include/bits/streambuf_iterator.h
>> +++ b/libstdc++-v3/include/bits/streambuf_iterator.h
>> @@ -318,6 +318,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>> failed() const _GLIBCXX_USE_NOEXCEPT
>> { return _M_failed; }
>>
>> + /// @cond internal
>> + _GLIBCXX_NODISCARD
>> + streambuf_type*
>> + _M_get_sbuf() const _GLIBCXX_USE_NOEXCEPT
>> + { return _M_sbuf; }
>> +
>> + void
>> + _M_set_failed() _GLIBCXX_USE_NOEXCEPT
>> + { _M_failed = true; }
>> + /// @endcond
>> +
>> ostreambuf_iterator&
>> _M_put(const _CharT* __ws, streamsize __len)
>> {
>> diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
>> index 9bf5e7857..b676629b1 100644
>> --- a/libstdc++-v3/include/std/format
>> +++ b/libstdc++-v3/include/std/format
>> @@ -59,6 +59,7 @@
>> #include <bits/ranges_util.h> // subrange
>> #include <bits/ranges_algobase.h> // ranges::copy
>> #include <bits/stl_iterator.h> // counted_iterator
>> +#include <bits/streambuf_iterator.h> // ostreambuf_iterator, basic_streambuf
>> #include <bits/stl_pair.h> // __is_pair
>> #include <bits/unicode.h> // __is_scalar_value, _Utf_view, etc.
>> #include <bits/utility.h> // tuple_size_v
>> @@ -3530,6 +3531,137 @@ namespace __format
>> { }
>> };
>>
>> + // A format sink that writes directly to a basic_streambuf.
>> + // Prefers zero-copy writes into the streambuf's put area
>> + // (pptr/epptr/pbump), falling back to the stack buffer
>> + // (_M_buf) and bulk sputn.
>> + template<typename _CharT, typename _Traits = char_traits<_CharT>>
>> + class _Streambuf_sink : public _Buf_sink<_CharT>
>> + {
>> + using _Buf_sink<_CharT>::_M_buf;
>> +
>> + protected:
>> + basic_streambuf<_CharT, _Traits>* _M_sbuf;
>> + bool _M_write_failed = false;
>> +
>> + _GLIBCXX_CONSTEXPR_FORMAT bool
>> + _M_on_stack() const noexcept
>> + { return this->_M_used().data() == this->_M_buf; }
>> +
>> + // Switch to _M_buf when the streambuf has no put area
>> + // or we need to stop using it.
>> + _GLIBCXX_CONSTEXPR_FORMAT void
>> + _M_use_stackbuf()
>> + { this->_M_reset(_M_buf); }
>> +
>> + // Try to point our span directly into the streambuf's put
>> + // area for zero-copy writes. Caller must fall back to
>> + // _M_use_stackbuf() on failure.
>> + _GLIBCXX_CONSTEXPR_FORMAT bool
>> + _M_use_put_area(size_t __n = 0)
>> + {
>> + if (auto __p = _M_sbuf->pptr())
>> + {
>> + auto __e = _M_sbuf->epptr();
>> + if (__e && __e > __p
>> + && static_cast<size_t>(__e - __p) >= __n)
>> + {
>> + this->_M_reset(
>> + span<_CharT>{__p, static_cast<size_t>(__e - __p)});
>> + return true;
>> + }
>> + }
>> + return false;
>> + }
>> +
>> + // Allow derived classes to commit to the put area
>> + // without needing friendship to basic_streambuf.
>> + _GLIBCXX_CONSTEXPR_FORMAT void
>> + _M_pbump(streamsize __n)
>> + { _M_sbuf->__safe_pbump(__n); }
> Is tehre any reason to use internal __safe_pbump instead of simply pbump here?
void pbump(int __n);
void __safe_pbump(streamsize __n); // streamsize is ptrdiff_t
Caller passes __s.size() (size_t), so __safe_pbump avoids a narrowing
conversion. Otherwise they're identical.
>> +
>> + // Write characters to the streambuf, tracking errors.
>> + // I/O exceptions from sputn are caught and converted to
>> + // _M_write_failed so they do not propagate as vformat exceptions
>> + // per [ostream.formatted.print]/(4.2). Does not refresh the
>> + // span; callers use _M_use_put_area/stackbuf.
>> + _GLIBCXX_CONSTEXPR_FORMAT void
>> + _M_flush(span<_CharT> __s)
>> + {
>> + if (__s.empty() || _M_write_failed) [[unlikely]]
>> + return;
>> + __try {
>> + if (_M_on_stack())
>> + {
>> + if (_M_sbuf->sputn(__s.data(), __s.size())
>> + != static_cast<streamsize>(__s.size()))
>> + _M_write_failed = true;
>> + }
>> + else
>> + _M_pbump(__s.size());
>> + }
>> + __catch(const __cxxabiv1::__forced_unwind&)
>> + { throw; }
>> + __catch(...)
>> + { _M_write_failed = true; }
>> + }
>> +
>> + _GLIBCXX_CONSTEXPR_FORMAT void
>> + _M_overflow() override
>> + {
>> + _M_flush(this->_M_used());
>> + if (!_M_use_put_area())
>> + _M_use_stackbuf();
>> + }
>> +
>> + public:
>> + [[__gnu__::__always_inline__]]
>> + _GLIBCXX_CONSTEXPR_FORMAT explicit
>> + _Streambuf_sink(basic_streambuf<_CharT, _Traits>* __sbuf) noexcept
>> + : _M_sbuf(__sbuf)
>> + { }
>> +
>> + using _Sink<_CharT>::out;
>> +
>> + // _M_bump is not overridden: the default advances _M_next within
>> + // the current span (stack buffer or put area). Actual commit
>> + // to the streambuf happens via _M_flush, called from _M_overflow
>> + // (buffer full) or _M_finish (end of formatting).
>> +
>> + _GLIBCXX_CONSTEXPR_FORMAT typename _Sink<_CharT>::_Reservation
>> + _M_reserve(size_t __n) override
>> + {
>> + if (__n <= this->_M_unused().size())
>> + return { this };
>> +
>> + if (!this->_M_used().empty())
>> + _M_overflow();
>> +
>> + // Try to write directly into the streambuf's put area.
>> + if (_M_use_put_area(__n))
>> + return { this };
>> +
>> + // Otherwise reset to the stack buffer.
>> + _M_use_stackbuf();
>> + if (__n <= this->_M_unused().size())
>> + return { this };
>> +
>> + return { nullptr };
>> + }
>> +
>> + _GLIBCXX_CONSTEXPR_FORMAT bool
>> + _M_discarding() const override
>> + { return _M_write_failed; }
>> +
>> + _GLIBCXX_CONSTEXPR_FORMAT void
>> + _M_finish() &&
>> + { _M_flush(this->_M_used()); }
>> +
>> + _GLIBCXX_CONSTEXPR_FORMAT bool
>> + _M_failed() const noexcept
>> + { return _M_write_failed; }
>> + };
>> +
>> using _GLIBCXX_STD_C::vector;
>>
>> // A sink that fills a sequence (e.g. std::string, std::vector, std::deque).
>> diff --git a/libstdc++-v3/include/std/streambuf b/libstdc++-v3/include/std/streambuf
>> index 616e44f74..22b505228 100644
>> --- a/libstdc++-v3/include/std/streambuf
>> +++ b/libstdc++-v3/include/std/streambuf
>> @@ -57,6 +57,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>> __copy_streambufs_eof(basic_streambuf<_CharT, _Traits>*,
>> basic_streambuf<_CharT, _Traits>*, bool&);
>>
>> + namespace __format { template<typename, typename> class _Streambuf_sink; }
>> +
>> /**
>> * @brief The actual work of input and output (interface).
>> * @ingroup io
>> @@ -149,6 +151,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>> friend class basic_ostream<char_type, traits_type>;
>> friend class istreambuf_iterator<char_type, traits_type>;
>> friend class ostreambuf_iterator<char_type, traits_type>;
>> + template<typename, typename>
>> + friend class __format::_Streambuf_sink;
>>
>> friend streamsize
>> __copy_streambufs_eof<>(basic_streambuf*, basic_streambuf*, bool&);
>> --
>> 2.34.1
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260812/f9752be5/attachment-0001.htm>
More information about the Libstdc++
mailing list