On Thu, Jun 12, 2025 at 3:22 PM Jonathan Wakely <[email protected]> wrote:
> On 06/06/25 12:41 +0200, Tomasz Kamiński wrote:
> >This patch change implementation of the formatters for sys_info and
> local_info,
> >so they no longer delegate to operator<< for ostream in case of empty
> spec.
> >As this types may be only formatted with empty chrono-spec (with
> padding), they now
> >use a separate __formatter_chrono_info formatter.
> >
> >The __formatter_chrono_info formats sys_info using format_to call with
> format
> >specifier extracted from corresponding operator<<, that now delegates to
> format
> >with empty spec. For local_info we replicate functionality of the
> operator<<.
> >The alignment and padding is handled using an _Padding_sink.
> >
> >libstdc++-v3/ChangeLog:
> >
> > * include/bits/chrono_io.h (__format::__formatter_chrono_info):
> > Define.
> > (std::formatter<chrono::sys_info, _CharT>)
> > (std::formatter<chrono::local_inf, _CharT>): Delegate to
> > __format::__formatter_chrono_info.
> > (std::operator<<(basic_ostream<_CharT, _Traits>& const sys_info&)):
> > Use format on sys_info with empty format spec.
> >---
> > libstdc++-v3/include/bits/chrono_io.h | 132 +++++++++++++++++++++++---
> > 1 file changed, 117 insertions(+), 15 deletions(-)
> >
> >diff --git a/libstdc++-v3/include/bits/chrono_io.h
> b/libstdc++-v3/include/bits/chrono_io.h
> >index 24c7dfb91c9..bbbae3d3064 100644
> >--- a/libstdc++-v3/include/bits/chrono_io.h
> >+++ b/libstdc++-v3/include/bits/chrono_io.h
> >@@ -1948,6 +1948,104 @@ namespace __format
> > }
> > };
> >
> >+ template<typename _CharT>
> >+ struct __formatter_chrono_info
> >+ {
> >+ constexpr typename basic_format_parse_context<_CharT>::iterator
> >+ _M_parse(basic_format_parse_context<_CharT>& __pc)
> >+ {
> >+ auto __first = __pc.begin();
> >+ auto __last = __pc.end();
> >+
> >+ _Spec<_CharT> __spec{};
> >+
> >+ auto __finished = [&] {
> >+ if (__first == __last || *__first == '}')
> >+ {
> >+ _M_spec = __spec;
> >+ return true;
> >+ }
> >+ return false;
> >+ };
> >+
> >+ if (__finished())
> >+ return __first;
> >+
> >+ __first = __spec._M_parse_fill_and_align(__first, __last);
> >+ if (__finished())
> >+ return __first;
> >+
> >+ __first = __spec._M_parse_width(__first, __last, __pc);
> >+ if (__finished())
> >+ return __first;
> >+
> >+ return __spec._M_parse_locale(__first, __last);
>
> It looks like if we reach this point, i.e. the L option is present,
> then we don't do _M_spec = __spec?
>
> And do we want to cause parsing to fail if __finished() == false after
> parsing the locale option? Otherwise doesn't it mean we will ignore
> any garbage after the L in the format string?
>
> Ah no, I see you handle that in formatter::parse after calling
> _M_f._M_parse.
>
> But I don't think it's true that the chrono-specs must be empty for
> local_info and sys_info, consider:
>
> std::format("{:%n %t %%}", std::chrono::sys_info{})
>
> Does __formatter_chrono_info need to parse and store the chrono-specs,
> and then handle that in its _M_format? Only whitespace characters are
> valid, and if there's a non-empty chrono-specs then we don't use the
> format arg at all, just loop over the chrono-specs and output the
> relevant whitespace chars. And that loop can be [[unlikely]] because
> I doubt anybody will ever do this!
>
The only from [time.format] specification for sys_info and local_info
available,
is that empty spec gives equivalent output to the osteam operator. Yes,
I agree that you can use sys_info to print whitespaces or %, but unless the
standard
will be explicit that we need to do that, I would prefer not supporting it.
Even if we risk regression, and someone submit a bug, they will not be able
to point in standard where this is required.
>
>
> >+ }
> >+
> >+ template<typename _Info, typename _Out>
> >+ typename basic_format_context<_Out, _CharT>::iterator
> >+ format(const _Info& __i,
> >+ basic_format_context<_Out, _CharT>& __fc) const
> >+ {
> >+ const size_t __padwidth = _M_spec._M_get_width(__fc);
> >+ if (__padwidth == 0)
> >+ return _M_format_to(__fc.out(), __i);
> >+
> >+ _Padding_sink<_Out, _CharT> __sink(__fc.out(), __padwidth);
> >+ _M_format_to(__sink.out(), __i);
> >+ return __sink._M_finish(_M_spec._M_align, _M_spec._M_fill);
> >+ }
> >+
> >+ private:
> >+ template<typename _Out>
> >+ _Out
> >+ _M_format_to(_Out __out, const chrono::sys_info& __si) const
> >+ {
> >+ using _FmtStr = _Runtime_format_string<_CharT>;
> >+ // n.b. only decimal separator is locale dependent for
> specifiers
> >+ // used below, as sys_info uses seconds and minutes duration,
> the
> >+ // output is locale-independent.
> >+ constexpr auto* __fs
> >+ = _GLIBCXX_WIDEN("[{0:%F %T},{1:%F
> %T},{2:%T},{3:%Q%q},{0:%Z}]");
> >+ const chrono::local_seconds __lb(__si.begin.time_since_epoch());
> >+ return std::format_to(std::move(__out), _FmtStr(__fs),
> >+ chrono::local_time_format(__lb,
> &__si.abbrev),
> >+ __si.end, __si.offset, __si.save);
> >+ }
> >+
> >+ template<typename _Out>
> >+ _Out
> >+ _M_format_to(_Out __out, const chrono::local_info& __li) const
> >+ {
> >+ *__out = _Separators<_CharT>::_S_squares()[0];
> >+ ++__out;
> >+ if (__li.result == chrono::local_info::unique)
> >+ __out = _M_format_to(std::move(__out), __li.first);
> >+ else
> >+ {
> >+ basic_string_view<_CharT> __sv;
> >+ if (__li.result == chrono::local_info::nonexistent)
> >+ __sv =_GLIBCXX_WIDEN("nonexistent");
> >+ else
> >+ __sv = _GLIBCXX_WIDEN("ambiguous");
> >+ __out = __format::__write(std::move(__out), __sv);
> >+
> >+ __sv = _GLIBCXX_WIDEN(" local time between ");
> >+ __out = __format::__write(std::move(__out), __sv);
> >+ __out = _M_format_to(std::move(__out), __li.first);
> >+
> >+ __sv = _GLIBCXX_WIDEN(" and ");
> >+ __out = __format::__write(std::move(__out), __sv);
> >+ __out = _M_format_to(std::move(__out), __li.second);
> >+ }
> >+ *__out = _Separators<_CharT>::_S_squares()[1];
> >+ ++__out;
> >+ return std::move(__out);
> >+ }
> >+
> >+ _Spec<_CharT> _M_spec;
> >+ };
> >+
> > } // namespace __format
> > /// @endcond
> >
> >@@ -2438,16 +2536,22 @@ namespace __format
> > {
> > constexpr typename basic_format_parse_context<_CharT>::iterator
> > parse(basic_format_parse_context<_CharT>& __pc)
> >- { return _M_f._M_parse(__pc, __format::_ChronoParts{}); }
> >+ {
> >+ auto __res = _M_f._M_parse(__pc);
> >+ if (__res == __pc.end() || *__res == '}')
> >+ return __res;
> >+ std::__throw_format_error("format error: invalid format-spec for "
> >+ "std::chrono::sys_info");
> >+ }
> >
> > template<typename _Out>
> > typename basic_format_context<_Out, _CharT>::iterator
> > format(const chrono::sys_info& __i,
> > basic_format_context<_Out, _CharT>& __fc) const
> >- { return _M_f._M_format(__i, __fc); }
> >+ { return _M_f.format(__i, __fc); }
> >
> > private:
> >- __format::__formatter_chrono<_CharT> _M_f;
> >+ __format::__formatter_chrono_info<_CharT> _M_f;
> > };
> >
> > template<__format::__char _CharT>
> >@@ -2455,16 +2559,22 @@ namespace __format
> > {
> > constexpr typename basic_format_parse_context<_CharT>::iterator
> > parse(basic_format_parse_context<_CharT>& __pc)
> >- { return _M_f._M_parse(__pc, __format::_ChronoParts{}); }
> >+ {
> >+ auto __res = _M_f._M_parse(__pc);
> >+ if (__res == __pc.end() || *__res == '}')
> >+ return __res;
> >+ std::__throw_format_error("format error: invalid format-spec for "
> >+ "std::chrono::local_info");
> >+ }
> >
> > template<typename _Out>
> > typename basic_format_context<_Out, _CharT>::iterator
> > format(const chrono::local_info& __i,
> > basic_format_context<_Out, _CharT>& __fc) const
> >- { return _M_f._M_format(__i, __fc); }
> >+ { return _M_f.format(__i, __fc); }
> >
> > private:
> >- __format::__formatter_chrono<_CharT> _M_f;
> >+ __format::__formatter_chrono_info<_CharT> _M_f;
> > };
> > #endif
> >
> >@@ -3326,15 +3436,7 @@ namespace __detail
> > basic_ostream<_CharT, _Traits>&
> > operator<<(basic_ostream<_CharT, _Traits>& __os, const sys_info& __i)
> > {
> >- // n.b. only decimal separator is locale dependent for specifiers
> >- // used below, as sys_info uses seconds and minutes duration, the
> >- // output is locale-independent.
> >- constexpr auto* __fs
> >- = _GLIBCXX_WIDEN("[{0:%F %T},{1:%F %T},{2:%T},{3:%Q%q},{0:%Z}]");
> >- local_seconds __lb(__i.begin.time_since_epoch());
> >- __os << std::format(__fs, local_time_format(__lb, &__i.abbrev),
> >- __i.end, __i.offset, __i.save);
> >- return __os;
> >+ return __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{}"),
> __i);
> > }
> >
> > /// Writes a local_info object to an ostream in an unspecified format.
> >--
> >2.49.0
> >
> >
>
>