[PATCH v2 3/4] libstdc++: Specialize _Iter_sink for ostreambuf_iterator
Tomasz Kaminski
tkaminsk@redhat.com
Tue Aug 18 07:58:07 GMT 2026
On Tue, Aug 18, 2026 at 9:11 AM Anlai Lu <agicy@qq.com> wrote:
>
>
> > On Aug 18, 2026, at 14:18, Anlai Lu <agicy@qq.com> wrote:
> >
> > Hi Tomasz,
> >
> > Thanks for the patch. I have reviewed it previously but just now I ran
> > some additional tests. During testing, I found two issues that should
> > be fixed before this is merged.
> >
> >> On Aug 11, 2026, at 00:11, Tomasz Kamiński <tkaminsk@redhat.com> 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.
> >>
> >> 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. Stack writes go through
> >> _M_out._M_put() which tracks failure on the iterator. sputn
> >> exceptions are caught and the iterator is marked failed via
> >> _M_set_failed().
> >>
> >> 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>
> >> Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
> >> ---
> >> Changes in v2:
> >> - limit unused/reserved buffer to the remaining characters
> >> in _M_overflow/_M_reserve and constructor. Mostly via
> >> calling _M_trim_buf. Rewrote _M_overflow in process
> >> - overloads constructor without limit to avoid above
> >> - befriend _Iter_sink specialization with ostreambuf_iterator
> >> - remove _M_out member and recosntructor iterator in _M_finish
> >>
> >> Testing on powerprc64. Tested on x86_64, additionally tested
> >> *format* and *print* in all standard modes, assertions and
> >> debug. OK for trunk?
> >>
> >> .../include/bits/streambuf_iterator.h | 12 +++
> >> libstdc++-v3/include/std/format | 82 +++++++++++++++++++
> >> 2 files changed, 94 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 9223cd2e78b..ad6a56ecc3a 100644
> >> --- a/libstdc++-v3/include/std/format
> >> +++ b/libstdc++-v3/include/std/format
> >> @@ -3847,6 +3847,88 @@ 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();
> >> + if (_M_count >= _M_max)
> >> + this->_M_reset(this->_M_buf);
> >> + else if (size_t __a = _M_max - _M_count; __a > __new)
> >> + {
> >> + _Base::_M_overflow();
> >> + _M_trim_buf();
> >
> > _M_trim_buf() depends on the value of _M_count. In this else-if
> > branch it is called before _M_count is updated, which leads to an
> > incorrect trim.
> >
> >> + }
> >> + else
> >> + {
> >> + this->_M_flush();
> >> + this->_M_reset(this->_M_buf);
> >> + }
> >> + _M_count += __new;
> >> + }
> >> +
> >> + 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 __a = _M_max - _M_count; __a >= __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();
> >
> > This condition should be _M_count <= _M_max.
> > When _M_count becomes exactly _M_max, there may still be data
> > that has not been flushed.
> >
> >> +
> >> + _OutIter __out;
> >> + __out._M_sbuf = 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
> >
> > Two issues above can be covered by this test case:
> >
> > std::string big(257, 'A');
> > std::ostringstream os;
> > auto res = std::format_to_n(std::ostreambuf_iterator<char>(os),
> > 257, "{}", big);
> > VERIFY(res.size == 257);
> > VERIFY(os.view() == big);
> >
> > The existing test suite does not cover this exact-boundary case, which
> > is why these issues were not caught earlier. The existing cases in
> > test_format_to_n have a (len=1000, limit=256) and (len=8, limit=8), but
> > does not cover the case (len > 256 && len = limit).
>
> Quick follow up:
>
> I previously wrote that the (len=8, limit=8) test passes because 8 < 256.
> That is not the full picture. The real reason is that the len=limit=8
> case uses {}{}, "AAAA", "BBBB", which has intermediate flush.
>
> The second issue can be found with just
> std::format_to_n(oit, 1, "{}", "A");
>
> Sorry for the confusion in my earlier analysis.
>
Thank you very much, I will post an updated version of first patch
(including
new test) and this patch soon.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260818/fb29f287/attachment-0001.htm>
More information about the Libstdc++
mailing list