[PATCH v2 2/4] libstdc++: Add _Streambuf_sink for direct streambuf formatting

Anlai Lu agicy@qq.com
Wed Aug 12 03:05:56 GMT 2026



> On Aug 11, 2026, at 16:35, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
> 
> 
> 
> On Mon, Aug 10, 2026 at 6:29 PM Tomasz Kaminski <tkaminsk@redhat.com <mailto:tkaminsk@redhat.com>> wrote:
>> 
>> 
>> On Mon, Aug 10, 2026 at 5:40 PM Tomasz Kamiński <tkaminsk@redhat.com <mailto:tkaminsk@redhat.com>> wrote:
>>> From: Anlai Lu <agicy@qq.com <mailto:agicy@qq.com>>
>>> 
>>> 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.
>>> 
>>> Streambuf I/O errors are tracked internally (_M_write_failed) and
>>> reported after formatting completes, so that format-phase exceptions
>>> (format_error, bad_alloc) propagate without setting badbit per
>>> [ostream.formatted.print]/(4.2).
>>> 
>>> libstdc++-v3/ChangeLog:
>>> 
>>>         * include/std/format (__format::_Streambuf_sink): New class
>>>         template.
>>>         * include/std/streambuf: Include bits/version.h.
>>>         (__format::_Streambuf_sink) [__glibcxx_format]: Declare.
>>>         (std::basic_streambuf) [__glibcxx_format]: Befriend
>>>         __format::_Streambuf_sink.
>>> 
>>> Co-authored-by: Tomasz Kamiński <tkaminsk@redhat.com <mailto:tkaminsk@redhat.com>>
>>> Signed-off-by: Anlai Lu <agicy@qq.com <mailto:agicy@qq.com>>
>>> Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com <mailto:tkaminsk@redhat.com>>
>>> ---
>>> Changes in v2:
>>>  - call _M_reserve_put_area in _Streambuf_sink constructor,
>>>    poiting buffer to put area 
>>>  - ostreambuf_iterator: Remove the changes, that were unrelated
>>>    to standard
>>>  - remove _GLIBCXX_CONSTEXPR_FORMAT, as they classes cannot
>>>    be used at compile time
>>>  - remove and inline _M_pbump and _M_use_stackbuf, rename
>>>    _M_use_put_area to _M_reserve_put_area
>>>  - done some formatting changes
>>> 
>>> Testing on powerprc64. Tested on x86_64, additionally tested
>>> *format* and *print* in all standard modes, assertions and
>>> debug. OK for trunk?
>>> 
>>>  libstdc++-v3/include/std/format    | 110 +++++++++++++++++++++++++++++
>>>  libstdc++-v3/include/std/streambuf |   9 +++
>>>  2 files changed, 119 insertions(+)
>>> 
>>> diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
>>> index 729cb89ec60..9223cd2e78b 100644
>>> --- a/libstdc++-v3/include/std/format
>>> +++ b/libstdc++-v3/include/std/format
>>> @@ -3532,6 +3532,116 @@ 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>
>>> +    {
>>> +    protected:
>>> +      basic_streambuf<_CharT, _Traits>* _M_sbuf;
>>> +      bool _M_write_failed = false;
>>> +
>>> +      bool
>>> +      _M_on_stack() const noexcept
>>> +      { return this->_M_used().data() == this->_M_buf; }
>>> +
>>> +      // 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_reserve_put_area(size_t __n = 0)
>>> +      {
>>> +       if (auto __p = _M_sbuf->pptr())
>>> +         if (auto __e = _M_sbuf->epptr(); __e > __p)
>>> +           if (size_t __a(__e - __p); __a >= __n)
>>> +             {
>>> +               this->_M_reset(span<_CharT>(__p, __a));
>>> +               return true;
>>> +             }
>>> +       return false;
>>> +      }
>>> +
>>> +      // Write characters to the streambuf, tracking errors.
>>> +      // I/O exceptions from sputn are caught and converted to
>>> +      // _M_write_failed so they do not propagate as vformat exceptions
>>> +      // per [ostream.formatted.print]/(4.2).
>>> +      void
>>> +      _M_flush()
>>> +      {
>>> +       span<const _CharT> __s = this->_M_used();
>>> +       if (__s.empty() || _M_write_failed) [[unlikely]]
>>> +         return;
>>> +
>>> +       __try
>>> +         {
>>> +           if (!_M_on_stack())
>>> +             _M_sbuf->__safe_pbump(__s.size());
>>> +           else if (_M_sbuf->sputn(__s.data(), __s.size())
>>> +                      != static_cast<streamsize>(__s.size()))
>>> +             _M_write_failed = true;
>>> +         }
>>> +       __catch(const __cxxabiv1::__forced_unwind&)
>>> +         { throw; }
>>> +       __catch(...)
>>> +         { _M_write_failed = true; }
>>> +      }
>>> +
>>> +      void
>>> +      _M_overflow() override
>>> +      {
>>> +       _M_flush();
>>> +       if (!_M_reserve_put_area() || _M_write_failed)
>> We should check _M_write_failed first and make it unlikely. 
> Changed it locally to:
>        _M_overflow() override
>        {
>         _M_flush();
> -       if (!_M_reserve_put_area() || _M_write_failed)
> -         this->_M_reset(this->_M_buf);
> +       if (!_M_write_failed) [[likely]]
> +         if (_M_reserve_put_area())
> +           return;
> +       this->_M_reset(this->_M_buf);
>        }
>  
Done - _M_overflow now checks _M_write_failed first, per your version.

I will re-run the benchmarks after this change.
>>> +         this->_M_reset(this->_M_buf);
>>> +      }
>>> +
>>> +    public:
>>> +      [[__gnu__::__always_inline__]]
>>> +      explicit
>>> +      _Streambuf_sink(basic_streambuf<_CharT, _Traits>* __sbuf) noexcept
>>> +      : _M_sbuf(__sbuf)
>>> +      { _M_reserve_put_area(); }
>>> +
>>> +      // _M_bump is not overridden: the default advances _M_next within
>>> +      // the current span (stack buffer or put area).  Actual commit
>>> +      // to the streambuf happens via _M_flush, called from _M_overflow
>>> +      // (buffer full) or _M_finish (end of formatting).
>>> +
>>> +      typename _Sink<_CharT>::_Reservation
>>> +      _M_reserve(size_t __n) override
>>> +      {
>>> +       if (__n <= this->_M_unused().size())
>>> +         return { this };
>>> +
>>> +       if (!this->_M_used().empty())
>>> +         _M_flush();
>>> +
>>> +       // Try to write directly into the streambuf's put area.
>>> +       if (_M_reserve_put_area(__n))
>>> +         return { this };
>>> +
>>> +       // Otherwise reset to the stack buffer.
>>> +       this->_M_reset(this->_M_buf);
>>> +       if (__n <= this->_M_unused().size())
>>> +         return { this };
>>> +
>>> +       return { nullptr };
>>> +      }
>>> +
>>> +      bool
>>> +      _M_discarding() const override
>>> +      { return _M_write_failed; }
>>> +
>>> +      void
>>> +      _M_finish()
>>> +      { _M_flush(); }
>>> +
>>> +      bool
>>> +      _M_failed() const noexcept
>>> +      { return _M_write_failed; }
>>> +    };
>>> +
>>>    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 616e44f74a7..d273db2835f 100644
>>> --- a/libstdc++-v3/include/std/streambuf
>>> +++ b/libstdc++-v3/include/std/streambuf
>>> @@ -39,6 +39,8 @@
>>> 
>>>  #include <bits/requires_hosted.h> // iostreams
>>> 
>>> +#include <bits/version.h>
>>> +
>>>  #include <bits/c++config.h>
>>>  #include <bits/iosfwd.h>
>>>  #include <bits/localefwd.h>
>>> @@ -57,6 +59,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>>      __copy_streambufs_eof(basic_streambuf<_CharT, _Traits>*,
>>>                           basic_streambuf<_CharT, _Traits>*, bool&);
>>> 
>>> +#ifdef __glibcxx_format // C++ >= 20 && HOSTED
>>> +  namespace __format { template<typename, typename> class _Streambuf_sink; }
>>> +#endif
>>> +
>>>    /**
>>>     *  @brief  The actual work of input and output (interface).
>>>     *  @ingroup io
>>> @@ -149,6 +155,9 @@ _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>;
>>> +#ifdef __glibcxx_format // C++ >= 20 && HOSTED
>>> +      friend class __format::_Streambuf_sink<char_type, traits_type>;
>>> +#endif
>>> 
>>>        friend streamsize
>>>        __copy_streambufs_eof<>(basic_streambuf*, basic_streambuf*, bool&);
>>> -- 
>>> 2.55.0

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260812/3b5c3084/attachment.htm>


More information about the Libstdc++ mailing list