<div dir="ltr"><div dir="ltr"><br></div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">On Thu, Jul 16, 2026 at 2:04 PM Anlai Lu <<a href="mailto:agicy@qq.com">agicy@qq.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Introduce _Streambuf_sink that writes directly to basic_streambuf<br>
via sputn, preferring zero-copy writes into the streambuf put area<br>
(pptr/epptr/pbump) and falling back to the stack buffer and bulk<br>
sputn.<br>
<br>
Streambuf I/O errors are tracked internally (_M_write_failed) and<br>
reported after formatting completes, so that format-phase exceptions<br>
(format_error, bad_alloc) propagate without setting badbit per<br>
[ostream.formatted.print]/(4.2).<br>
<br>
The _Sink_state enum is omitted in favor of _M_on_stack() which<br>
tests whether the current span points at the stack buffer.<br>
<br>
libstdc++-v3/ChangeLog:<br>
<br>
        * include/bits/streambuf_iterator.h (ostreambuf_iterator):<br>
        Add internal _M_get_sbuf() and _M_set_failed() members.<br>
        * include/std/format: Include <bits/streambuf_iterator.h>.<br>
        (__format::_Streambuf_sink): New class template.<br>
        * include/std/streambuf: Forward-declare __format::_Streambuf_sink,<br>
        add friend declaration to basic_streambuf.<br>
<br>
Signed-off-by: Anlai Lu <<a href="mailto:agicy@qq.com" target="_blank">agicy@qq.com</a>><br>
---<br>
 .../include/bits/streambuf_iterator.h         |  11 ++<br>
 libstdc++-v3/include/std/format               | 132 ++++++++++++++++++<br>
 libstdc++-v3/include/std/streambuf            |   4 +<br>
 3 files changed, 147 insertions(+)<br>
<br>
diff --git a/libstdc++-v3/include/bits/streambuf_iterator.h b/libstdc++-v3/include/bits/streambuf_iterator.h<br>
index 095928ca4..d919fdbb8 100644<br>
--- a/libstdc++-v3/include/bits/streambuf_iterator.h<br>
+++ b/libstdc++-v3/include/bits/streambuf_iterator.h<br>
@@ -318,6 +318,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION<br>
       failed() const _GLIBCXX_USE_NOEXCEPT<br>
       { return _M_failed; }<br>
<br>
+      /// @cond internal<br>
+      _GLIBCXX_NODISCARD<br>
+      streambuf_type*<br>
+      _M_get_sbuf() const _GLIBCXX_USE_NOEXCEPT<br>
+      { return _M_sbuf; }<br>
+<br>
+      void<br>
+      _M_set_failed() _GLIBCXX_USE_NOEXCEPT<br>
+      { _M_failed = true; }<br>
+      /// @endcond<br>
+<br>
       ostreambuf_iterator&<br>
       _M_put(const _CharT* __ws, streamsize __len)<br>
       {<br>
diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format<br>
index 9bf5e7857..b676629b1 100644<br>
--- a/libstdc++-v3/include/std/format<br>
+++ b/libstdc++-v3/include/std/format<br>
@@ -59,6 +59,7 @@<br>
 #include <bits/ranges_util.h>  // subrange<br>
 #include <bits/ranges_algobase.h> // ranges::copy<br>
 #include <bits/stl_iterator.h> // counted_iterator<br>
+#include <bits/streambuf_iterator.h> // ostreambuf_iterator, basic_streambuf<br>
 #include <bits/stl_pair.h>     // __is_pair<br>
 #include <bits/unicode.h>      // __is_scalar_value, _Utf_view, etc.<br>
 #include <bits/utility.h>      // tuple_size_v<br>
@@ -3530,6 +3531,137 @@ namespace __format<br>
       { }<br>
     };<br>
