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

Reply via email to