On Thu, Jul 9, 2026 at 1:20 PM Anlai Lu <[email protected]> wrote:

> 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.
>
> libstdc++-v3/ChangeLog:
>
>         * include/std/format
>         (_Iter_sink<ostreambuf_iterator>): New partial specialization.
>
> Signed-off-by: Anlai Lu <[email protected]>
> ---
>  libstdc++-v3/include/std/format | 77 +++++++++++++++++++++++++++++++++
>  1 file changed, 77 insertions(+)
>
> diff --git a/libstdc++-v3/include/std/format
> b/libstdc++-v3/include/std/format
> index cb6cc4592..b14ed9746 100644
> --- a/libstdc++-v3/include/std/format
> +++ b/libstdc++-v3/include/std/format
> @@ -3843,6 +3843,83 @@ 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 typename _Base::_Sink_state;
> +      using _OutIter = ostreambuf_iterator<_CharT, _Traits>;
> +      using __diff_t = iter_difference_t<_OutIter>;
> +      _OutIter _M_out;
> +      __diff_t _M_max;
>
Would use size_t here, we can convert it back to iter difference
of size_t, but will make check simpler (because we will never hit -1
characters).

> +
> +    protected:
> +      size_t _M_count = 0;
> +
> +      _GLIBCXX_CONSTEXPR_FORMAT void
> +      _M_overflow() override
> +      {
> +       auto __s = this->_M_used();
> +       if (__s.empty()) [[unlikely]]
> +         return;
>
I would implement this as follows, comments are for explanation, but does
not need to
be in final code.
           size_t __prev = _M_count;
           _M_count += n;

           // We already wrote more than max, just clear the buffer.
           if (__prev >= _M_max)
             this->_M_rewind();
           // We havent reached the maximum, just write to the sink
           else if (_M_count < _M_max)
             _Streambuf_sink::_M_oveflow();
           // Last write pushed us over maximum, we just need to
           // handle last charcters
           else
             {
                // Limit then number of written characters
                 this->_M_reset(__s, _M_max - __prev);
                 _Streambuf_sink::_M_oveflow(); // Write this to stream
                 _M_reset(_M_buf); // Use buffer to store and write
remaining characters.
              }
This way we do not need _M_pbump.

We also need to override _M_reserve to avoid reserving more than _M_max
characters,
something like:
     _GLIBCXX_CONSTEXPR_FORMAT typename _Sink<_CharT>::_Reservation
     _M_reserve(size_t __n) override
     {
           if (_M_max - _M_count < __n)
             return { nullptr; }
           return _Streambuf_sink::_M_reserve(__n);
     }

And then _M_bump to count written characters:
     _M_bump(size_t __n)
      {
          _M_count += __n;
         _Streambuf_sink::_M_bump(__n);
        // We written up to _M_max (_M_reserve prevents us from writting
more).
         if (_M_count >= _M_max)
           _M_reset(_M_buf);
     }
        _
+

> +       size_t __n = __s.size();
> +       size_t __commit = 0;
> +       if (_M_max < 0)
> +         __commit = __n;
> +       else if (_M_count < static_cast<size_t>(_M_max))
> +         {
> +           size_t __max = _M_max - _M_count;
> +           __commit = min(__n, __max);
> +         }
> +
> +       _M_count += __n;
> +       switch (this->_M_state)
> +       {
> +         case _Sink_state::_S_stack:
> +           // _M_put checks sputn's return value and sets
> +           // _M_failed on the iterator on short write, so
> +           // the caller sees the failure.
> +           _M_out._M_put(__s.data(), __commit);
> +           break;
> +         case _Sink_state::_S_put_area:
> +           this->_M_pbump(__commit);
> +           break;
> +       }
> +
> +       if (!this->_M_use_put_area())
> +         this->_M_use_stackbuf();
> +      }
> +
> +      _GLIBCXX_CONSTEXPR_FORMAT bool
> +      _M_discarding() const override
> +      {
> +       return false;
> +      }
> +
> +    public:
> +      [[__gnu__::__always_inline__]]
> +      _GLIBCXX_CONSTEXPR_FORMAT explicit
> +      _Iter_sink(_OutIter __out, __diff_t __max = -1)
> +      : _Base(__out._M_get_sbuf()), _M_out(__out), _M_max(__max)
> +      { }
> +
> +      using _Base::out;
> +
> +      _GLIBCXX_CONSTEXPR_FORMAT format_to_n_result<_OutIter>
> +      _M_finish() &&
> +      {
> +       if (this->_M_used().size() != 0)
> +         _M_overflow();
> +
> +       iter_difference_t<_OutIter> __count(_M_count);
>
We still do not restore the failed flag.

> +       return { std::move(_M_out), __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.34.1
>
>

Reply via email to