On Tue, Jun 30, 2026 at 12:04 PM Tomasz Kaminski <[email protected]> wrote:
> > > 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. >> > This !ok() checks are already handled by internal debug mode, that is enabled if empty-spec is used, so are not needed. Anyway, the type formatters if checking if format specifiers (like "%d") can be used in combination with specific type (and not particular object value), but here we used a fixed and always valid format string, so we can simply skip them by using _Dynamic_format_string. > >> 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 >> >>
