On Tue, Jun 30, 2026 at 11:18 AM Anlai Lu <[email protected]> wrote: > Replace `os << format(...)` with a stack-buffer approach in all > chrono operator<< overloads. A new helper > __detail::__chrono_write formats into a 128-byte stack buffer via > __format::__do_vformat_to_n, then writes through __ostream_insert > which correctly handles sentry construction and width/fill padding. > If the output exceeds the buffer (which should not happen for typical > chrono output), the helper falls back to std::vformat with dynamic > allocation. > > A bridge function __detail::__empty_fmt uses basic_format_string to > extract a basic_string_view from __formatter_chrono::_S_empty_fs(), > which is now public. Types whose formatters already handle validation > (day, month, year, weekday, year_month_day) use __empty_fmt instead > of hand-rolled format strings with substr, eliminating manual ok() > checks. > > The weekday_indexed and weekday_last operator<< overloads now use > __chrono_write directly, per [time.cal.wdidx.nonmembers] and > [time.cal.wdlast.nonmembers]. There is no need for special failbit > handling because the standard specifies the output as a simple format > call, and __ostream_insert handles state via the standard sentry > mechanism.
> This eliminates temporary std::string and std::stringstream allocations > while preserving formatted output function semantics. > > libstdc++-v3/ChangeLog: > > * include/bits/chrono_io.h (__formatter_chrono::_S_empty_fs): > Make public. > (__detail::__empty_fmt): New function template. > (__detail::__chrono_write): New function template. > (operator<<): Use __chrono_write and __empty_fmt consistently. > (operator<<(weekday_indexed)): Replace stringstream with > __chrono_write. > (operator<<(weekday_last)): Likewise. > > Signed-off-by: Anlai Lu <[email protected]> > --- > libstdc++-v3/include/bits/chrono_io.h | 202 +++++++++++++++----------- > 1 file changed, 115 insertions(+), 87 deletions(-) > > diff --git a/libstdc++-v3/include/bits/chrono_io.h > b/libstdc++-v3/include/bits/chrono_io.h > index f8bb485d1..9e17c8ffd 100644 > --- a/libstdc++-v3/include/bits/chrono_io.h > +++ b/libstdc++-v3/include/bits/chrono_io.h > @@ -884,11 +884,13 @@ namespace __format > static constexpr const _CharT* _S_minus_empty_spec = _S_chars + 17; > static constexpr const _CharT* _S_empty_spec = _S_chars + 18; > > + public: > [[__gnu__::__always_inline__]] > static _Dynamic_format_string<_CharT> > _S_empty_fs() > { return _Dynamic_format_string<_CharT>(_S_empty_spec); } > > + protected: > static constexpr const _CharT* _S_weekdays[] > { > _GLIBCXX_WIDEN("Sunday"), > @@ -3607,6 +3609,77 @@ namespace __detail > } > } > > + // Bridge to extract basic_string_view from _S_empty_fs(). > + // Uses basic_format_string to safely convert, then discards it. > + template<typename _CharT, typename _Tp> > + inline constexpr basic_string_view<_CharT> > + __empty_fmt() > + { > + using _Fmt = basic_format_string<_CharT, type_identity_t<_Tp>>; > + return > _Fmt(__format::__formatter_chrono<_CharT>::_S_empty_fs()).get(); > + } > + > + // Format into stack buffer, write via __ostream_insert. > + template<typename _CharT, typename _Traits, typename... _Args> > + inline basic_ostream<_CharT, _Traits>& > + __chrono_write(basic_ostream<_CharT, _Traits>& __os, > + basic_string_view<_CharT> __fmt, const _Args&... __args) > Passing a format string should not be necessary, for all formatter specializations, the "{}" is already equivalent to the output of the operator<<, so we could just pass ostream and locale. > + { > + constexpr size_t __bufsize = 128; > + _CharT __buf[__bufsize]; > + auto __res > + = __format::__do_vformat_to_n(__buf, __bufsize, __fmt, > + std::make_format_args< > + __format::__format_context<_CharT> > + >(__args...)); > Because __format_fs returns _Dynamic_format_string, format calls will not check the the format string at compile time, so I think we could replace that, with: template<typename _CharT, typename _Traits, typename _Arg, typename... OptLocale> inline basic_ostream<_CharT, _Traits>& __chrono_write(basic_ostream<_CharT, _Traits>& __os, const _Arg& __arg, const OptLocale... __loc...) { static_assert(sizeof...(OptLocale) <= 1); constexpr auto __fs = __format::__formatter_chrono<_CharT>::_S_empty_fs(); constexpr size_t __bufsize = 128; _CharT __buf[__bufsize]; auto __res = std::format_to_n(__buf, __buf_size, __loc..., __fs, __arg); if (static_cast<size_t>(__res.size) <= __bufsize) [[likely]] return std::__ostream_insert(__os, __buf, __res.size); auto __s = std::format(__loc..., __fs, __arg); return std::__ostream_insert(__os, __s.data(), __s.size()); } And then call for every operator<< accepting xx except for duration __format::__chrono_write(__os, _xx); __format::__chrono_write(__os, _xx, __os.getloc()); Did you run into any problems with the above? + if (static_cast<size_t>(__res.size) <= __bufsize) [[likely]] > + return std::__ostream_insert(__os, __buf, __res.size); > + auto __s = std::vformat(__fmt, > + std::make_format_args< > + __format::__format_context<_CharT> > + >(__args...)); > + return std::__ostream_insert(__os, __s.data(), __s.size()); > + } > + > + template<typename _CharT, typename _Traits, typename... _Args> > + inline basic_ostream<_CharT, _Traits>& > + __chrono_write(basic_ostream<_CharT, _Traits>& __os, const locale& > __loc, > + basic_string_view<_CharT> __fmt, const _Args&... __args) > + { > + constexpr size_t __bufsize = 128; > + _CharT __buf[__bufsize]; > + auto __res > + = __format::__do_vformat_to_n(__buf, __bufsize, __fmt, > + std::make_format_args< > + __format::__format_context<_CharT> > + >(__args...), > + &__loc); > + if (static_cast<size_t>(__res.size) <= __bufsize) [[likely]] > + return std::__ostream_insert(__os, __buf, __res.size); > + auto __s = std::vformat(__loc, __fmt, > + std::make_format_args< > + > __format::__format_context<_CharT>>(__args...)); > + return std::__ostream_insert(__os, __s.data(), __s.size()); > + } > + > + // Thin forwarders for const _CharT* (compile-time literals). > + template<typename _CharT, typename _Traits, typename... _Args> > + inline basic_ostream<_CharT, _Traits>& > + __chrono_write(basic_ostream<_CharT, _Traits>& __os, > + const _CharT* __fmt, const _Args&... __args) > + { > + return __chrono_write(__os, basic_string_view<_CharT>(__fmt), > __args...); > + } > + > + template<typename _CharT, typename _Traits, typename... _Args> > + inline basic_ostream<_CharT, _Traits>& > + __chrono_write(basic_ostream<_CharT, _Traits>& __os, const locale& > __loc, > + const _CharT* __fmt, const _Args&... __args) > + { > + return __chrono_write(__os, __loc, basic_string_view<_CharT>(__fmt), > + __args...); > + } > + > } // namespace __detail > /// @endcond > > @@ -3629,14 +3702,9 @@ namespace __detail > inline basic_ostream<_CharT, _Traits>& > operator<<(basic_ostream<_CharT, _Traits>& __os, const day& __d) > { > - using _Ctx = __format::__format_context<_CharT>; > - using _Str = basic_string_view<_CharT>; > - _Str __s = _GLIBCXX_WIDEN("{:02d} is not a valid day"); > - if (__d.ok()) > - __s = __s.substr(0, 6); > - auto __u = (unsigned)__d; > - __os << std::vformat(__s, make_format_args<_Ctx>(__u)); > - return __os; > + return __detail::__chrono_write(__os, > + __detail::__empty_fmt<_CharT, day>(), > + __d); > } > > template<typename _CharT, typename _Traits, > @@ -3657,18 +3725,9 @@ namespace __detail > inline basic_ostream<_CharT, _Traits>& > operator<<(basic_ostream<_CharT, _Traits>& __os, const month& __m) > { > - using _Ctx = __format::__format_context<_CharT>; > - 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)); > - else > - { > - auto __u = (unsigned)__m; > - __os << std::vformat(__s.substr(6), make_format_args<_Ctx>(__u)); > - } > - return __os; > + return __detail::__chrono_write(__os, __os.getloc(), > + __detail::__empty_fmt<_CharT, > month>(), > + __m); > } > > template<typename _CharT, typename _Traits, > @@ -3689,18 +3748,9 @@ namespace __detail > inline basic_ostream<_CharT, _Traits>& > operator<<(basic_ostream<_CharT, _Traits>& __os, const year& __y) > { > - using _Ctx = __format::__format_context<_CharT>; > - using _Str = basic_string_view<_CharT>; > - _Str __s = _GLIBCXX_WIDEN("-{:04d} is not a valid year"); > - if (__y.ok()) > - __s = __s.substr(0, 7); > - int __i = (int)__y; > - if (__i >= 0) [[likely]] > - __s.remove_prefix(1); > - else > - __i = -__i; > - __os << std::vformat(__s, make_format_args<_Ctx>(__i)); > - return __os; > + return __detail::__chrono_write(__os, > + __detail::__empty_fmt<_CharT, > year>(), > + __y); > } > > template<typename _CharT, typename _Traits, > @@ -3721,18 +3771,9 @@ namespace __detail > inline basic_ostream<_CharT, _Traits>& > operator<<(basic_ostream<_CharT, _Traits>& __os, const weekday& __wd) > { > - using _Ctx = __format::__format_context<_CharT>; > - 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)); > - else > - { > - auto __c = __wd.c_encoding(); > - __os << std::vformat(__s.substr(6), make_format_args<_Ctx>(__c)); > - } > - return __os; > + return __detail::__chrono_write(__os, __os.getloc(), > + __detail::__empty_fmt<_CharT, > weekday>(), > + __wd); > } > > template<typename _CharT, typename _Traits, > @@ -3754,23 +3795,14 @@ namespace __detail > operator<<(basic_ostream<_CharT, _Traits>& __os, > const weekday_indexed& __wdi) > { > - // The standard says to format wdi.weekday() and wdi.index() using > - // either "{:L}[{}]" or "{:L}[{} is not a valid index]". The {:L} > spec > - // means to format the weekday using ostringstream, so just do that. > - basic_stringstream<_CharT> __os2; > - __os2.imbue(__os.getloc()); > - __os2 << __wdi.weekday(); > - const auto __i = __wdi.index(); > - basic_string_view<_CharT> __s > - = _GLIBCXX_WIDEN("[ is not a valid index]"); > - __os2 << __s[0]; > - __os2 << std::format(_GLIBCXX_WIDEN("{}"), __i); > - if (__i >= 1 && __i <= 5) > - __os2 << __s.back(); > - else > - __os2 << __s.substr(1); > - __os << __os2.view(); > - return __os; > + // [time.cal.wdidx.nonmembers] > + basic_string_view<_CharT> __s1 = _GLIBCXX_WIDEN("{:L}[{}]"); > + basic_string_view<_CharT> __s2 > + = _GLIBCXX_WIDEN("{:L}[{} is not a valid index]"); > + auto __i = __wdi.index(); > + return __detail::__chrono_write(__os, __os.getloc(), > + __i >= 1 && __i <= 5 ? __s1 : __s2, > + __wdi.weekday(), __i); > This should not be needed, simple "{}" should work. > } > > template<typename _CharT, typename _Traits> > @@ -3778,12 +3810,10 @@ namespace __detail > operator<<(basic_ostream<_CharT, _Traits>& __os, > const weekday_last& __wdl) > { > - // As above, just write straight to a stringstream, as if by > "{:L}[last]" > - basic_stringstream<_CharT> __os2; > - __os2.imbue(__os.getloc()); > - __os2 << __wdl.weekday() << _GLIBCXX_WIDEN("[last]"); > - __os << __os2.view(); > - return __os; > + // [time.cal.wdlast.nonmembers] > + return __detail::__chrono_write(__os, __os.getloc(), > + _GLIBCXX_WIDEN("{:L}[last]"), > + __wdl.weekday()); > } > > template<typename _CharT, typename _Traits> > @@ -3906,12 +3936,8 @@ namespace __detail > operator<<(basic_ostream<_CharT, _Traits>& __os, > const year_month_day& __ymd) > { > - 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)); > - return __os; > + return __detail::__chrono_write( > + __os, __detail::__empty_fmt<_CharT, year_month_day>(), __ymd); > } > > template<typename _CharT, typename _Traits, > @@ -3994,7 +4020,8 @@ namespace __detail > operator<<(basic_ostream<_CharT, _Traits>& __os, > const hh_mm_ss<_Duration>& __hms) > { > - return __os << format(__os.getloc(), _GLIBCXX_WIDEN("{:L%T}"), > __hms); > + return __detail::__chrono_write(__os, __os.getloc(), > + _GLIBCXX_WIDEN("{:L%T}"), __hms); > } > > #if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI > @@ -4003,7 +4030,8 @@ 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); > + return __detail::__chrono_write( > + __os, __os.getloc(), __detail::__empty_fmt<_CharT, sys_info>(), > __i); > } > > /// Writes a local_info object to an ostream in an unspecified format. > @@ -4033,8 +4061,8 @@ 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); > - return __os; > + return __detail::__chrono_write(__os, __os.getloc(), > + _GLIBCXX_WIDEN("{:L%F %T %Z}"), __t); > } > #endif > > @@ -4045,8 +4073,8 @@ namespace __detail > operator<<(basic_ostream<_CharT, _Traits>& __os, > const sys_time<_Duration>& __tp) > { > - __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), > __tp); > - return __os; > + return __detail::__chrono_write(__os, __os.getloc(), > + _GLIBCXX_WIDEN("{:L%F %T}"), __tp); > } > > template<typename _CharT, typename _Traits> > @@ -4090,8 +4118,8 @@ namespace __detail > operator<<(basic_ostream<_CharT, _Traits>& __os, > const utc_time<_Duration>& __t) > { > - __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), > __t); > - return __os; > + return __detail::__chrono_write(__os, __os.getloc(), > + _GLIBCXX_WIDEN("{:L%F %T}"), __t); > } > > template<typename _CharT, typename _Traits, typename _Duration, > @@ -4125,8 +4153,8 @@ namespace __detail > operator<<(basic_ostream<_CharT, _Traits>& __os, > const tai_time<_Duration>& __t) > { > - __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), > __t); > - return __os; > + return __detail::__chrono_write(__os, __os.getloc(), > + _GLIBCXX_WIDEN("{:L%F %T}"), __t); > } > > template<typename _CharT, typename _Traits, typename _Duration, > @@ -4164,8 +4192,8 @@ namespace __detail > operator<<(basic_ostream<_CharT, _Traits>& __os, > const gps_time<_Duration>& __t) > { > - __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), > __t); > - return __os; > + return __detail::__chrono_write(__os, __os.getloc(), > + _GLIBCXX_WIDEN("{:L%F %T}"), __t); > } > > template<typename _CharT, typename _Traits, typename _Duration, > @@ -4202,8 +4230,8 @@ namespace __detail > operator<<(basic_ostream<_CharT, _Traits>& __os, > const file_time<_Duration>& __t) > { > - __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), > __t); > - return __os; > + return __detail::__chrono_write(__os, __os.getloc(), > + _GLIBCXX_WIDEN("{:L%F %T}"), __t); > } > > template<typename _CharT, typename _Traits, typename _Duration, > -- > 2.34.1 > >
