]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
libstdc++: Constrain formatter for thread::id [PR119918]
authorTomasz Kamiński <tkaminsk@redhat.com>
Thu, 24 Apr 2025 07:32:24 +0000 (09:32 +0200)
committerTomasz Kamiński <tkaminsk@redhat.com>
Fri, 25 Apr 2025 11:02:04 +0000 (13:02 +0200)
This patch add constraint __formatter::__char to _CharT type parameter
of formatter<thread::id, _CharT> specialization, matching the constraint
of formatting of integer/pointers that are used as native handles.

The dependency on <format> header, is changed to <bits/formatfwd.h>.
To achieve that, formatting of pointers is extracted from void const*
specialization to internal __formatter_ptr<_CharT>, that can be forward
declared.

Finally, the handle representation is now printed directly to __fc.out(),
by the formatter for handle type. To support this, internal formatters
can now be constructed from _Spec object as alternative to invoking parse
method.

PR libstdc++/119918

libstdc++-v3/ChangeLog:

* include/bits/formatfwd.h (__format::_Align): Moved from std/format.
(std::__throw_format_error, __format::__formatter_str)
(__format::__formatter_ptr): Declare.
* include/std/format (__format::_Align): Moved to bits/formatfwd.h.
(__formatter_int::__formatter_int): Define.
(__format::__formatter_ptr): Extracted from formatter for const void*.
(std::formatter<const void*, _CharT>, formatter<void*, _CharT>)
(std::formatter<nullptr_t, _CharT>): Delegate to __formatter_ptr<_CharT>.
* include/std/thread (std::formatter<thread::id, _CharT>): Constrain
_CharT template parameter.
(formatter<thread::id, _CharT>::parse): Specify default aligment, and
qualify __throw_format_error to disable ADL.
(formatter<thread::id, _CharT>::format): Use formatters to write directly
to output.
* testsuite/30_threads/thread/id/output.cc: Tests for formatting thread::id
representing not-a-thread with padding and formattable concept.

Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
libstdc++-v3/include/bits/formatfwd.h
libstdc++-v3/include/std/format
libstdc++-v3/include/std/thread
libstdc++-v3/testsuite/30_threads/thread/id/output.cc

index 9ba658b078a50dafc5cd78283f5cec141f578497..12ae2ad2ac0814244f9a693e1eaae6dbf663b1ea 100644 (file)
@@ -57,6 +57,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   template<typename _Tp, typename _CharT = char> struct formatter;
 
 /// @cond undocumented
+  [[noreturn]]
+  inline void
+  __throw_format_error(const char* __what);
+
 namespace __format
 {
 #ifdef _GLIBCXX_USE_WCHAR_T
@@ -67,6 +71,19 @@ namespace __format
     concept __char = same_as<_CharT, char>;
 #endif
 
+  enum _Align {
+    _Align_default,
+    _Align_left,
+    _Align_right,
+    _Align_centre,
+  };
+
+  template<typename _CharT> struct _Spec;
+
+  template<__char _CharT> struct __formatter_str;
+  template<__char _CharT> struct __formatter_int;
+  template<__char _CharT> struct __formatter_ptr;
+
   template<typename _Tp, typename _Context,
           typename _Formatter
             = typename _Context::template formatter_type<remove_const_t<_Tp>>,
@@ -107,9 +124,6 @@ namespace __format
     {
       __f.set_debug_format();
     };
-
-  template<__char _CharT>
-    struct __formatter_int;
 } // namespace __format
 /// @endcond
 
@@ -141,7 +155,6 @@ namespace __format
 }
 #endif // format_ranges
 
-
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace std
 #endif // __glibcxx_format
index 7d3067098befea0c9dbd0da95c9995972c0500ea..86c93f0e6ebd674ad79ba2eafede26b5f46ae1ea 100644 (file)
@@ -491,13 +491,6 @@ namespace __format
     _Pres_esc = 0xf,          // For strings, charT and ranges
   };
 
