On Thu, 12 Jun 2025 at 14:40, Tomasz Kaminski <tkami...@redhat.com> wrote:
>
>
>
> On Thu, Jun 12, 2025 at 3:22 PM Jonathan Wakely <jwak...@redhat.com> 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.

I don't think we can argue it's not required to work (currently,
unless we change it as a DR).

The standard shows:
template<class charT> struct formatter<chrono::sys_info, charT>;
template<class charT> struct formatter<chrono::local_info, charT>;
and it says what format-chrono-spec means, and it only says we can
reject a chrono-spec if the type doesn't provide the info needed, but
there is no info needed for "%n %t %%" so it should be valid for all
chrono types.

The standard doesn't seem to say much about how those types are
formatted, but for "%n %t %%" I don't think there's any ambiguity.


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

Reply via email to