On Mon, Aug 10, 2026 at 5:40 PM Tomasz Kamiński <[email protected]> wrote:
> From: Anlai Lu <[email protected]> > > 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 <[email protected]> > Signed-off-by: Anlai Lu <[email protected]> > Signed-off-by: Tomasz Kamiński <[email protected]> > --- > 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 > >
