On Thu, Jul 9, 2026 at 3:26 PM Tomasz Kaminski <[email protected]> wrote:
> > > 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 > 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 >> >>
