[PATCH v2 2/4] libstdc++: Add _Streambuf_sink for direct streambuf formatting

Tomasz Kamiński tkaminsk@redhat.com
Mon Aug 10 15:39:02 GMT 2026


From: Anlai Lu <agicy@qq.com>

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 <tkaminsk@redhat.com>
Signed-off-by: Anlai Lu <agicy@qq.com>
Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
---
Changes in v2:
 - call _M_reserve_put_area in _Streambuf_sink constructor,
   poiting buffer to put area 
 - ostreambuf_iterator: Remove the changes, that were unrelated
   to standard
 - remove _GLIBCXX_CONSTEXPR_FORMAT, as they classes cannot
   be used at compile time
 - remove and inline _M_pbump and _M_use_stackbuf, rename
   _M_use_put_area to _M_reserve_put_area
 - done some formatting changes

Testing on powerprc64. Tested on x86_64, additionally tested
*format* and *print* in all standard modes, assertions and
debug. OK for trunk?

 libstdc++-v3/include/std/format    | 110 +++++++++++++++++++++++++++++
 libstdc++-v3/include/std/streambuf |   9 +++
 2 files changed, 119 insertions(+)

diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
index 729cb89ec60..9223cd2e78b 100644
--- a/libstdc++-v3/include/std/format
+++ b/libstdc++-v3/include/std/format
@@ -3532,6 +3532,116 @@ 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.
+      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());
+	    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_reserve_put_area() || _M_write_failed)
+	  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(); }
+
+      // _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



More information about the Libstdc++ mailing list