[v4,3/4] libstdc++: Specialize _Iter_sink for ostreambuf_iterator

Message ID 20261002125052.1107743-1-tkaminsk@redhat.com
State Committed
Commit 0876c8b7f4ed876e2beba17bc91438934543896b
Headers
Series None |

Commit Message

Tomasz Kaminski Oct. 2, 2026, 12:49 p.m. UTC
  From: Anlai Lu <agicy@qq.com>

Add partial specialization of _Iter_sink for ostreambuf_iterator
that inherits _Streambuf_sink, replacing per-character sputc with
bulk sputn and zero-copy put-area writes. As _Streambuf_sink this
specialization is limited only to std::char_traits.

All counting and truncation (_M_max) is handled in this
specialization so that _Streambuf_sink stays a pure I/O layer.
_M_overflow counts all characters and only writes up to the limit,
so format_to_n can compute the total output length. _M_discarding
returns false for the same reason. The _M_write_failed is propagated
to returned iterator on _M_finish.

The maximum count uses size_t with _S_no_limit sentinel, matching
the _Ptr_sink convention and avoiding signed comparisons.

libstdc++-v3/ChangeLog:

	* include/bits/streambuf_iterator.h (__format::_Iter_sink)
	[__glibcxx_format]: Declare with specialization for
	ostreambuf_iterator.
	(std::ostreambuf_iterator) [__glibcxx_format]: Befriend
	__format::_Iter_sink for ostreambuf_iterator.
	* include/std/format (_Iter_sink<_CharT, ostreambuf_iterator<...>>):
	Define new partial specialization.

Co-authored-by: Tomasz Kamiński <tkaminsk@redhat.com>
Signed-off-by: Anlai Lu <agicy@qq.com>
---
v4:
 * removes _Traits parameter and updates friend declaration accordingly.

Testing on x86_64-linux. *format* already passed.
OK for trunk when all test passes?

 .../include/bits/streambuf_iterator.h         | 12 +++
 libstdc++-v3/include/std/format               | 83 +++++++++++++++++++
 2 files changed, 95 insertions(+)
  

Comments

Jonathan Wakely Oct. 2, 2026, 12:55 p.m. UTC | #1
On Fri, 02 Oct 2026 at 14:49 +0200, Tomasz Kamiński wrote:
>From: Anlai Lu <agicy@qq.com>
>
>Add partial specialization of _Iter_sink for ostreambuf_iterator
>that inherits _Streambuf_sink, replacing per-character sputc with
>bulk sputn and zero-copy put-area writes. As _Streambuf_sink this
>specialization is limited only to std::char_traits.
>
>All counting and truncation (_M_max) is handled in this
>specialization so that _Streambuf_sink stays a pure I/O layer.
>_M_overflow counts all characters and only writes up to the limit,
>so format_to_n can compute the total output length. _M_discarding
>returns false for the same reason. The _M_write_failed is propagated
>to returned iterator on _M_finish.
>
>The maximum count uses size_t with _S_no_limit sentinel, matching
>the _Ptr_sink convention and avoiding signed comparisons.
>
>libstdc++-v3/ChangeLog:
>
>	* include/bits/streambuf_iterator.h (__format::_Iter_sink)
>	[__glibcxx_format]: Declare with specialization for
>	ostreambuf_iterator.
>	(std::ostreambuf_iterator) [__glibcxx_format]: Befriend
>	__format::_Iter_sink for ostreambuf_iterator.
>	* include/std/format (_Iter_sink<_CharT, ostreambuf_iterator<...>>):
>	Define new partial specialization.
>
>Co-authored-by: Tomasz Kamiński <tkaminsk@redhat.com>
>Signed-off-by: Anlai Lu <agicy@qq.com>
>---
>v4:
> * removes _Traits parameter and updates friend declaration accordingly.
>
>Testing on x86_64-linux. *format* already passed.
>OK for trunk when all test passes?

OK thanks