-  enum _Align {
-    _Align_default,
-    _Align_left,
-    _Align_right,
-    _Align_centre,
-  };
-
   enum _Sign {
     _Sign_default,
     _Sign_plus,
@@ -1440,6 +1433,13 @@ namespace __format
       static constexpr _Pres_type _AsBool = _Pres_s;
       static constexpr _Pres_type _AsChar = _Pres_c;
 
+      __formatter_int() = default;
+
+      constexpr
+      __formatter_int(_Spec<_CharT> __spec) noexcept
+      : _M_spec(__spec)
+      { }
+
       constexpr typename basic_format_parse_context<_CharT>::iterator
       _M_do_parse(basic_format_parse_context<_CharT>& __pc, _Pres_type __type)
       {
@@ -2387,6 +2387,134 @@ namespace __format
       _Spec<_CharT> _M_spec{};
     };
 
+  template<__format::__char _CharT>
+    struct __formatter_ptr
+    {
+      __formatter_ptr() = default;
+
+      constexpr
+      __formatter_ptr(_Spec<_CharT> __spec) noexcept
+      : _M_spec(__spec)
+      { }
+
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      {
+       __format::_Spec<_CharT> __spec{};
+       const auto __last = __pc.end();
+       auto __first = __pc.begin();
+
+       auto __finalize = [this, &__spec] {
+         _M_spec = __spec;
+       };
+
+       auto __finished = [&] {
+         if (__first == __last || *__first == '}')
+           {
+             __finalize();
+             return true;
+           }
+         return false;
+       };
+
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_fill_and_align(__first, __last);
+       if (__finished())
+         return __first;
+
+// _GLIBCXX_RESOLVE_LIB_DEFECTS
+// P2510R3 Formatting pointers
+#if __glibcxx_format >= 202304L
+       __first = __spec._M_parse_zero_fill(__first, __last);
+       if (__finished())
+         return __first;
+#endif
+
+       __first = __spec._M_parse_width(__first, __last, __pc);
+
+       if (__first != __last)
+         {
+           if (*__first == 'p')
+             ++__first;
+#if __glibcxx_format >= 202304L
+           else if (*__first == 'P')
+           {
+             __spec._M_type = __format::_Pres_P;
+             ++__first;
+           }
+#endif
+         }
+
+       if (__finished())
+         return __first;
+
+       __format::__failed_to_parse_format_spec();
+      }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(const void* __v, basic_format_context<_Out, _CharT>& __fc) const
+       {
+         auto __u = reinterpret_cast<__UINTPTR_TYPE__>(__v);
+         char __buf[2 + sizeof(__v) * 2];
+         auto [__ptr, __ec] = std::to_chars(__buf + 2, std::end(__buf),
+                                            __u, 16);
+         int __n = __ptr - __buf;
+         __buf[0] = '0';
+         __buf[1] = 'x';
+#if __glibcxx_format >= 202304L
+         if (_M_spec._M_type == __format::_Pres_P)
+           {
+             __buf[1] = 'X';
+             for (auto __p = __buf + 2; __p != __ptr; ++__p)
+#if __has_builtin(__builtin_toupper)
+               *__p = __builtin_toupper(*__p);
+#else
+               *__p = std::toupper(*__p);
+#endif
+           }
+#endif
+
+         basic_string_view<_CharT> __str;
+         if constexpr (is_same_v<_CharT, char>)
+           __str = string_view(__buf, __n);
+#ifdef _GLIBCXX_USE_WCHAR_T
+         else
+           {
+             auto __p = (_CharT*)__builtin_alloca(__n * sizeof(_CharT));
+             std::__to_wstring_numeric(__buf, __n, __p);
+             __str = wstring_view(__p, __n);
+           }
+#endif
+
+#if __glibcxx_format >= 202304L
+         if (_M_spec._M_zero_fill)
+           {
+             size_t __width = _M_spec._M_get_width(__fc);
+             if (__width <= __str.size())
+               return __format::__write(__fc.out(), __str);
+
+             auto __out = __fc.out();
+             // Write "0x" or "0X" prefix before zero-filling.
+             __out = __format::__write(std::move(__out), __str.substr(0, 2));
+             __str.remove_prefix(2);
+             size_t __nfill = __width - __n;
+             return __format::__write_padded(std::move(__out), __str,
+                                             __format::_Align_right,
+                                             __nfill, _CharT('0'));
+           }
+#endif
+
+         return __format::__write_padded_as_spec(__str, __n, __fc, _M_spec,
+                                                 __format::_Align_right);
+       }
+
+    private:
+      __format::_Spec<_CharT> _M_spec{};
+    };
+
 } // namespace __format
 /// @endcond
 
@@ -2851,120 +2979,15 @@ namespace __format
 
       constexpr typename basic_format_parse_context<_CharT>::iterator
       parse(basic_format_parse_context<_CharT>& __pc)
