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.
> 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
>> >>
>> >>
>>