Hi Tomasz, I have reviewed the whole series. The fixes for the issues I reported are correct and the new tests look good. LGTM.
Thanks, Anlai > On Aug 18, 2026, at 16:07, Tomasz Kamiński <[email protected]> wrote: > > From: Anlai Lu <[email protected]> > > 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. > > 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 <[email protected]> > Signed-off-by: Anlai Lu <[email protected]> > Signed-off-by: Tomasz Kamiński <[email protected]> > --- > v3 fixes the _M_trim_buf and _M_flush issue pointed > by Anlai Liu, and updates commit description. > > .../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..d715edc24ae 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, typename _Traits> > + class _Iter_sink<_CharT, ostreambuf_iterator<_CharT, _Traits>>; > + } > +#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>; > +#endif > + > private: > streambuf_type* _M_sbuf; > bool _M_failed; > diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format > index f1698c3eb68..e8b1823d0d8 100644 > --- a/libstdc++-v3/include/std/format > +++ b/libstdc++-v3/include/std/format > @@ -3849,6 +3849,89 @@ namespace __format > } > }; > > + // Specialization replacing per-character sputc with bulk sputn > + // and zero-copy writes into the streambuf's put area. > + template<typename _CharT, typename _Traits> > + class _Iter_sink<_CharT, ostreambuf_iterator<_CharT, _Traits>> > + : public _Streambuf_sink<_CharT, _Traits> > + { > + using _Base = _Streambuf_sink<_CharT, _Traits>; > + using _OutIter = ostreambuf_iterator<_CharT, _Traits>; > + > + 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
