[PATCH v2 3/3] libstdc++: Specialize _Iter_sink for ostreambuf_iterator
Tomasz Kaminski
tkaminsk@redhat.com
Thu Jul 9 13:38:06 GMT 2026
On Thu, Jul 9, 2026 at 3:26 PM Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>
>
> On Thu, Jul 9, 2026 at 1:20 PM Anlai Lu <agicy@qq.com> 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 <agicy@qq.com>
>> ---
>> 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
>
We could use something like _M_flush() here, that will "write" pending
characters (call pbump or write buffer), without reseting the span.
I would extract the following code from _Stream_sink::_M_overflow
(and call it from there).
> _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();
>>
> We could also call _M_flush here, as we do not need to restore the buffer.
> +
>> + 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
>>
>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260709/5fdf7691/attachment-0001.htm>
More information about the Libstdc++
mailing list