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]>
---
v3 makes the !_M_write_failed branch likely, as send previously.
libstdc++-v3/include/std/format | 112 +++++++++++++++++++++++++++++
libstdc++-v3/include/std/streambuf | 9 +++
2 files changed, 121 insertions(+)
diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
index 729cb89ec60..f1698c3eb68 100644
--- a/libstdc++-v3/include/std/format
+++ b/libstdc++-v3/include/std/format
@@ -3532,6 +3532,118 @@ 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_write_failed) [[likely]]
+ if (_M_reserve_put_area())
+ return;
+ 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