[PATCH v2 2/2] libstdc++: Use __chrono_write and __empty_fmt for chrono ostream insertion
Tomasz Kaminski
tkaminsk@redhat.com
Tue Jun 30 10:04:32 GMT 2026
On Tue, Jun 30, 2026 at 11:18 AM Anlai Lu <agicy@qq.com> 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 <agicy@qq.com>
> ---
> 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
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260630/e364de73/attachment-0001.htm>
More information about the Libstdc++
mailing list