https://gcc.gnu.org/g:74605294950e4a49627fb71fdedefea176f5ecf7

commit r16-137-g74605294950e4a49627fb71fdedefea176f5ecf7
Author: Tomasz Kamiński <tkami...@redhat.com>
Date:   Thu Apr 24 09:32:24 2025 +0200

    libstdc++: Constrain formatter for thread::id [PR119918]
    
    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 <jwak...@redhat.com>
    Signed-off-by: Tomasz Kamiński <tkami...@redhat.com>

Diff:
---
 libstdc++-v3/include/bits/formatfwd.h              |  21 +-
 libstdc++-v3/include/std/format                    | 257 +++++++++++----------
 libstdc++-v3/include/std/thread                    |  45 ++--
 .../testsuite/30_threads/thread/id/output.cc       |  30 +++
 4 files changed, 210 insertions(+), 143 deletions(-)

diff --git a/libstdc++-v3/include/bits/formatfwd.h 
b/libstdc++-v3/include/bits/formatfwd.h
index 9ba658b078a5..12ae2ad2ac08 100644
--- a/libstdc++-v3/include/bits/formatfwd.h
+++ b/libstdc++-v3/include/bits/formatfwd.h
@@ -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
diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
index 7d3067098bef..86c93f0e6ebd 100644
--- a/libstdc++-v3/include/std/format
+++ b/libstdc++-v3/include/std/format
@@ -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;
     };
   /// @}
 
diff --git a/libstdc++-v3/include/std/thread b/libstdc++-v3/include/std/thread
index d2f91ad89953..0de08c0bd3e3 100644
--- a/libstdc++-v3/include/std/thread
+++ b/libstdc++-v3/include/std/thread
@@ -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:
diff --git a/libstdc++-v3/testsuite/30_threads/thread/id/output.cc 
b/libstdc++-v3/testsuite/30_threads/thread/id/output.cc
index 94a6ff0e2a1d..3d1dd38d998f 100644
--- a/libstdc++-v3/testsuite/30_threads/thread/id/output.cc
+++ b/libstdc++-v3/testsuite/30_threads/thread/id/output.cc
@@ -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

Reply via email to