-      {
-       __format::_Spec<_CharT> __spec{};
-       const auto __last = __pc.end();
-       auto __first = __pc.begin();
-
-       auto __finalize = [this, &__spec] {
-         _M_spec = __spec;
-       };
-
-       auto __finished = [&] {
-         if (__first == __last || *__first == '}')
-           {
-             __finalize();
-             return true;
-           }
-         return false;
-       };
-
-       if (__finished())
-         return __first;
-
-       __first = __spec._M_parse_fill_and_align(__first, __last);
-       if (__finished())
-         return __first;
-
-// _GLIBCXX_RESOLVE_LIB_DEFECTS
-// P2510R3 Formatting pointers
-#if __glibcxx_format >= 202304L
-       __first = __spec._M_parse_zero_fill(__first, __last);
-       if (__finished())
-         return __first;
-#endif
-
-       __first = __spec._M_parse_width(__first, __last, __pc);
-
-       if (__first != __last)
-         {
-           if (*__first == 'p')
-             ++__first;
-#if __glibcxx_format >= 202304L
-           else if (*__first == 'P')
-           {
-             __spec._M_type = __format::_Pres_P;
-             ++__first;
-           }
-#endif
-         }
-
-       if (__finished())
-         return __first;
-
-       __format::__failed_to_parse_format_spec();
-      }
+      { return _M_f.parse(__pc); }
 
       template<typename _Out>
        typename basic_format_context<_Out, _CharT>::iterator
        format(const void* __v, basic_format_context<_Out, _CharT>& __fc) const
-       {
-         auto __u = reinterpret_cast<__UINTPTR_TYPE__>(__v);
-         char __buf[2 + sizeof(__v) * 2];
-         auto [__ptr, __ec] = std::to_chars(__buf + 2, std::end(__buf),
-                                            __u, 16);
-         int __n = __ptr - __buf;
-         __buf[0] = '0';
-         __buf[1] = 'x';
-#if __glibcxx_format >= 202304L
-         if (_M_spec._M_type == __format::_Pres_P)
-           {
-             __buf[1] = 'X';
-             for (auto __p = __buf + 2; __p != __ptr; ++__p)
-#if __has_builtin(__builtin_toupper)
-               *__p = __builtin_toupper(*__p);
-#else
-               *__p = std::toupper(*__p);
-#endif
-           }
-#endif
-
-         basic_string_view<_CharT> __str;
-         if constexpr (is_same_v<_CharT, char>)
-           __str = string_view(__buf, __n);
-#ifdef _GLIBCXX_USE_WCHAR_T
-         else
-           {
-             auto __p = (_CharT*)__builtin_alloca(__n * sizeof(_CharT));
-             std::__to_wstring_numeric(__buf, __n, __p);
-             __str = wstring_view(__p, __n);
-           }
-#endif
-
-#if __glibcxx_format >= 202304L
-         if (_M_spec._M_zero_fill)
-           {
-             size_t __width = _M_spec._M_get_width(__fc);
-             if (__width <= __str.size())
-               return __format::__write(__fc.out(), __str);
-
-             auto __out = __fc.out();
-             // Write "0x" or "0X" prefix before zero-filling.
-             __out = __format::__write(std::move(__out), __str.substr(0, 2));
-             __str.remove_prefix(2);
-             size_t __nfill = __width - __n;
-             return __format::__write_padded(std::move(__out), __str,
-                                             __format::_Align_right,
-                                             __nfill, _CharT('0'));
-           }
-#endif
-
-         return __format::__write_padded_as_spec(__str, __n, __fc, _M_spec,
-                                                 __format::_Align_right);
-       }
+       { return _M_f.format(__v, __fc); }
 
     private:
-      __format::_Spec<_CharT> _M_spec{};
+      __format::__formatter_ptr<_CharT> _M_f;
     };
 
   template<__format::__char _CharT>
@@ -2983,7 +3006,7 @@ namespace __format
        { return _M_f.format(__v, __fc); }
 
     private:
-      formatter<const void*, _CharT> _M_f;
+      __format::__formatter_ptr<_CharT> _M_f;
     };
 
   template<__format::__char _CharT>
@@ -3002,7 +3025,7 @@ namespace __format
        { return _M_f.format(nullptr, __fc); }
 
     private:
-      formatter<const void*, _CharT> _M_f;
+      __format::__formatter_ptr<_CharT> _M_f;
     };
   /// @}
 
index d2f91ad89953cfdb265c0c8c41827e13738fa162..0de08c0bd3e3601bf7a75dc444db068d97e76bf7 100644 (file)
@@ -297,7 +297,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif // __cpp_lib_jthread
 
 #ifdef __cpp_lib_formatters // C++ >= 23
