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

Tomasz Kaminski tkaminsk@redhat.com
Tue Aug 11 10:08:17 GMT 2026


On Thu, Jul 16, 2026 at 2:04 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.
>
> 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).
>
> The _Sink_state enum is omitted in favor of _M_on_stack() which
> tests whether the current span points at the stack buffer.
>
> libstdc++-v3/ChangeLog:
>
>         * include/bits/streambuf_iterator.h (ostreambuf_iterator):
>         Add internal _M_get_sbuf() and _M_set_failed() members.
>         * 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         |  11 ++
>  libstdc++-v3/include/std/format               | 132 ++++++++++++++++++
>  libstdc++-v3/include/std/streambuf            |   4 +
>  3 files changed, 147 insertions(+)
>
> diff --git a/libstdc++-v3/include/bits/streambuf_iterator.h
> b/libstdc++-v3/include/bits/streambuf_iterator.h
> index 095928ca4..d919fdbb8 100644
> --- a/libstdc++-v3/include/bits/streambuf_iterator.h
> +++ b/libstdc++-v3/include/bits/streambuf_iterator.h
> @@ -318,6 +318,17 @@ _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; }
> +
> +      void
> +      _M_set_failed() _GLIBCXX_USE_NOEXCEPT
> +      { _M_failed = true; }
> +      /// @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 9bf5e7857..b676629b1 100644
> --- a/libstdc++-v3/include/std/format
> +++ b/libstdc++-v3/include/std/format
> @@ -59,6 +59,7 @@
>  #include <bits/ranges_util.h>  // subrange
>  #include <bits/ranges_algobase.h> // ranges::copy
>  #include <bits/stl_iterator.h> // counted_iterator
> +#include <bits/streambuf_iterator.h> // ostreambuf_iterator,
> basic_streambuf
>  #include <bits/stl_pair.h>     // __is_pair
>  #include <bits/unicode.h>      // __is_scalar_value, _Utf_view, etc.
>  #include <bits/utility.h>      // tuple_size_v
> @@ -3530,6 +3531,137 @@ 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:
> +      basic_streambuf<_CharT, _Traits>* _M_sbuf;
> +      bool _M_write_failed = false;
> +
> +      _GLIBCXX_CONSTEXPR_FORMAT bool
> +      _M_on_stack() const noexcept
> +      { return this->_M_used().data() == this->_M_buf; }
> +
> +      // Switch to _M_buf when the streambuf has no put area
> +      // or we need to stop using it.
> +      _GLIBCXX_CONSTEXPR_FORMAT void
> +      _M_use_stackbuf()
> +      { this->_M_reset(_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.
> +      _GLIBCXX_CONSTEXPR_FORMAT 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)});
> +               return true;
> +             }
> +         }
> +       return false;
> +      }
> +
> +      // Allow derived classes to commit to the put area
> +      // without needing friendship to basic_streambuf.
> +      _GLIBCXX_CONSTEXPR_FORMAT void
> +      _M_pbump(streamsize __n)
> +      { _M_sbuf->__safe_pbump(__n); }
>
Is tehre any reason to use internal __safe_pbump instead of simply pbump
here?

> +
> +      // 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).  Does not refresh the
> +      // span; callers use _M_use_put_area/stackbuf.
> +      _GLIBCXX_CONSTEXPR_FORMAT void
> +      _M_flush(span<_CharT> __s)
> +      {
> +       if (__s.empty() || _M_write_failed) [[unlikely]]
> +         return;
> +       __try {
> +         if (_M_on_stack())
> +           {
> +             if (_M_sbuf->sputn(__s.data(), __s.size())
> +                 != static_cast<streamsize>(__s.size()))
> +               _M_write_failed = true;
> +           }
> +         else
> +           _M_pbump(__s.size());
> +       }
> +       __catch(const __cxxabiv1::__forced_unwind&)
> +         { throw; }
> +       __catch(...)
> +         { _M_write_failed = true; }
> +      }
> +
> +      _GLIBCXX_CONSTEXPR_FORMAT void
> +      _M_overflow() override
> +      {
> +       _M_flush(this->_M_used());
> +       if (!_M_use_put_area())
> +         _M_use_stackbuf();
> +      }
> +
> +    public:
> +      [[__gnu__::__always_inline__]]
> +      _GLIBCXX_CONSTEXPR_FORMAT explicit
> +      _Streambuf_sink(basic_streambuf<_CharT, _Traits>* __sbuf) noexcept
> +      : _M_sbuf(__sbuf)
> +      { }
> +
> +      using _Sink<_CharT>::out;
> +
> +      // _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).
> +
> +      _GLIBCXX_CONSTEXPR_FORMAT typename _Sink<_CharT>::_Reservation
> +      _M_reserve(size_t __n) override
> +      {
> +       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 };
> +      }
> +
> +      _GLIBCXX_CONSTEXPR_FORMAT bool
> +      _M_discarding() const override
> +      { return _M_write_failed; }
> +
> +      _GLIBCXX_CONSTEXPR_FORMAT void
> +      _M_finish() &&
> +      { _M_flush(this->_M_used()); }
> +
> +      _GLIBCXX_CONSTEXPR_FORMAT 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 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/20260811/5c8010a4/attachment.htm>


More information about the Libstdc++ mailing list