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

Tomasz Kaminski <[email protected]>
Newsgroups gmane.comp.gcc.patches,gmane.comp.gcc.libstdc++.devel
Message-ID <CAKvuMXBpm1wifP-530YUjawGHhOncezHACz26FL8-7AzdGZc5Q@mail.gmail.com>
On Mon, Aug 10, 2026 at 5:40 PM Tomasz Kamiński <[email protected]> wrote:

> From: Anlai Lu <[email protected]>
>
> 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 <[email protected]>
> Signed-off-by: Anlai Lu <[email protected]>
> Signed-off-by: Tomasz Kamiński <[email protected]>
> ---
> 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.

> +         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
>
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.