-  template<typename _CharT>
+  // We deviate from the standard, that does not put requirements
+  // on _CharT here.
+  template<__format::__char _CharT>
     requires is_pointer_v<thread::native_handle_type>
       || is_integral_v<thread::native_handle_type>
     class formatter<thread::id, _CharT>
@@ -307,6 +309,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       parse(basic_format_parse_context<_CharT>& __pc)
       {
        __format::_Spec<_CharT> __spec{};
+       __spec._M_align = __format::_Align_right;
        const auto __last = __pc.end();
        auto __first = __pc.begin();
 
@@ -334,36 +337,34 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        if (__finished())
          return __first;
 
-       __throw_format_error("format error: invalid format-spec for "
-                            "std::thread::id");
+       std::__throw_format_error("format error: invalid format-spec for "
+                                 "std::thread::id");
       }
 
       template<typename _Out>
        typename basic_format_context<_Out, _CharT>::iterator
        format(thread::id __id, basic_format_context<_Out, _CharT>& __fc) const
        {
-         basic_string_view<_CharT> __sv;
-         if constexpr (is_same_v<_CharT, char>)
-           __sv = "{}thread::id of a non-executing thread";
-         else
-           __sv = L"{}thread::id of a non-executing thread";
-         basic_string<_CharT> __str;
+
          if (__id == thread::id())
-           __sv.remove_prefix(2);
-         else
            {
-             using _FmtStr = __format::_Runtime_format_string<_CharT>;
-             // Convert non-void pointers to const void* for formatted output.
-             using __output_type
-               = __conditional_t<is_pointer_v<thread::native_handle_type>,
-                                 const void*,
-                                 thread::native_handle_type>;
-             auto __o = static_cast<__output_type>(__id._M_thread);
-             __sv = __str = std::format(_FmtStr(__sv.substr(0, 2)), __o);
+             const _CharT* __msg;
+             if constexpr (is_same_v<_CharT, char>)
+               __msg = "thread::id of a non-executing thread";
+             else
+               __msg = L"thread::id of a non-executing thread";
+
+             __format::__formatter_str<_CharT> __formatter(_M_spec);
+             return __formatter.format(__msg, __fc);
            }
-         return __format::__write_padded_as_spec(__sv, __sv.size(),
-                                                 __fc, _M_spec,
-                                                 __format::_Align_right);
+
+         using _HandleFormatter
+           = __conditional_t<is_pointer_v<thread::native_handle_type>,
+                             __format::__formatter_ptr<_CharT>,
+                             __format::__formatter_int<_CharT>>;
+
+         _HandleFormatter __formatter(_M_spec);
+         return __formatter.format(__id._M_thread, __fc);
        }
 
     private:
index 94a6ff0e2a1db1db6aa10ddbf9d0d1b43d537dfd..3d1dd38d998ff574de17b3b8ba07d1b5815bb786 100644 (file)
@@ -118,8 +118,38 @@ test02()
   VERIFY( ws1.length() == len );
 #endif
 
+  out.str("");
+  out << i;
+  s1 = out.str();
+  len = s1.size();
+  out.str("");
+
+  // with width
+  s2 = std::format("{0:{1}}", i, len + 2);
+  VERIFY( s2 == ("  " + s1) );
+  // with align + width
+  s2 = std::format("{0:>{1}}", i, len + 2);
+  VERIFY( s2 == ("  " + s1) );
+  s2 = std::format("{0:<{1}}", i, len + 2);
+  VERIFY( s2 == (s1 + "  ") );
+  // with fill-and-align + width
+  s2 = std::format("{0:x^{1}}", i, len + 5);
+  VERIFY( s2 == ("xx" + s1 + "xxx") );
+
+#ifdef _GLIBCXX_USE_WCHAR_T
+  static_assert( std::is_default_constructible_v<std::formatter<std::thread::id, wchar_t>> );
+  ws1 = std::format(L"{}", i);
+  VERIFY( ws1.length() == len );
+#endif
+
   t1.join();
   t2.join();
+
+  static_assert( std::formattable<std::thread::id, char> );
+  static_assert( std::formattable<std::thread::id, wchar_t> );
+  static_assert( !std::formattable<std::thread::id, char16_t> );
+  static_assert( !std::formattable<std::thread::id, int> );
 #elif __cplusplus >= 202302L
 # error "Feature-test macro for formatters has wrong value in <thread>"
 #endif