[PATCH v3 2/2] libstdc++: Use __chrono_write via _S_empty_fs for chrono ostream insertion
Jonathan Wakely
jwakely@redhat.com
Thu Jul 23 10:47:03 GMT 2026
On Wed, 01 Jul 2026 at 14:33 +0200, Tomasz Kaminski wrote:
>On Wed, Jul 1, 2026 at 2:15 PM Anlai Lu <agicy@qq.com> wrote:
>
>> Add __detail::__chrono_write which formats a chrono object into a
>> stack buffer via std::format_to_n with _S_empty_fs(), then writes
>> through __ostream_insert. A non-type template parameter _BufSize
>> (default 128) allows per-type buffer tuning. Each _BufSize is the
>> smallest power of two that accommodates the longest possible formatted
>> output for that type, including error cases. All chrono operator<<
>> overloads that previously used std::format, std::vformat, or
>> basic_stringstream now use this helper.
>>
>> libstdc++-v3/ChangeLog:
>>
>> * include/bits/chrono_io.h (__formatter_chrono::_S_empty_fs):
>> Make public.
>> (__detail::__chrono_write): New function template.
>> (operator<<): Use __chrono_write consistently for all chrono
>> types.
>>
>> Signed-off-by: Anlai Lu <agicy@qq.com>
>> ---
>> libstdc++-v3/include/bits/chrono_io.h | 244 ++++++--------------------
>> 1 file changed, 52 insertions(+), 192 deletions(-)
>>
>LGTM. Thank you for working on this.
>
>>
>> diff --git a/libstdc++-v3/include/bits/chrono_io.h
>> b/libstdc++-v3/include/bits/chrono_io.h
>> index f8bb485d1..d3740a0e1 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,30 @@ namespace __detail
>> }
>> }
>>
>> + // Format into a stack buffer via std::format_to_n with the empty
>> + // chrono-spec (_S_empty_fs), then write through __ostream_insert.
>> + // The empty spec lets each formatter use its __defSpec, producing
>> + // output equivalent to the corresponding operator<<.
>> + // _BufSize allows per-type tuning of the stack buffer size.
>> + template<size_t _BufSize = 128, typename _CharT, typename _Traits,
>>
>I am wondering about making buffer size explicit on each call, and
>removing the default. But I will handle that before committing, if Jonathan
>also like that direction.
What would be the advantage of requiring it explicitly every time?
>> + 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;
>>
>Nice trick for storing a function pointer. I really like it.
We could just make _S_empty_spec public, and then use that instead of
_S_empty_fs(), right?
That seems simpler to me.
constexpr auto __fs = __format::__formatter_chrono<_CharT>::_S_empty_spec;
and then just use __fs instead of __fs()
>> + constexpr size_t __bufsize = _BufSize;
>> + _CharT __buf[__bufsize];
>> + auto __res = std::format_to_n(__buf, __bufsize, __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());
>> + }
>> +
>> } // namespace __detail
>> /// @endcond
>>
>> @@ -3629,14 +3655,7 @@ 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<32>(__os, __d);
>> }
>>
>> template<typename _CharT, typename _Traits,
>> @@ -3657,18 +3676,7 @@ 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, __m, __os.getloc());
>> }
>>
>> template<typename _CharT, typename _Traits,
>> @@ -3689,18 +3697,7 @@ 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<32>(__os, __y);
>> }
>>
>> template<typename _CharT, typename _Traits,
>> @@ -3721,18 +3718,7 @@ 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, __wd, __os.getloc());
>> }
>>
>> template<typename _CharT, typename _Traits,
>> @@ -3754,23 +3740,7 @@ 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;
>> + return __detail::__chrono_write(__os, __wdi, __os.getloc());
>> }
>>
>> template<typename _CharT, typename _Traits>
>> @@ -3778,29 +3748,14 @@ 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;
>> + return __detail::__chrono_write(__os, __wdl, __os.getloc());
>> }
>>
>> template<typename _CharT, typename _Traits>
>> inline basic_ostream<_CharT, _Traits>&
>> operator<<(basic_ostream<_CharT, _Traits>& __os, const month_day&
>> __md)
>> {
>> - // As above, just write straight to a stringstream, as if by
>> "{:L}/{}"
>> - basic_stringstream<_CharT> __os2;
>> - __os2.imbue(__os.getloc());
>> - __os2 << __md.month();
>> - if constexpr (is_same_v<_CharT, char>)
>> - __os2 << '/';
>> - else
>> - __os2 << L'/';
>> - __os2 << __md.day();
>> - __os << __os2.view();
>> - return __os;
>> + return __detail::__chrono_write(__os, __md, __os.getloc());
>> }
>>
>> template<typename _CharT, typename _Traits,
>> @@ -3824,12 +3779,7 @@ namespace __detail
>> operator<<(basic_ostream<_CharT, _Traits>& __os,
>> const month_day_last& __mdl)
>> {
>> - // As above, just write straight to a stringstream, as if by
>> "{:L}/last"
>> - basic_stringstream<_CharT> __os2;
>> - __os2.imbue(__os.getloc());
>> - __os2 << __mdl.month() << _GLIBCXX_WIDEN("/last");
>> - __os << __os2.view();
>> - return __os;
>> + return __detail::__chrono_write(__os, __mdl, __os.getloc());
>> }
>>
>> template<typename _CharT, typename _Traits>
>> @@ -3837,17 +3787,7 @@ namespace __detail
>> operator<<(basic_ostream<_CharT, _Traits>& __os,
>> const month_weekday& __mwd)
>> {
>> - // As above, just write straight to a stringstream, as if by
>> "{:L}/{:L}"
>> - basic_stringstream<_CharT> __os2;
>> - __os2.imbue(__os.getloc());
>> - __os2 << __mwd.month();
>> - if constexpr (is_same_v<_CharT, char>)
>> - __os2 << '/';
>> - else
>> - __os2 << L'/';
>> - __os2 << __mwd.weekday_indexed();
>> - __os << __os2.view();
>> - return __os;
>> + return __detail::__chrono_write(__os, __mwd, __os.getloc());
>> }
>>
>> template<typename _CharT, typename _Traits>
>> @@ -3855,34 +3795,14 @@ namespace __detail
>> operator<<(basic_ostream<_CharT, _Traits>& __os,
>> const month_weekday_last& __mwdl)
>> {
>> - // As above, just write straight to a stringstream, as if by
>> "{:L}/{:L}"
>> - basic_stringstream<_CharT> __os2;
>> - __os2.imbue(__os.getloc());
>> - __os2 << __mwdl.month();
>> - if constexpr (is_same_v<_CharT, char>)
>> - __os2 << '/';
>> - else
>> - __os2 << L'/';
>> - __os2 << __mwdl.weekday_last();
>> - __os << __os2.view();
>> - return __os;
>> + return __detail::__chrono_write(__os, __mwdl, __os.getloc());
>> }
>>
>> template<typename _CharT, typename _Traits>
>> inline basic_ostream<_CharT, _Traits>&
>> operator<<(basic_ostream<_CharT, _Traits>& __os, const year_month&
>> __ym)
>> {
>> - // As above, just write straight to a stringstream, as if by
>> "{}/{:L}"
>> - basic_stringstream<_CharT> __os2;
>> - __os2.imbue(__os.getloc());
>> - __os2 << __ym.year();
>> - if constexpr (is_same_v<_CharT, char>)
>> - __os2 << '/';
>> - else
>> - __os2 << L'/';
>> - __os2 << __ym.month();
>> - __os << __os2.view();
>> - return __os;
>> + return __detail::__chrono_write(__os, __ym, __os.getloc());
>> }
>>
>> template<typename _CharT, typename _Traits,
>> @@ -3906,12 +3826,7 @@ 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<64>(__os, __ymd);
>> }
>>
>> template<typename _CharT, typename _Traits,
>> @@ -3936,17 +3851,7 @@ namespace __detail
>> operator<<(basic_ostream<_CharT, _Traits>& __os,
>> const year_month_day_last& __ymdl)
>> {
>> - // As above, just write straight to a stringstream, as if by
>> "{}/{:L}"
>> - basic_stringstream<_CharT> __os2;
>> - __os2.imbue(__os.getloc());
>> - __os2 << __ymdl.year();
>> - if constexpr (is_same_v<_CharT, char>)
>> - __os2 << '/';
>> - else
>> - __os2 << L'/';
>> - __os2 << __ymdl.month_day_last();
>> - __os << __os2.view();
>> - return __os;
>> + return __detail::__chrono_write(__os, __ymdl, __os.getloc());
>> }
>>
>> template<typename _CharT, typename _Traits>
>> @@ -3954,19 +3859,7 @@ namespace __detail
>> operator<<(basic_ostream<_CharT, _Traits>& __os,
>> const year_month_weekday& __ymwd)
>> {
>> - // As above, just write straight to a stringstream, as if by
>> - // "{}/{:L}/{:L}"
>> - basic_stringstream<_CharT> __os2;
>> - __os2.imbue(__os.getloc());
>> - _CharT __slash;
>> - if constexpr (is_same_v<_CharT, char>)
>> - __slash = '/';
>> - else
>> - __slash = L'/';
>> - __os2 << __ymwd.year() << __slash << __ymwd.month() << __slash
>> - << __ymwd.weekday_indexed();
>> - __os << __os2.view();
>> - return __os;
>> + return __detail::__chrono_write(__os, __ymwd, __os.getloc());
>> }
>>
>> template<typename _CharT, typename _Traits>
>> @@ -3974,19 +3867,7 @@ namespace __detail
>> operator<<(basic_ostream<_CharT, _Traits>& __os,
>> const year_month_weekday_last& __ymwdl)
>> {
>> - // As above, just write straight to a stringstream, as if by
>> - // "{}/{:L}/{:L}"
>> - basic_stringstream<_CharT> __os2;
>> - __os2.imbue(__os.getloc());
>> - _CharT __slash;
>> - if constexpr (is_same_v<_CharT, char>)
>> - __slash = '/';
>> - else
>> - __slash = L'/';
>> - __os2 << __ymwdl.year() << __slash << __ymwdl.month() << __slash
>> - << __ymwdl.weekday_last();
>> - __os << __os2.view();
>> - return __os;
>> + return __detail::__chrono_write(__os, __ymwdl, __os.getloc());
>> }
>>
>> template<typename _CharT, typename _Traits, typename _Duration>
>> @@ -3994,7 +3875,7 @@ 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<64>(__os, __hms, __os.getloc());
>> }
>>
>> #if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI
>> @@ -4003,7 +3884,7 @@ 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, __i, __os.getloc());
>> }
>>
>> /// Writes a local_info object to an ostream in an unspecified format.
>> @@ -4011,20 +3892,7 @@ namespace __detail
>> basic_ostream<_CharT, _Traits>&
>> operator<<(basic_ostream<_CharT, _Traits>& __os, const local_info&
>> __li)
>> {
>> - __os << __format::_Separators<_CharT>::_S_squares()[0];
>> - if (__li.result == local_info::unique)
>> - __os << __li.first;
>> - else
>> - {
>> - if (__li.result == local_info::nonexistent)
>> - __os << _GLIBCXX_WIDEN("nonexistent");
>> - else
>> - __os << _GLIBCXX_WIDEN("ambiguous");
>> - __os << _GLIBCXX_WIDEN(" local time between ") << __li.first;
>> - __os << _GLIBCXX_WIDEN(" and ") << __li.second;
>> - }
>> - __os << __format::_Separators<_CharT>::_S_squares()[1];
>> - return __os;
>> + return __detail::__chrono_write<256>(__os, __li);
>> }
>>
>> template<typename _CharT, typename _Traits, typename _Duration,
>> @@ -4033,8 +3901,7 @@ 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, __t, __os.getloc());
>> }
>> #endif
>>
>> @@ -4045,16 +3912,14 @@ 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<64>(__os, __tp, __os.getloc());
>> }
>>
>> template<typename _CharT, typename _Traits>
>> inline basic_ostream<_CharT, _Traits>&
>> operator<<(basic_ostream<_CharT, _Traits>& __os, const sys_days& __dp)
>> {
>> - __os << year_month_day{__dp};
>> - return __os;
>> + return __detail::__chrono_write<32>(__os, __dp);
>> }
>>
>> template<typename _CharT, typename _Traits, typename _Duration,
>> @@ -4090,8 +3955,7 @@ 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<64>(__os, __t, __os.getloc());
>> }
>>
>> template<typename _CharT, typename _Traits, typename _Duration,
>> @@ -4125,8 +3989,7 @@ 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<64>(__os, __t, __os.getloc());
>> }
>>
>> template<typename _CharT, typename _Traits, typename _Duration,
>> @@ -4164,8 +4027,7 @@ 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<64>(__os, __t, __os.getloc());
>> }
>>
>> template<typename _CharT, typename _Traits, typename _Duration,
>> @@ -4202,8 +4064,7 @@ 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<64>(__os, __t, __os.getloc());
>> }
>>
>> template<typename _CharT, typename _Traits, typename _Duration,
>> @@ -4228,8 +4089,7 @@ namespace __detail
>> // 4257. Stream insertion for chrono::local_time should be constrained
>> requires requires(const sys_time<_Duration>& __st) { __os << __st; }
>> {
>> - __os << sys_time<_Duration>{__lt.time_since_epoch()};
>> - return __os;
>> + return __detail::__chrono_write<64>(__os, __lt, __os.getloc());
>> }
>>
>> template<typename _CharT, typename _Traits, typename _Duration,
>> --
>> 2.34.1
>>
>>
More information about the Libstdc++
mailing list