On Fri, Oct 2, 2026 at 12:34 PM Jonathan Wakely <[email protected]> wrote:
> On Fri, 02 Oct 2026 at 11:24 +0100, Jonathan Wakely wrote: > >On Tue, 18 Aug 2026 at 10:10 +0200, Tomasz Kamiński 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]> > >>--- > >>v3 makes the !_M_write_failed branch likely, as send previously. > >> > >>libstdc++-v3/include/std/format | 112 +++++++++++++++++++++++++++++ > >>libstdc++-v3/include/std/streambuf | 9 +++ > >>2 files changed, 121 insertions(+) > >> > >>diff --git a/libstdc++-v3/include/std/format > b/libstdc++-v3/include/std/format > >>index 729cb89ec60..f1698c3eb68 100644 > >>--- a/libstdc++-v3/include/std/format > >>+++ b/libstdc++-v3/include/std/format > >>@@ -3532,6 +3532,118 @@ 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. > > There is no _M_use_stackbuf() member, this comment needs a fix. > > >>+ 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()); > > > >We cannot assume the existence of the __safe_pbump member except for > >the std::streambuf and std::wstreambuf specializations. A > >program-defined specialization of std::basic_streambuf<char, MyTraits> > >would not have that member and would not declare _Streambuf_sink as a > >friend. > > > >I think we can only use this sink when _Traits is > >std::char_traits<_CharT>. > > > >And that means that _Streambuf_sink doesn't need the _Traits > >parameter, and its _M_sbuf member can be basic_streambuf<_CharT> > >without the second template argument. > > > >Apart from that, this looks very nice. > > > >If you agree with my assessment, this is OK for trunk with _Traits > >removed from the template head, the _M_sbuf data member, and the > >constructor parameter. > > > > > >>+ 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_write_failed) [[likely]] > >>+ if (_M_reserve_put_area()) > >>+ return; > >>+ 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(); } > > If __sbuf is null (which ostreambuf_iterator allows) then Cwe must not > try to reserve the put area. > > Maybe it's best to handle this in _M_reserve_put_area() since it's > called from several places. It can just return false if _M_sbuf is > null. > I do not think we should accept creating _Streambuf_sink with nullptr, I will change this parameter to reference, and handle that case in the Iter_sink specialization. For this commit wil just remove Traits specialziation. > > This case needs to be tested, e.g. trying to format to a > default-constructed (i.e. past-the-end) ostreambuf_iterator. > > >>+ > >>+ // _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 > >> > >> > >
