On Thu, Jul 9, 2026 at 3:27 PM Tomasz Kaminski <[email protected]> wrote:
> > > On Thu, Jul 9, 2026 at 3:09 PM Tomasz Kaminski <[email protected]> > wrote: > >> >> >> On Thu, Jul 9, 2026 at 1:18 PM Anlai Lu <[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]> >>> --- >>> .../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 >>> >>>
