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