<br>
+  // A format sink that writes directly to a basic_streambuf.<br>
+  // Prefers zero-copy writes into the streambuf's put area<br>
+  // (pptr/epptr/pbump), falling back to the stack buffer<br>
+  // (_M_buf) and bulk sputn.<br>
+  template<typename _CharT, typename _Traits = char_traits<_CharT>><br>
+    class _Streambuf_sink : public _Buf_sink<_CharT><br>
+    {<br>
+      using _Buf_sink<_CharT>::_M_buf;<br>
+<br>
+    protected:<br>
+      basic_streambuf<_CharT, _Traits>* _M_sbuf;<br>
+      bool _M_write_failed = false;<br>
+<br>
+      _GLIBCXX_CONSTEXPR_FORMAT bool<br>
+      _M_on_stack() const noexcept<br>
+      { return this->_M_used().data() == this->_M_buf; }<br>
+<br>
+      // Switch to _M_buf when the streambuf has no put area<br>
+      // or we need to stop using it.<br>
+      _GLIBCXX_CONSTEXPR_FORMAT void<br>
+      _M_use_stackbuf()<br>
+      { this->_M_reset(_M_buf); }<br>
+<br>
+      // Try to point our span directly into the streambuf's put<br>
+      // area for zero-copy writes.  Caller must fall back to<br>
+      // _M_use_stackbuf() on failure.<br>
+      _GLIBCXX_CONSTEXPR_FORMAT bool<br>
+      _M_use_put_area(size_t __n = 0)<br>
+      {<br>
+       if (auto __p = _M_sbuf->pptr())<br>
+         {<br>
+           auto __e = _M_sbuf->epptr();<br>
+           if (__e && __e > __p<br>
+               && static_cast<size_t>(__e - __p) >= __n)<br>
+             {<br>
+               this->_M_reset(<br>
+                 span<_CharT>{__p, static_cast<size_t>(__e - __p)});<br>
+               return true;<br>
+             }<br>
+         }<br>
+       return false;<br>
+      }<br>
+<br>
+      // Allow derived classes to commit to the put area<br>
+      // without needing friendship to basic_streambuf.<br>
+      _GLIBCXX_CONSTEXPR_FORMAT void<br>
+      _M_pbump(streamsize __n)<br>
+      { _M_sbuf->__safe_pbump(__n); }<br></blockquote><div>Is tehre any reason to use internal __safe_pbump instead of simply pbump here? </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+<br>
+      // Write characters to the streambuf, tracking errors.<br>
+      // I/O exceptions from sputn are caught and converted to<br>
+      // _M_write_failed so they do not propagate as vformat exceptions<br>
+      // per [ostream.formatted.print]/(4.2).  Does not refresh the<br>
+      // span; callers use _M_use_put_area/stackbuf.<br>
+      _GLIBCXX_CONSTEXPR_FORMAT void<br>
+      _M_flush(span<_CharT> __s)<br>
+      {<br>
+       if (__s.empty() || _M_write_failed) [[unlikely]]<br>
+         return;<br>
+       __try {<br>
+         if (_M_on_stack())<br>
+           {<br>
+             if (_M_sbuf->sputn(__s.data(), __s.size())<br>
+                 != static_cast<streamsize>(__s.size()))<br>
+               _M_write_failed = true;<br>
+           }<br>
+         else<br>
+           _M_pbump(__s.size());<br>
+       }<br>
+       __catch(const __cxxabiv1::__forced_unwind&)<br>
+         { throw; }<br>
+       __catch(...)<br>
+         { _M_write_failed = true; }<br>
+      }<br>
+<br>
+      _GLIBCXX_CONSTEXPR_FORMAT void<br>
+      _M_overflow() override<br>
+      {<br>
+       _M_flush(this->_M_used());<br>
+       if (!_M_use_put_area())<br>
+         _M_use_stackbuf();<br>
+      }<br>
+<br>
+    public:<br>
+      [[__gnu__::__always_inline__]]<br>
+      _GLIBCXX_CONSTEXPR_FORMAT explicit<br>
+      _Streambuf_sink(basic_streambuf<_CharT, _Traits>* __sbuf) noexcept<br>
+      : _M_sbuf(__sbuf)<br>
+      { }<br>
+<br>
+      using _Sink<_CharT>::out;<br>
+<br>
+      // _M_bump is not overridden: the default advances _M_next within<br>
+      // the current span (stack buffer or put area).  Actual commit<br>
+      // to the streambuf happens via _M_flush, called from _M_overflow<br>
+      // (buffer full) or _M_finish (end of formatting).<br>
+<br>
+      _GLIBCXX_CONSTEXPR_FORMAT typename _Sink<_CharT>::_Reservation<br>
+      _M_reserve(size_t __n) override<br>
+      {<br>
+       if (__n <= this->_M_unused().size())<br>
+         return { this };<br>
+<br>
+       if (!this->_M_used().empty())<br>
+         _M_overflow();<br>
+<br>
+       // Try to write directly into the streambuf's put area.<br>
+       if (_M_use_put_area(__n))<br>
+         return { this };<br>
+<br>
+       // Otherwise reset to the stack buffer.<br>
+       _M_use_stackbuf();<br>
+       if (__n <= this->_M_unused().size())<br>
+         return { this };<br>
+<br>
+       return { nullptr };<br>
+      }<br>
+<br>
+      _GLIBCXX_CONSTEXPR_FORMAT bool<br>
+      _M_discarding() const override<br>
+      { return _M_write_failed; }<br>
+<br>
+      _GLIBCXX_CONSTEXPR_FORMAT void<br>
+      _M_finish() &&<br>
+      { _M_flush(this->_M_used()); }<br>
+<br>
+      _GLIBCXX_CONSTEXPR_FORMAT bool<br>
+      _M_failed() const noexcept<br>
+      { return _M_write_failed; }<br>
+    };<br>
+<br>
   using _GLIBCXX_STD_C::vector;<br>
<br>
   // A sink that fills a sequence (e.g. std::string, std::vector, std::deque).<br>
diff --git a/libstdc++-v3/include/std/streambuf b/libstdc++-v3/include/std/streambuf<br>
index 616e44f74..22b505228 100644<br>
--- a/libstdc++-v3/include/std/streambuf<br>
+++ b/libstdc++-v3/include/std/streambuf<br>
@@ -57,6 +57,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION<br>
     __copy_streambufs_eof(basic_streambuf<_CharT, _Traits>*,<br>
                          basic_streambuf<_CharT, _Traits>*, bool&);<br>
<br>
+  namespace __format { template<typename, typename> class _Streambuf_sink; }<br>
+<br>
   /**<br>
    *  @brief  The actual work of input and output (interface).<br>
    *  @ingroup io<br>
@@ -149,6 +151,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION<br>
       friend class basic_ostream<char_type, traits_type>;<br>
       friend class istreambuf_iterator<char_type, traits_type>;<br>
       friend class ostreambuf_iterator<char_type, traits_type>;<br>
+      template<typename, typename><br>
+       friend class __format::_Streambuf_sink;<br>
<br>
       friend streamsize<br>
       __copy_streambufs_eof<>(basic_streambuf*, basic_streambuf*, bool&);<br>
-- <br>
2.34.1<br>
<br>
</blockquote></div></div>