On Mon, Jun 29, 2026 at 2:30 PM Anlai Lu <[email protected]> wrote: > Replace `os << std::format(...)` with `std::format_to(ostreambuf_iterator, > ...)` > in chrono operator<< overloads. This avoids creating a temporary > std::string > for the formatted output, instead writing directly to the stream buffer. > > This addresses the TODO in the chrono formatter about using format_to > instead of format for ostream insertion (PR libstdc++/111052). When > combined with a subsequent _Iter_sink<ostreambuf_iterator> specialization, > this also benefits from zero-copy writing directly to the streambuf's > put area. > Thank you for the patch, I like the direction, but have few suggestions below.
> > libstdc++-v3/ChangeLog: > > * include/bits/chrono_io.h (operator<<): Use format_to with > ostreambuf_iterator instead of os << format. Check for write > failure and set badbit on the stream. > > Signed-off-by: Anlai Lu <[email protected]> > --- > libstdc++-v3/include/bits/chrono_io.h | 102 +++++++++++++++++++++----- > 1 file changed, 83 insertions(+), 19 deletions(-) > > diff --git a/libstdc++-v3/include/bits/chrono_io.h > b/libstdc++-v3/include/bits/chrono_io.h > index f8bb485d1..2656bd9c4 100644 > --- a/libstdc++-v3/include/bits/chrono_io.h > +++ b/libstdc++-v3/include/bits/chrono_io.h > @@ -3635,7 +3635,10 @@ namespace __detail > if (__d.ok()) > After the changes I have done in the commit: https://gcc.gnu.org/cgit/gcc/commit/?id=a651e3f3a6b4eb66185a9066c8cefe5288cda575 libstdc++: Rework formatting of empty chrono-spec for duration. I believe, it is now possible to implement this formatter simply as: std::format_to("{}", __d); __formatter_chrono has _S_empty_fs() that you could make public and use instead of "{}". However, before going that route, we need to consider whether we might Introduce a partial output by writing directly to the ostream specialization. What I mean is in a situation where an exception is thrown in the middle of producing output (after some characters were printed) and then if an exception is thrown we may see partial output, which would be non-conforming. (Note, I do not think this applies to format_to with ostream_iterator in general, as this is not specified in terms of creating the string first, and then printing the output). I do not think this is possible, for calendar types and in usual scenarios. However, if duration uses a custom representation type, such type may throw an exception. on arithmetic operations necessary. (For example on overwflow) With the introduction in _ChronoData ( https://gcc.gnu.org/cgit/gcc/commit/?id=4b3cefed1a08344495fedec4982d85168bd8173f ) I think we perform all checks and calculations before we emit any characters, but we would need to revisit every impacted formatter. But Furthermore, I think I would prefer an introduction of separate _streambuf_sink, that will accept rdbuf directly, instead of going to iterator and specialization. We may still introduce it, but I would not tie this to together. To summarise: * Introduce a separate _Streambuf_sink (we could also use it in print) * Create a commit using simply {} for formatting and above sink * Create separate commit that specializes _Iter_sink for ostreambuf_iterator __s = __s.substr(0, 6); > auto __u = (unsigned)__d; > - __os << std::vformat(__s, make_format_args<_Ctx>(__u)); > + auto __it = std::vformat_to(std::ostreambuf_iterator<_CharT>(__os), > + __s, make_format_args<_Ctx>(__u)); > + if (__it.failed()) > + __os.setstate(ios_base::badbit); > return __os; > } > > @@ -3661,12 +3664,21 @@ namespace __detail > using _Str = basic_string_view<_CharT>; > _Str __s = _GLIBCXX_WIDEN("{:L%b}{} is not a valid month"); > if (__m.ok()) > - __os << std::vformat(__os.getloc(), __s.substr(0, 6), > - make_format_args<_Ctx>(__m)); > + { > + auto __it = > std::vformat_to(std::ostreambuf_iterator<_CharT>(__os), > + __os.getloc(), __s.substr(0, 6), > + make_format_args<_Ctx>(__m)); > + if (__it.failed()) > + __os.setstate(ios_base::badbit); > + } > else > { > auto __u = (unsigned)__m; > - __os << std::vformat(__s.substr(6), make_format_args<_Ctx>(__u)); > + auto __it = > std::vformat_to(std::ostreambuf_iterator<_CharT>(__os), > + __s.substr(6), > + make_format_args<_Ctx>(__u)); > + if (__it.failed()) > + __os.setstate(ios_base::badbit); > } > return __os; > } > @@ -3699,7 +3711,11 @@ namespace __detail > __s.remove_prefix(1); > else > __i = -__i; > - __os << std::vformat(__s, make_format_args<_Ctx>(__i)); > + auto __it = std::vformat_to(std::ostreambuf_iterator<_CharT>(__os), > + __s, > + make_format_args<_Ctx>(__i)); > + if (__it.failed()) > + __os.setstate(ios_base::badbit); > return __os; > } > > @@ -3725,12 +3741,21 @@ namespace __detail > using _Str = basic_string_view<_CharT>; > _Str __s = _GLIBCXX_WIDEN("{:L%a}{} is not a valid weekday"); > if (__wd.ok()) > - __os << std::vformat(__os.getloc(), __s.substr(0, 6), > - make_format_args<_Ctx>(__wd)); > + { > + auto __it = > std::vformat_to(std::ostreambuf_iterator<_CharT>(__os), > + __os.getloc(), __s.substr(0, 6), > + make_format_args<_Ctx>(__wd)); > + if (__it.failed()) > + __os.setstate(ios_base::badbit); > + } > else > { > auto __c = __wd.c_encoding(); > - __os << std::vformat(__s.substr(6), make_format_args<_Ctx>(__c)); > + auto __it = > std::vformat_to(std::ostreambuf_iterator<_CharT>(__os), > + __s.substr(6), > + make_format_args<_Ctx>(__c)); > + if (__it.failed()) > + __os.setstate(ios_base::badbit); > } > return __os; > } > @@ -3764,7 +3789,10 @@ namespace __detail > basic_string_view<_CharT> __s > = _GLIBCXX_WIDEN("[ is not a valid index]"); > __os2 << __s[0]; > - __os2 << std::format(_GLIBCXX_WIDEN("{}"), __i); > + auto __it_idx = > std::format_to(std::ostreambuf_iterator<_CharT>(__os2), > + _GLIBCXX_WIDEN("{}"), __i); > + if (__it_idx.failed()) > + __os2.setstate(ios_base::badbit); > if (__i >= 1 && __i <= 5) > __os2 << __s.back(); > else > @@ -3909,8 +3937,11 @@ namespace __detail > using _Ctx = __format::__format_context<_CharT>; > using _Str = basic_string_view<_CharT>; > _Str __s = _GLIBCXX_WIDEN("{:%F} is not a valid date"); > - __os << std::vformat(__ymd.ok() ? __s.substr(0, 5) : __s, > - make_format_args<_Ctx>(__ymd)); > + auto __it = std::vformat_to(std::ostreambuf_iterator<_CharT>(__os), > + __ymd.ok() ? __s.substr(0, 5) : __s, > + make_format_args<_Ctx>(__ymd)); > + if (__it.failed()) > + __os.setstate(ios_base::badbit); > return __os; > } > > @@ -3994,7 +4025,12 @@ namespace __detail > operator<<(basic_ostream<_CharT, _Traits>& __os, > const hh_mm_ss<_Duration>& __hms) > { > - return __os << format(__os.getloc(), _GLIBCXX_WIDEN("{:L%T}"), > __hms); > + auto __it = std::format_to(std::ostreambuf_iterator<_CharT>(__os), > + __os.getloc(), > + _GLIBCXX_WIDEN("{:L%T}"), __hms); > + if (__it.failed()) > + __os.setstate(ios_base::badbit); > + return __os; > } > > #if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI > @@ -4003,7 +4039,11 @@ namespace __detail > basic_ostream<_CharT, _Traits>& > operator<<(basic_ostream<_CharT, _Traits>& __os, const sys_info& __i) > { > - return __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{}"), > __i); > + auto __it = std::format_to(std::ostreambuf_iterator<_CharT>(__os), > + __os.getloc(), _GLIBCXX_WIDEN("{}"), __i); > + if (__it.failed()) > + __os.setstate(ios_base::badbit); > + return __os; > } > > /// Writes a local_info object to an ostream in an unspecified format. > @@ -4033,7 +4073,11 @@ namespace __detail > operator<<(basic_ostream<_CharT, _Traits>& __os, > const zoned_time<_Duration, _TimeZonePtr>& __t) > { > - __os << format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T %Z}"), __t); > + auto __it = std::format_to(std::ostreambuf_iterator<_CharT>(__os), > + __os.getloc(), > + _GLIBCXX_WIDEN("{:L%F %T %Z}"), __t); > + if (__it.failed()) > + __os.setstate(ios_base::badbit); > return __os; > } > #endif > @@ -4045,7 +4089,11 @@ namespace __detail > operator<<(basic_ostream<_CharT, _Traits>& __os, > const sys_time<_Duration>& __tp) > { > - __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), > __tp); > + auto __it = std::format_to(std::ostreambuf_iterator<_CharT>(__os), > + __os.getloc(), > + _GLIBCXX_WIDEN("{:L%F %T}"), __tp); > + if (__it.failed()) > + __os.setstate(ios_base::badbit); > return __os; > } > > @@ -4090,7 +4138,11 @@ namespace __detail > operator<<(basic_ostream<_CharT, _Traits>& __os, > const utc_time<_Duration>& __t) > { > - __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), > __t); > + auto __it = std::format_to(std::ostreambuf_iterator<_CharT>(__os), > + __os.getloc(), > + _GLIBCXX_WIDEN("{:L%F %T}"), __t); > + if (__it.failed()) > + __os.setstate(ios_base::badbit); > return __os; > } > > @@ -4125,7 +4177,11 @@ namespace __detail > operator<<(basic_ostream<_CharT, _Traits>& __os, > const tai_time<_Duration>& __t) > { > - __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), > __t); > + auto __it = std::format_to(std::ostreambuf_iterator<_CharT>(__os), > + __os.getloc(), > + _GLIBCXX_WIDEN("{:L%F %T}"), __t); > + if (__it.failed()) > + __os.setstate(ios_base::badbit); > return __os; > } > > @@ -4164,7 +4220,11 @@ namespace __detail > operator<<(basic_ostream<_CharT, _Traits>& __os, > const gps_time<_Duration>& __t) > { > - __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), > __t); > + auto __it = std::format_to(std::ostreambuf_iterator<_CharT>(__os), > + __os.getloc(), > + _GLIBCXX_WIDEN("{:L%F %T}"), __t); > + if (__it.failed()) > + __os.setstate(ios_base::badbit); > return __os; > } > > @@ -4202,7 +4262,11 @@ namespace __detail > operator<<(basic_ostream<_CharT, _Traits>& __os, > const file_time<_Duration>& __t) > { > - __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), > __t); > + auto __it = std::format_to(std::ostreambuf_iterator<_CharT>(__os), > + __os.getloc(), > + _GLIBCXX_WIDEN("{:L%F %T}"), __t); > + if (__it.failed()) > + __os.setstate(ios_base::badbit); > return __os; > } > > -- > 2.34.1 > >
