> On Jul 9, 2026, at 21:35, Tomasz Kaminski <[email protected]> wrote:
>
>
>
> On Thu, Jul 9, 2026 at 3:27 PM Tomasz Kaminski <[email protected]
> <mailto:[email protected]>> wrote:
>>
>>
>> On Thu, Jul 9, 2026 at 3:09 PM Tomasz Kaminski <[email protected]
>> <mailto:[email protected]>> wrote:
>>>
>>>
>>> On Thu, Jul 9, 2026 at 1:18 PM Anlai Lu <[email protected]
>>> <mailto:[email protected]>> 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 <[email protected] <mailto:[email protected]>>
>>>> ---
>>>> .../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.
Agreed, that's cleaner. I will removed the enum and replaced
the switch with a direct comparison (or a thin helper)
__s.data() == _M_buf
tells us whether we're on the stack buffer or the put area.
>> 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.
A comment explaining why no override is needed. Thanks for double-checking.
I will add 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
>>>>