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

Reply via email to