[v4,3/4] libstdc++: Specialize _Iter_sink for ostreambuf_iterator
Commit Message
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
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
>
>
@@ -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;
@@ -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