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

Reply via email to