On Mon, Aug 10, 2026 at 6:29 PM Tomasz Kaminski <[email protected]> wrote:

>
>
> 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.
>
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);
       }


> +         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
>>
>>

Reply via email to