> .../include/bits/streambuf_iterator.h         | 12 +++
> libstdc++-v3/include/std/format               | 83 +++++++++++++++++++
> 2 files changed, 95 insertions(+)
>
>diff --git a/libstdc++-v3/include/bits/streambuf_iterator.h b/libstdc++-v3/include/bits/streambuf_iterator.h
>index 095928ca4d8..8a881730c9f 100644
>--- a/libstdc++-v3/include/bits/streambuf_iterator.h
>+++ b/libstdc++-v3/include/bits/streambuf_iterator.h
>@@ -42,6 +42,14 @@ namespace std _GLIBCXX_VISIBILITY(default)
> {
> _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
>+#ifdef __glibcxx_format // C++ >= 20 && HOSTED
>+  namespace __format {
>+    template<typename, typename> class _Iter_sink;
>+    template<typename _CharT>
>+      class _Iter_sink<_CharT, ostreambuf_iterator<_CharT>>;
>+  }
>+#endif
>+
>   /**
>    * @addtogroup iterators
>    * @{
>@@ -266,6 +274,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> 	copy(istreambuf_iterator<_CharT2>, istreambuf_iterator<_CharT2>,
> 	     ostreambuf_iterator<_CharT2>);
>
>+#ifdef __glibcxx_format // C++ >= 20 && HOSTED
>+      friend class __format::_Iter_sink<char_type, ostreambuf_iterator<char_type>>;
>+#endif
>+
>     private:
>       streambuf_type*	_M_sbuf;
>       bool		_M_failed;
>diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
>index c7762766909..f1cd49dd98a 100644
>--- a/libstdc++-v3/include/std/format
>+++ b/libstdc++-v3/include/std/format
>@@ -3865,6 +3865,89 @@ namespace __format
>       }
>     };
>
>+  // Specialization replacing per-character sputc with bulk sputn
>+  // and zero-copy writes into the streambuf's put area.
>+  template<typename _CharT>
>+    class _Iter_sink<_CharT, ostreambuf_iterator<_CharT>>
>+    : public _Streambuf_sink<_CharT>
>+    {
>+      using _Base = _Streambuf_sink<_CharT>;
>+      using _OutIter = ostreambuf_iterator<_CharT>;
>+
>+      static constexpr size_t _S_no_limit = size_t(-1);
>+
>+      void
>+      _M_trim_buf()
>+      {
>+	const size_t __avail = _M_max - _M_count;
>+	if (this->_M_unused().size() > __avail)
>+	  this->_M_reset(this->_M_unused().first(__avail));
>+      }
>+
>+    protected:
>+      size_t _M_max = _S_no_limit;
>+      size_t _M_count = 0;
>+
>+      void
>+      _M_overflow() override
>+      {
>+	const size_t __new = this->_M_used().size();
>+	const size_t __pcount = _M_count;
>+	_M_count += __new;
>+
>+	if (__pcount >= _M_max)
>+	  this->_M_reset(this->_M_buf);
>+	else if (size_t __avail = _M_max - __pcount; __avail > __new)
>+	  {
>+	    _Base::_M_overflow();
>+	    _M_trim_buf();
>+	  }
>+	else
>+	  {
>+	    this->_M_flush();
>+	    this->_M_reset(this->_M_buf);
>+	  }
>+      }
>+
>+      bool
>+      _M_discarding() const override
>+      { return false; }
>+
>+    public:
>+      [[__gnu__::__always_inline__]]
>+      explicit
>+      _Iter_sink(_OutIter __out)
>+      : _Base(__out._M_sbuf)
>+      { }
>+
>+      [[__gnu__::__always_inline__]]
>+      explicit
>+      _Iter_sink(_OutIter __out, iter_difference_t<_OutIter> __max)
>+      : _Base(__out._M_sbuf), _M_max(__max < 0 ? _S_no_limit : size_t(__max))
>+      { _M_trim_buf(); }
>+
>+      typename _Sink<_CharT>::_Reservation
>+      _M_reserve(size_t __n) override
>+      {
>+	if (_M_count < _M_max)
>+	  if (size_t __avail = _M_max - _M_count; __avail >= __n)
>+	    return _Base::_M_reserve(__n);
>+	return { nullptr };
>+      }
>+
>+      format_to_n_result<_OutIter>
>+      _M_finish() &&
>+      {
>+	_M_count += this->_M_used().size();
>+	if (_M_count <= _M_max)
>+	  this->_M_flush();
>+
>+	_OutIter __out(this->_M_sbuf);
>+	__out._M_failed = this->_M_write_failed;
>+	return { std::move(__out), ptrdiff_t(_M_count) };
>+      }
>+    };
>+
>   // Used for contiguous iterators.
>   // No buffer is used, characters are written straight to the iterator.
>   // We do not know the size of the output range, so the span size just grows
>-- 
>2.55.0
>
>
  

Patch

diff --git a/libstdc++-v3/include/bits/streambuf_iterator.h b/libstdc++-v3/include/bits/streambuf_iterator.h
index 095928ca4d8..8a881730c9f 100644
--- a/libstdc++-v3/include/bits/streambuf_iterator.h
+++ b/libstdc++-v3/include/bits/streambuf_iterator.h
@@ -42,6 +42,14 @@  namespace std _GLIBCXX_VISIBILITY(default)
 {
 _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
+#ifdef __glibcxx_format // C++ >= 20 && HOSTED
+  namespace __format {
+    template<typename, typename> class _Iter_sink;
+    template<typename _CharT>
+      class _Iter_sink<_CharT, ostreambuf_iterator<_CharT>>;
+  }
+#endif
+
   /**
    * @addtogroup iterators
    * @{
@@ -266,6 +274,10 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	copy(istreambuf_iterator<_CharT2>, istreambuf_iterator<_CharT2>,
 	     ostreambuf_iterator<_CharT2>);
 
+#ifdef __glibcxx_format // C++ >= 20 && HOSTED
+      friend class __format::_Iter_sink<char_type, ostreambuf_iterator<char_type>>;
+#endif
+
     private:
       streambuf_type*	_M_sbuf;
       bool		_M_failed;
diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
index c7762766909..f1cd49dd98a 100644
--- a/libstdc++-v3/include/std/format
+++ b/libstdc++-v3/include/std/format
@@ -3865,6 +3865,89 @@  namespace __format
       }
     };
 
+  // Specialization replacing per-character sputc with bulk sputn
+  // and zero-copy writes into the streambuf's put area.
+  template<typename _CharT>
+    class _Iter_sink<_CharT, ostreambuf_iterator<_CharT>>
+    : public _Streambuf_sink<_CharT>
+    {
+      using _Base = _Streambuf_sink<_CharT>;
+      using _OutIter = ostreambuf_iterator<_CharT>;
+
+      static constexpr size_t _S_no_limit = size_t(-1);
+
+      void
+      _M_trim_buf()
+      {
+	const size_t __avail = _M_max - _M_count;
+	if (this->_M_unused().size() > __avail)
+	  this->_M_reset(this->_M_unused().first(__avail));
+      }
+
+    protected:
+      size_t _M_max = _S_no_limit;
+      size_t _M_count = 0;
+
+      void
+      _M_overflow() override
+      {
+	const size_t __new = this->_M_used().size();
+	const size_t __pcount = _M_count;
+	_M_count += __new;
+
+	if (__pcount >= _M_max)
+	  this->_M_reset(this->_M_buf);
+	else if (size_t __avail = _M_max - __pcount; __avail > __new)
+	  {
+	    _Base::_M_overflow();
+	    _M_trim_buf();
+	  }
+	else
+	  {
+	    this->_M_flush();
+	    this->_M_reset(this->_M_buf);
+	  }
+      }
+
+      bool
+      _M_discarding() const override
+      { return false; }
+
+    public:
+      [[__gnu__::__always_inline__]]
+      explicit
+      _Iter_sink(_OutIter __out)
+      : _Base(__out._M_sbuf)
+      { }
+
+      [[__gnu__::__always_inline__]]
+      explicit
+      _Iter_sink(_OutIter __out, iter_difference_t<_OutIter> __max)
+      : _Base(__out._M_sbuf), _M_max(__max < 0 ? _S_no_limit : size_t(__max))
+      { _M_trim_buf(); }
+
+      typename _Sink<_CharT>::_Reservation
+      _M_reserve(size_t __n) override
+      {
+	if (_M_count < _M_max)
+	  if (size_t __avail = _M_max - _M_count; __avail >= __n)
+	    return _Base::_M_reserve(__n);
+	return { nullptr };
+      }
+
+      format_to_n_result<_OutIter>
+      _M_finish() &&
+      {
+	_M_count += this->_M_used().size();
+	if (_M_count <= _M_max)
+	  this->_M_flush();
+
+	_OutIter __out(this->_M_sbuf);
+	__out._M_failed = this->_M_write_failed;
+	return { std::move(__out), ptrdiff_t(_M_count) };
+      }
+    };
+
   // Used for contiguous iterators.
   // No buffer is used, characters are written straight to the iterator.
   // We do not know the size of the output range, so the span size just grows