On Fri, Oct 2, 2026 at 12:57 PM Jonathan Wakely <[email protected]> wrote:
> On Fri, 2 Oct 2026 at 11:55, Tomasz Kaminski <[email protected]> wrote: > > > > > > > > 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. > > Yes, that makes sense. If the iterator is not dereferenceable, don't > construct a sink to write to it. > As I use it as base class for _Iter_sink that is really hard to do, so will follow your original suggestion and add to _M_reserve_put_area: if (!_M_sbuf) [[unlikely]] return false; This is unlikely are only no UB cases for this are: std::format_to(ostream_iterator(), ""); std::format_to_n(ostream_iterator(), 0, // any arguments); > > > For this commit wil just remove Traits specialziation. > > Thanks. > > >> > >> > >> 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 > >> >> > >> >> > >> > >
