[PATCH v2 2/4] libstdc++: Add _Streambuf_sink for direct streambuf formatting
Tomasz Kaminski
tkaminsk@redhat.com
Mon Aug 10 16:29:41 GMT 2026
On Mon, Aug 10, 2026 at 5:40 PM Tomasz Kamiński <tkaminsk@redhat.com> wrote:
> From: Anlai Lu <agicy@qq.com>
>
> 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).
>
> libstdc++-v3/ChangeLog:
>
> * include/std/format (__format::_Streambuf_sink): New class
> template.
> * include/std/streambuf: Include bits/version.h.
> (__format::_Streambuf_sink) [__glibcxx_format]: Declare.
> (std::basic_streambuf) [__glibcxx_format]: Befriend
> __format::_Streambuf_sink.
>
> Co-authored-by: Tomasz Kamiński <tkaminsk@redhat.com>
> Signed-off-by: Anlai Lu <agicy@qq.com>
> Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
> ---
> Changes in v2:
> - call _M_reserve_put_area in _Streambuf_sink constructor,
> poiting buffer to put area
> - ostreambuf_iterator: Remove the changes, that were unrelated
> to standard
> - remove _GLIBCXX_CONSTEXPR_FORMAT, as they classes cannot
> be used at compile time
> - remove and inline _M_pbump and _M_use_stackbuf, rename
> _M_use_put_area to _M_reserve_put_area
> - done some formatting changes
>
> Testing on powerprc64. Tested on x86_64, additionally tested
> *format* and *print* in all standard modes, assertions and
> debug. OK for trunk?
>
> libstdc++-v3/include/std/format | 110 +++++++++++++++++++++++++++++
> libstdc++-v3/include/std/streambuf | 9 +++
> 2 files changed, 119 insertions(+)
>
> diff --git a/libstdc++-v3/include/std/format
> b/libstdc++-v3/include/std/format
> index 729cb89ec60..9223cd2e78b 100644
> --- a/libstdc++-v3/include/std/format
> +++ b/libstdc++-v3/include/std/format
> @@ -3532,6 +3532,116 @@ 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>
> + {
> + protected:
> + basic_streambuf<_CharT, _Traits>* _M_sbuf;
> + bool _M_write_failed = false;
> +
> + bool
> + _M_on_stack() const noexcept
> + { return this->_M_used().data() == this->_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.
> + bool
> + _M_reserve_put_area(size_t __n = 0)
> + {
> + if (auto __p = _M_sbuf->pptr())
> + if (auto __e = _M_sbuf->epptr(); __e > __p)
> + if (size_t __a(__e - __p); __a >= __n)
> + {
> + this->_M_reset(span<_CharT>(__p, __a));
> + return true;
> + }
> + return false;
> + }
> +
> + // 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).
> + void
> + _M_flush()
> + {
> + span<const _CharT> __s = this->_M_used();
> + if (__s.empty() || _M_write_failed) [[unlikely]]
> + return;
> +
> + __try
> + {
> + if (!_M_on_stack())
> + _M_sbuf->__safe_pbump(__s.size());
> + else if (_M_sbuf->sputn(__s.data(), __s.size())
> + != static_cast<streamsize>(__s.size()))
> + _M_write_failed = true;
> + }
> + __catch(const __cxxabiv1::__forced_unwind&)
> + { throw; }
> + __catch(...)
> + { _M_write_failed = true; }
> + }
> +
> + void
> + _M_overflow() override
> + {
> + _M_flush();
> + if (!_M_reserve_put_area() || _M_write_failed)
>
We should check _M_write_failed first and make it unlikely.
> + this->_M_reset(this->_M_buf);
> + }
> +
> + public:
> + [[__gnu__::__always_inline__]]
> + explicit
> + _Streambuf_sink(basic_streambuf<_CharT, _Traits>* __sbuf) noexcept
> + : _M_sbuf(__sbuf)
> + { _M_reserve_put_area(); }
> +
> + // _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).
> +
> + typename _Sink<_CharT>::_Reservation
> + _M_reserve(size_t __n) override
> + {
> + if (__n <= this->_M_unused().size())
> + return { this };
> +
> + if (!this->_M_used().empty())
> + _M_flush();
> +
> + // Try to write directly into the streambuf's put area.
> + if (_M_reserve_put_area(__n))
> + return { this };
> +
> + // Otherwise reset to the stack buffer.
> + this->_M_reset(this->_M_buf);
> + if (__n <= this->_M_unused().size())
> + return { this };
> +
> + return { nullptr };
> + }
> +
> + bool
> + _M_discarding() const override
> + { return _M_write_failed; }
> +
> + void
> + _M_finish()
> + { _M_flush(); }
> +
> + 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 616e44f74a7..d273db2835f 100644
> --- a/libstdc++-v3/include/std/streambuf
> +++ b/libstdc++-v3/include/std/streambuf
> @@ -39,6 +39,8 @@
>
> #include <bits/requires_hosted.h> // iostreams
>
> +#include <bits/version.h>
> +
> #include <bits/c++config.h>
> #include <bits/iosfwd.h>
> #include <bits/localefwd.h>
> @@ -57,6 +59,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> __copy_streambufs_eof(basic_streambuf<_CharT, _Traits>*,
> basic_streambuf<_CharT, _Traits>*, bool&);
>
> +#ifdef __glibcxx_format // C++ >= 20 && HOSTED
> + namespace __format { template<typename, typename> class
> _Streambuf_sink; }
> +#endif
> +
> /**
> * @brief The actual work of input and output (interface).
> * @ingroup io
> @@ -149,6 +155,9 @@ _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>;
> +#ifdef __glibcxx_format // C++ >= 20 && HOSTED
> + friend class __format::_Streambuf_sink<char_type, traits_type>;
> +#endif
>
> friend streamsize
> __copy_streambufs_eof<>(basic_streambuf*, basic_streambuf*, bool&);
> --
> 2.55.0
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260810/cde5ef71/attachment-0001.htm>
More information about the Libstdc++
mailing list