[PATCH v2 2/3] libstdc++: Add _Streambuf_sink for direct streambuf formatting
Tomasz Kaminski
tkaminsk@redhat.com
Thu Jul 9 13:35:27 GMT 2026
On Thu, Jul 9, 2026 at 3:27 PM Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>
>
> On Thu, Jul 9, 2026 at 3:09 PM Tomasz Kaminski <tkaminsk@redhat.com>
> wrote:
>
>>
>>
>> On Thu, Jul 9, 2026 at 1:18 PM Anlai Lu <agicy@qq.com> wrote:
>>
>>> Introduce _Streambuf_sink that writes directly to basic_streambuf
>>> via sputn, preferring zero-copy writes into the streambuf put area
>>> (pptr/epptr/pbump) and falling back to the stack buffer and bulk
>>> sputn.
>>>
>>> libstdc++-v3/ChangeLog:
>>>
>>> * include/bits/streambuf_iterator.h (ostreambuf_iterator):
>>> Add internal _M_get_sbuf() member.
>>> * include/std/format: Include <bits/streambuf_iterator.h>.
>>> (__format::_Streambuf_sink): New class template.
>>> * include/std/streambuf: Forward-declare
>>> __format::_Streambuf_sink,
>>> add friend declaration to basic_streambuf.
>>>
>>> Signed-off-by: Anlai Lu <agicy@qq.com>
>>> ---
>>> .../include/bits/streambuf_iterator.h | 7 ++
>>> libstdc++-v3/include/std/format | 107 ++++++++++++++++++
>>> libstdc++-v3/include/std/streambuf | 4 +
>>> 3 files changed, 118 insertions(+)
>>>
>>> diff --git a/libstdc++-v3/include/bits/streambuf_iterator.h
>>> b/libstdc++-v3/include/bits/streambuf_iterator.h
>>> index 095928ca4..564365934 100644
>>> --- a/libstdc++-v3/include/bits/streambuf_iterator.h
>>> +++ b/libstdc++-v3/include/bits/streambuf_iterator.h
>>> @@ -318,6 +318,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>> failed() const _GLIBCXX_USE_NOEXCEPT
>>> { return _M_failed; }
>>>
>>> + /// @cond internal
>>> + _GLIBCXX_NODISCARD
>>> + streambuf_type*
>>> + _M_get_sbuf() const _GLIBCXX_USE_NOEXCEPT
>>> + { return _M_sbuf; }
>>> + /// @endcond
>>> +
>>> ostreambuf_iterator&
>>> _M_put(const _CharT* __ws, streamsize __len)
>>> {
>>> diff --git a/libstdc++-v3/include/std/format
>>> b/libstdc++-v3/include/std/format
>>> index dd275b6b8..cb6cc4592 100644
>>> --- a/libstdc++-v3/include/std/format
>>> +++ b/libstdc++-v3/include/std/format
>>> @@ -60,6 +60,7 @@
>>> #include <bits/ranges_algobase.h> // ranges::copy
>>> #include <bits/stl_iterator.h> // counted_iterator
>>> #include <bits/stl_pair.h> // __is_pair
>>> +#include <bits/streambuf_iterator.h> // ostreambuf_iterator,
>>> basic_streambuf
>>> #include <bits/unicode.h> // __is_scalar_value, _Utf_view, etc.
>>> #include <bits/utility.h> // tuple_size_v
>>> #include <ext/numeric_traits.h> // __int_traits
>>> @@ -3531,6 +3532,112 @@ namespace __format
>>> { }
>>> };
>>>
>>> + // A format sink that writes directly to a basic_streambuf.
>>> + // Prefers zero-copy writes into the streambuf's put area
>>> + // (pptr/epptr/pbump), falling back to the stack buffer
>>> + // (_M_buf) and bulk sputn.
>>> + template<typename _CharT, typename _Traits = char_traits<_CharT>>
>>> + class _Streambuf_sink : public _Buf_sink<_CharT>
>>> + {
>>> + using _Buf_sink<_CharT>::_M_buf;
>>> +
>>> + protected:
>>> + enum class _Sink_state { _S_stack, _S_put_area };
>>>
>> I think I would restore the _S_failed state, becuse we could use it to
>> return true from _M_set_discarding. I was wondering about using this
>> stream directly to implement std::print to ostream, and _M_discarding
>> would be useful for that.
>>
>> However, I do not think we need _S_stack, _S_put_area state,
>> to see if we are using a stack buffer you could simply
>> compare _M_used.data()
>> with _M_buf, so single bool would be needed.
>>
> This is required, because nobody may call _M_overflow after _M_bump,
> so we should print the characters.
>
>> +
>>> + basic_streambuf<_CharT, _Traits>* _M_sbuf;
>>> + _Sink_state _M_state = _Sink_state::_S_stack;
>>> +
>>> + // Switch to _M_buf when the streambuf has no put area
>>> + // or we need to stop using it.
>>> + void
>>> + _M_use_stackbuf()
>>> + {
>>> + this->_M_reset(_M_buf);
>>> + _M_state = _Sink_state::_S_stack;
>>> + }
>>> +
>>> + // Try to point our span directly into the streambuf's put
>>> + // area for zero-copy writes. Caller must fall back to
>>> + // _M_use_stackbuf() on failure.
>>> + bool
>>> + _M_use_put_area(size_t __n = 0)
>>> + {
>>> + if (auto __p = _M_sbuf->pptr())
>>> + {
>>> + auto __e = _M_sbuf->epptr();
>>> + if (__e && __e > __p
>>> + && static_cast<size_t>(__e - __p) >= __n)
>>> + {
>>> + this->_M_reset(
>>> + span<_CharT>{__p, static_cast<size_t>(__e - __p)});
>>> + _M_state = _Sink_state::_S_put_area;
>>> + return true;
>>> + }
>>> + }
>>> + return false;
>>> + }
>>> +
>>> + // Allow derived classes to commit to the put area
>>> + // without needing friendship to basic_streambuf.
>>> + void
>>> + _M_pbump(streamsize __n)
>>> + { _M_sbuf->__safe_pbump(__n); }
>>> +
>>> + _GLIBCXX_CONSTEXPR_FORMAT void
>>> + _M_overflow() override
>>> + {
>>> + auto __s = this->_M_used();
>>> + if (__s.empty()) [[unlikely]]
>>> + return;
>>> +
>>> + switch (_M_state)
>>> + {
>>> + case _Sink_state::_S_stack:
>>> + // _Iter_sink overrides _M_overflow and calls
>>> + // _M_out._M_put(), which checks sputn's return
>>> + // value and tracks failure on the iterator.
>>> + _M_sbuf->sputn(__s.data(), __s.size());
>>> + break;
>>> + case _Sink_state::_S_put_area:
>>> + _M_sbuf->__safe_pbump(__s.size());
>>> + break;
>>> + }
>>> +
>>> + if (!_M_use_put_area())
>>> + _M_use_stackbuf();
>>> + }
>>> +
>>> + public:
>>> + [[__gnu__::__always_inline__]]
>>> + constexpr explicit
>>> + _Streambuf_sink(basic_streambuf<_CharT, _Traits>* __sbuf) noexcept
>>> + : _M_sbuf(__sbuf)
>>> + { }
>>> +
>>> + using _Sink<_CharT>::out;
>>> +
>>> + _GLIBCXX_CONSTEXPR_FORMAT typename _Sink<_CharT>::_Reservation
>>> + _M_reserve(size_t __n) override
>>>
>> The reservation protocor works in two step, firstly the formatter calls
>> _M_reserve,
>> to get the stage area, and then after writting some characters _M_bump is
>> called
>> (without calling override). The default implementaiton increasments the
>> current _M_used
>> span pointer,
>>
>> We need to override to _M_bump for this sink, so if we are using
>> _S_put_area,
>> then we need to call pbump on _M_sbuf, and otehrwise call _M_bump on base,
>> and rebuild the buf.
>>
> After checking again, I think the current implementation of _M_bump is
valid,
but we could add a comment here explaining it.
> + {
>>> + if (__n <= this->_M_unused().size())
>>> + return { this };
>>> +
>>> + if (!this->_M_used().empty())
>>> + _M_overflow();
>>> +
>>> + // Try to write directly into the streambuf's put area.
>>> + if (_M_use_put_area(__n))
>>> + return { this };
>>> +
>>> + // Otherwise reset to the stack buffer.
>>> + _M_use_stackbuf();
>>> + if (__n <= this->_M_unused().size())
>>> + return { this };
>>> +
>>> + return { nullptr };
>>> + }
>>> + };
>>> +
>>> using _GLIBCXX_STD_C::vector;
>>>
>>> // A sink that fills a sequence (e.g. std::string, std::vector,
>>> std::deque).
>>> diff --git a/libstdc++-v3/include/std/streambuf
>>> b/libstdc++-v3/include/std/streambuf
>>> index 616e44f74..22b505228 100644
>>> --- a/libstdc++-v3/include/std/streambuf
>>> +++ b/libstdc++-v3/include/std/streambuf
>>> @@ -57,6 +57,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>> __copy_streambufs_eof(basic_streambuf<_CharT, _Traits>*,
>>> basic_streambuf<_CharT, _Traits>*, bool&);
>>>
>>> + namespace __format { template<typename, typename> class
>>> _Streambuf_sink; }
>>> +
>>> /**
>>> * @brief The actual work of input and output (interface).
>>> * @ingroup io
>>> @@ -149,6 +151,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>> friend class basic_ostream<char_type, traits_type>;
>>> friend class istreambuf_iterator<char_type, traits_type>;
>>> friend class ostreambuf_iterator<char_type, traits_type>;
>>> + template<typename, typename>
>>> + friend class __format::_Streambuf_sink;
>>>
>>> friend streamsize
>>> __copy_streambufs_eof<>(basic_streambuf*, basic_streambuf*,
>>> bool&);
>>> --
>>> 2.34.1
>>>
>>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260709/e4689e3d/attachment.htm>
More information about the Libstdc++
mailing list