https://gcc.gnu.org/g:4896bb3199253dc350f8fb5ff63370310ca27ce2

commit r14-10232-g4896bb3199253dc350f8fb5ff63370310ca27ce2
Author: Jonathan Wakely <jwak...@redhat.com>
Date:   Fri May 17 10:55:32 2024 +0100

    libstdc++: Implement std::formatter<std::thread::id> without <sstream> 
[PR115099]
    
    The std::thread::id formatter uses std::basic_ostringstream without
    including <sstream>, which went unnoticed because the test for it uses
    a stringstream to check the output is correct.
    
    The fix implemented here is to stop using basic_ostringstream for
    formatting thread::id and just use std::format instead.
    
    As a drive-by fix, the formatter specialization is constrained to
    require that the thread::id::native_handle_type can be formatted, to
    avoid making the formatter ill-formed if the pthread_t type is not a
    pointer or integer. Since non-void pointers can't be formatted, ensure
    that we convert pointers to const void* for formatting. Make a similar
    change to the existing operator<< overload so that in the unlikely case
    that pthread_t is a typedef for char* we don't treat it as a
    null-terminated string when inserting into a stream.
    
    libstdc++-v3/ChangeLog:
    
            PR libstdc++/115099
            * include/bits/std_thread.h: Declare formatter as friend of
            thread::id.
            * include/std/thread (operator<<): Convert non-void pointers to
            void pointers for output.
            (formatter): Add constraint that thread::native_handle_type is a
            pointer or integer.
            (formatter::format): Reimplement without basic_ostringstream.
            * testsuite/30_threads/thread/id/output.cc: Check output
            compiles before <sstream> has been included.
    
    (cherry picked from commit 1a5e4dd83788ea4c049d354d83ad58a6a3d747e6)

Diff:
---
 libstdc++-v3/include/bits/std_thread.h             | 11 +++++-
 libstdc++-v3/include/std/thread                    | 43 +++++++++++++++++-----
 .../testsuite/30_threads/thread/id/output.cc       | 21 ++++++++++-
 3 files changed, 63 insertions(+), 12 deletions(-)

diff --git a/libstdc++-v3/include/bits/std_thread.h 
b/libstdc++-v3/include/bits/std_thread.h
index 2d7df12650d..5817bfb29dd 100644
--- a/libstdc++-v3/include/bits/std_thread.h
+++ b/libstdc++-v3/include/bits/std_thread.h
@@ -53,6 +53,10 @@ namespace std _GLIBCXX_VISIBILITY(default)
 {
 _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
+#if __glibcxx_formatters
+  template<typename, typename> class formatter;
+#endif
+
   /** @addtogroup threads
    *  @{
    */
@@ -117,13 +121,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<class _CharT, class _Traits>
        friend basic_ostream<_CharT, _Traits>&
        operator<<(basic_ostream<_CharT, _Traits>& __out, id __id);
+
+#if __glibcxx_formatters
+      friend formatter<id, char>;
+      friend formatter<id, wchar_t>;
+#endif
     };
 
   private:
     id                         _M_id;
 
     // _GLIBCXX_RESOLVE_LIB_DEFECTS
-    // 2097.  packaged_task constructors should be constrained
+    // 2097. packaged_task constructors should be constrained
     // 3039. Unnecessary decay in thread and packaged_task
     template<typename _Tp>
       using __not_same = __not_<is_same<__remove_cvref_t<_Tp>, thread>>;
diff --git a/libstdc++-v3/include/std/thread b/libstdc++-v3/include/std/thread
index 09ca3116e7f..e994d683bff 100644
--- a/libstdc++-v3/include/std/thread
+++ b/libstdc++-v3/include/std/thread
@@ -42,10 +42,6 @@
 # include <stop_token> // std::stop_source, std::stop_token, std::nostopstate
 #endif
 
-#if __cplusplus > 202002L
-# include <format>
-#endif
-
 #include <bits/std_thread.h> // std::thread, get_id, yield
 #include <bits/this_thread_sleep.h> // std::this_thread::sleep_for, sleep_until
 
@@ -53,6 +49,10 @@
 #define __glibcxx_want_formatters
 #include <bits/version.h>
 
+#if __cpp_lib_formatters
+# include <format>
+#endif
+
 namespace std _GLIBCXX_VISIBILITY(default)
 {
 _GLIBCXX_BEGIN_NAMESPACE_VERSION
@@ -104,10 +104,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     inline basic_ostream<_CharT, _Traits>&
     operator<<(basic_ostream<_CharT, _Traits>& __out, thread::id __id)
     {
+      // Convert non-void pointers to const void* for formatted output.
+      using __output_type
+       = __conditional_t<is_pointer<thread::native_handle_type>::value,
+                         const void*,
+                         thread::native_handle_type>;
+
       if (__id == thread::id())
        return __out << "thread::id of a non-executing thread";
       else
-       return __out << __id._M_thread;
+       return __out << static_cast<__output_type>(__id._M_thread);
     }
   /// @}
 
@@ -287,8 +293,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif // __cpp_lib_jthread
 
 #ifdef __cpp_lib_formatters // C++ >= 23
-
   template<typename _CharT>
+    requires is_pointer_v<thread::native_handle_type>
+      || is_integral_v<thread::native_handle_type>
     class formatter<thread::id, _CharT>
     {
     public:
@@ -331,10 +338,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        typename basic_format_context<_Out, _CharT>::iterator
        format(thread::id __id, basic_format_context<_Out, _CharT>& __fc) const
        {
-         std::basic_ostringstream<_CharT> __os;
-         __os << __id;
-         auto __str = __os.view();
-         return __format::__write_padded_as_spec(__str, __str.size(),
+         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);
+           }
+         return __format::__write_padded_as_spec(__sv, __sv.size(),
                                                  __fc, _M_spec,
                                                  __format::_Align_right);
        }
diff --git a/libstdc++-v3/testsuite/30_threads/thread/id/output.cc 
b/libstdc++-v3/testsuite/30_threads/thread/id/output.cc
index 3c167202b02..94a6ff0e2a1 100644
--- a/libstdc++-v3/testsuite/30_threads/thread/id/output.cc
+++ b/libstdc++-v3/testsuite/30_threads/thread/id/output.cc
@@ -3,8 +3,27 @@
 // { dg-require-gthreads "" }
 
 #include <thread>
-#include <sstream>
+
+#include <ostream>
 #include <format>
+
+void
+test_no_includes(std::ostream& out)
+{
+  std::thread::id i{};
+  // Check stream insertion works without including <sstream>:
+  out << i;
+#if __cpp_lib_formatters >= 202302
+  // PR libstdc++/115099 - compilation error: format thread::id
+  // Verify we can use std::thread::id with std::format without <sstream>:
+  (void) std::format("{}", i);
+#ifdef _GLIBCXX_USE_WCHAR_T
+  (void) std::format(L"{}", i);
+#endif
+#endif
+}
+
+#include <sstream>
 #include <testsuite_hooks.h>
 
 void

Reply via email to