[libcxx-commits] [libcxx] [libc++] Consolidate ordering/hashing/formatting of std::thread::id. (PR #195763)

Alexey Samsonov via libcxx-commits libcxx-commits at lists.llvm.org
Mon May 4 23:53:47 PDT 2026


https://github.com/vonosmas updated https://github.com/llvm/llvm-project/pull/195763

>From 849aa7f9685bcda534cd26601fc0a8e43e18322b Mon Sep 17 00:00:00 2001
From: Alexey Samsonov <vonosmas at gmail.com>
Date: Tue, 5 May 2026 00:07:53 +0000
Subject: [PATCH 1/5] [libc++] Consolidate ordering/hashing/formatting of
 std::thread::id.

In practice, libc++ assumes that the internal type backing the
std::thread::id is either pointer, or an integral type. Also, despite
the implementation-specific definitions (for C11, pthreads, Windows),
this type may be left unspecified, as different POSIX systems define
pthread_t in a different way.

Consolidate all the type-dependent logic for computing std::hash,
specifying std::formatter behavior, and implementing operator<=> for
std::thread::id in the class itself, together with all the assertions
about the underlying types. This should hopefully make the code cleaner,
and clearly indicate which parts need to be extended if/when new types
of thread-id need to be supported.

An alternative to that would be sinking the conditional logic for
comparing/formatting/hashing these IDs into the __thread/support
headers, which was attempted initially. The downsides to these would
be both duplication of the boilerplate code *and* the conditional
code (as pointer/integer distinction and type-traits would need to be
copied both to pthreads, and to C11 implementation).
---
 libcxx/include/__thread/formatter.h       | 25 +------
 libcxx/include/__thread/id.h              | 87 +++++++++++++++++------
 libcxx/include/__thread/support.h         |  3 -
 libcxx/include/__thread/support/c11.h     | 10 ---
 libcxx/include/__thread/support/pthread.h | 10 ---
 libcxx/include/__thread/thread.h          |  2 +-
 6 files changed, 68 insertions(+), 69 deletions(-)

diff --git a/libcxx/include/__thread/formatter.h b/libcxx/include/__thread/formatter.h
index 826607d47b469..352b588c2f923 100644
--- a/libcxx/include/__thread/formatter.h
+++ b/libcxx/include/__thread/formatter.h
@@ -18,10 +18,6 @@
 #include <__format/formatter_integral.h>
 #include <__format/parser_std_format_spec.h>
 #include <__thread/id.h>
-#include <__type_traits/conditional.h>
-#include <__type_traits/is_pointer.h>
-#include <__type_traits/is_same.h>
-#include <cstdint>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -43,29 +39,12 @@ struct formatter<__thread_id, _CharT> {
 
   template <class _FormatContext>
   _LIBCPP_HIDE_FROM_ABI typename _FormatContext::iterator format(__thread_id __id, _FormatContext& __ctx) const {
-    // In __thread/support/pthread.h, __libcpp_thread_id is either a
-    // unsigned long long or a pthread_t.
-    //
-    // The type of pthread_t is left unspecified in POSIX so it can be any
-    // type. The most logical types are an integral or pointer.
-    // On Linux systems pthread_t is an unsigned long long.
-    // On Apple systems pthread_t is a pointer type.
-    //
-    // Note the output should match what the stream operator does. Since
-    // the ostream operator has been shipped years before this formatter
-    // was added to the Standard, this formatter does what the stream
-    // operator does. This may require platform specific changes.
-
-    using _Tp = decltype(__get_underlying_id(__id));
-    using _Cp = conditional_t<integral<_Tp>, _Tp, conditional_t<is_pointer_v<_Tp>, uintptr_t, void>>;
-    static_assert(!is_same_v<_Cp, void>, "unsupported thread::id type, please file a bug report");
-
     __format_spec::__parsed_specifications<_CharT> __specs = __parser_.__get_parsed_std_specifications(__ctx);
-    if constexpr (is_pointer_v<_Tp>) {
+    if constexpr (__thread_id::__PRINT_AS_HEX) {
       __specs.__std_.__alternate_form_ = true;
       __specs.__std_.__type_           = __format_spec::__type::__hexadecimal_lower_case;
     }
-    return __formatter::__format_integer(reinterpret_cast<_Cp>(__get_underlying_id(__id)), __ctx, __specs);
+    return __formatter::__format_integer(__id.__get_formatter_value(), __ctx, __specs);
   }
 
   __format_spec::__parser<_CharT> __parser_{.__alignment_ = __format_spec::__alignment::__right};
diff --git a/libcxx/include/__thread/id.h b/libcxx/include/__thread/id.h
index 14a51fc9ee880..f19aa4ad7c11f 100644
--- a/libcxx/include/__thread/id.h
+++ b/libcxx/include/__thread/id.h
@@ -15,6 +15,10 @@
 #include <__fwd/functional.h>
 #include <__fwd/ostream.h>
 #include <__thread/support.h>
+#include <__type_traits/conditional.h>
+#include <__type_traits/is_integral.h>
+#include <__type_traits/is_pointer.h>
+#include <cstdint>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -35,24 +39,67 @@ template <>
 struct hash<__thread_id>;
 
 class __thread_id {
-  // FIXME: pthread_t is a pointer on Darwin but a long on Linux.
-  // NULL is the no-thread value on Darwin.  Someone needs to check
-  // on other platforms.  We assume 0 works everywhere for now.
   __libcpp_thread_id __id_;
 
-  static _LIBCPP_HIDE_FROM_ABI bool
-  __lt_impl(__thread_id __x, __thread_id __y) _NOEXCEPT { // id==0 is always less than any other thread_id
-    if (__x.__id_ == 0)
-      return __y.__id_ != 0;
-    if (__y.__id_ == 0)
-      return false;
-    return __libcpp_thread_id_less(__x.__id_, __y.__id_);
+  // Even though __libcpp_thread_id is provided by underlying threading implementation
+  // (e.g. C11, pthreads, or Windows) its type may still be unspecified. E.g. for pthreads
+  // implementation __libcpp_thread_id is an alias for pthread_t, which is left unspecified
+  // in POSIX. Typically it's either an integral type (glibc) or a pointer (Apple systems),
+  // but it can also be an opaque type on some systems / libc implementations.
+  //
+  // Note that in order to satisfy standard requirements on std::thread::id, we need:
+  // * strong total order
+  // * formatter support
+  // * std::hash implementation
+  // Currently, we can implement all of the above only on pointer and integral types.
+  using _Tp = __libcpp_thread_id;
+  static_assert(is_pointer_v<_Tp> || is_integral_v<_Tp>, "unsupported thread::id type, please file a bug report");
+
+  // Strong total order implementation.
+  // Here we provide a best-effort implementation of strong total order, comparing
+  // integral types as-is and routing pointers through uintptr_t for a well-defined comparison.
+  static _LIBCPP_HIDE_FROM_ABI bool __eq_impl(__thread_id __x, __thread_id __y) _NOEXCEPT {
+    if constexpr (is_pointer_v<_Tp>) {
+      return reinterpret_cast<uintptr_t>(__x.__id_) == reinterpret_cast<uintptr_t>(__y.__id_);
+    } else {
+      return __x.__id_ == __y.__id_;
+    }
   }
 
+  static _LIBCPP_HIDE_FROM_ABI bool __lt_impl(__thread_id __x, __thread_id __y) _NOEXCEPT {
+    if constexpr (is_pointer_v<_Tp>) {
+      return reinterpret_cast<uintptr_t>(__x.__id_) < reinterpret_cast<uintptr_t>(__y.__id_);
+    } else {
+      // For integral thread IDs, assume 0 is always less than any other thread_id.
+      if (__x.__id_ == 0)
+        return __y.__id_ != 0;
+      if (__y.__id_ == 0)
+        return false;
+      return __x.__id_ < __y.__id_;
+    }
+  }
+
+  // Hashing implementation.
+  // Simply use the underlying pointer or integral types as-is.
+  using _HashTp = _Tp;
+  _LIBCPP_HIDE_FROM_ABI _HashTp __hash_value() const { return __id_; }
+
+  // Formatter implementation.
+  // Note the output should match what the stream operator does. Since
+  // the ostream operator has been shipped years before the formatter
+  // was added to the Standard, our logic mimics what the stream
+  // operator does (i.e. prints thread-id as integer, but uses a hexadecimal
+  // format if it's represented by a pointer)
+  using _FormatterTp = conditional_t<is_integral_v<_Tp>, _Tp, uintptr_t>;
+
+  static _LIBCPP_HIDE_FROM_ABI constexpr bool __PRINT_AS_HEX = is_pointer_v<_Tp>;
+
+  _LIBCPP_HIDE_FROM_ABI _FormatterTp __get_formatter_value() const { return reinterpret_cast<_FormatterTp>(__id_); }
+
 public:
-  _LIBCPP_HIDE_FROM_ABI __thread_id() _NOEXCEPT : __id_(0) {}
+  _LIBCPP_HIDE_FROM_ABI __thread_id() _NOEXCEPT : __id_{} {}
 
-  _LIBCPP_HIDE_FROM_ABI void __reset() { __id_ = 0; }
+  _LIBCPP_HIDE_FROM_ABI void __reset() { __id_ = __libcpp_thread_id{}; }
 
   friend _LIBCPP_HIDE_FROM_ABI bool operator==(__thread_id __x, __thread_id __y) _NOEXCEPT;
 #  if _LIBCPP_STD_VER <= 17
@@ -66,22 +113,18 @@ class __thread_id {
   operator<<(basic_ostream<_CharT, _Traits>& __os, __thread_id __id);
 
 private:
-  _LIBCPP_HIDE_FROM_ABI __thread_id(__libcpp_thread_id __id) : __id_(__id) {}
-
-  _LIBCPP_HIDE_FROM_ABI friend __libcpp_thread_id __get_underlying_id(const __thread_id __id) { return __id.__id_; }
+  _LIBCPP_HIDE_FROM_ABI __thread_id(__libcpp_thread_id __id) : __id_{__id} {}
 
   friend __thread_id this_thread::get_id() _NOEXCEPT;
   friend class _LIBCPP_EXPORTED_FROM_ABI thread;
   friend struct hash<__thread_id>;
+
+  template <class _Typename, class _CharT>
+  friend struct formatter;
 };
 
 inline _LIBCPP_HIDE_FROM_ABI bool operator==(__thread_id __x, __thread_id __y) _NOEXCEPT {
-  // Don't pass id==0 to underlying routines
-  if (__x.__id_ == 0)
-    return __y.__id_ == 0;
-  if (__y.__id_ == 0)
-    return false;
-  return __libcpp_thread_id_equal(__x.__id_, __y.__id_);
+  return __thread_id::__eq_impl(__x, __y);
 }
 
 #  if _LIBCPP_STD_VER <= 17
@@ -89,7 +132,7 @@ inline _LIBCPP_HIDE_FROM_ABI bool operator==(__thread_id __x, __thread_id __y) _
 inline _LIBCPP_HIDE_FROM_ABI bool operator!=(__thread_id __x, __thread_id __y) _NOEXCEPT { return !(__x == __y); }
 
 inline _LIBCPP_HIDE_FROM_ABI bool operator<(__thread_id __x, __thread_id __y) _NOEXCEPT {
-  return __thread_id::__lt_impl(__x.__id_, __y.__id_);
+  return __thread_id::__lt_impl(__x, __y);
 }
 
 inline _LIBCPP_HIDE_FROM_ABI bool operator<=(__thread_id __x, __thread_id __y) _NOEXCEPT { return !(__y < __x); }
diff --git a/libcxx/include/__thread/support.h b/libcxx/include/__thread/support.h
index 50a18daf2b685..62b336ea5f429 100644
--- a/libcxx/include/__thread/support.h
+++ b/libcxx/include/__thread/support.h
@@ -72,9 +72,6 @@ int __libcpp_execute_once(__libcpp_exec_once_flag*, void (*__init_routine)());
 //
 using __libcpp_thread_id = ...;
 
-bool __libcpp_thread_id_equal(__libcpp_thread_id, __libcpp_thread_id);
-bool __libcpp_thread_id_less(__libcpp_thread_id, __libcpp_thread_id);
-
 //
 // Thread
 //
diff --git a/libcxx/include/__thread/support/c11.h b/libcxx/include/__thread/support/c11.h
index 463c8496ba6f4..5118467880a96 100644
--- a/libcxx/include/__thread/support/c11.h
+++ b/libcxx/include/__thread/support/c11.h
@@ -124,16 +124,6 @@ inline _LIBCPP_HIDE_FROM_ABI int __libcpp_execute_once(__libcpp_exec_once_flag*
 //
 typedef thrd_t __libcpp_thread_id;
 
-// Returns non-zero if the thread ids are equal, otherwise 0
-inline _LIBCPP_HIDE_FROM_ABI bool __libcpp_thread_id_equal(__libcpp_thread_id t1, __libcpp_thread_id t2) {
-  return thrd_equal(t1, t2) != 0;
-}
-
-// Returns non-zero if t1 < t2, otherwise 0
-inline _LIBCPP_HIDE_FROM_ABI bool __libcpp_thread_id_less(__libcpp_thread_id t1, __libcpp_thread_id t2) {
-  return t1 < t2;
-}
-
 //
 // Thread
 //
diff --git a/libcxx/include/__thread/support/pthread.h b/libcxx/include/__thread/support/pthread.h
index 4cf5c0342467b..faa5941b9c840 100644
--- a/libcxx/include/__thread/support/pthread.h
+++ b/libcxx/include/__thread/support/pthread.h
@@ -150,16 +150,6 @@ typedef unsigned long long __libcpp_thread_id;
 typedef pthread_t __libcpp_thread_id;
 #endif
 
-// Returns non-zero if the thread ids are equal, otherwise 0
-inline _LIBCPP_HIDE_FROM_ABI bool __libcpp_thread_id_equal(__libcpp_thread_id __t1, __libcpp_thread_id __t2) {
-  return __t1 == __t2;
-}
-
-// Returns non-zero if t1 < t2, otherwise 0
-inline _LIBCPP_HIDE_FROM_ABI bool __libcpp_thread_id_less(__libcpp_thread_id __t1, __libcpp_thread_id __t2) {
-  return __t1 < __t2;
-}
-
 //
 // Thread
 //
diff --git a/libcxx/include/__thread/thread.h b/libcxx/include/__thread/thread.h
index 7342cbb5e37f6..74017867b03df 100644
--- a/libcxx/include/__thread/thread.h
+++ b/libcxx/include/__thread/thread.h
@@ -123,7 +123,7 @@ void __thread_specific_ptr<_Tp>::set_pointer(pointer __p) {
 template <>
 struct hash<__thread_id> : public __unary_function<__thread_id, size_t> {
   _LIBCPP_HIDE_FROM_ABI size_t operator()(__thread_id __v) const _NOEXCEPT {
-    return hash<__libcpp_thread_id>()(__v.__id_);
+    return hash<__thread_id::_HashTp>()(__v.__hash_value());
   }
 };
 

>From 9d79d1d1b65a7ff0beb2f5a4eb86182640f4fd71 Mon Sep 17 00:00:00 2001
From: Alexey Samsonov <vonosmas at gmail.com>
Date: Tue, 5 May 2026 04:43:53 +0000
Subject: [PATCH 2/5] Support pre-C++17 mode, and remove unnecessary functions
 from Windows header as well.

---
 libcxx/include/__thread/id.h              | 11 ++++++-----
 libcxx/include/__thread/support/windows.h |  4 ----
 libcxx/src/support/win32/thread_win32.cpp |  1 +
 3 files changed, 7 insertions(+), 9 deletions(-)

diff --git a/libcxx/include/__thread/id.h b/libcxx/include/__thread/id.h
index f19aa4ad7c11f..a52a525da839c 100644
--- a/libcxx/include/__thread/id.h
+++ b/libcxx/include/__thread/id.h
@@ -53,13 +53,14 @@ class __thread_id {
   // * std::hash implementation
   // Currently, we can implement all of the above only on pointer and integral types.
   using _Tp = __libcpp_thread_id;
-  static_assert(is_pointer_v<_Tp> || is_integral_v<_Tp>, "unsupported thread::id type, please file a bug report");
+  static_assert(is_pointer<_Tp>::value || is_integral<_Tp>::value,
+                "unsupported thread::id type, please file a bug report");
 
   // Strong total order implementation.
   // Here we provide a best-effort implementation of strong total order, comparing
   // integral types as-is and routing pointers through uintptr_t for a well-defined comparison.
   static _LIBCPP_HIDE_FROM_ABI bool __eq_impl(__thread_id __x, __thread_id __y) _NOEXCEPT {
-    if constexpr (is_pointer_v<_Tp>) {
+    if _LIBCPP_CONSTEXPR (is_pointer<_Tp>::value) {
       return reinterpret_cast<uintptr_t>(__x.__id_) == reinterpret_cast<uintptr_t>(__y.__id_);
     } else {
       return __x.__id_ == __y.__id_;
@@ -67,7 +68,7 @@ class __thread_id {
   }
 
   static _LIBCPP_HIDE_FROM_ABI bool __lt_impl(__thread_id __x, __thread_id __y) _NOEXCEPT {
-    if constexpr (is_pointer_v<_Tp>) {
+    if _LIBCPP_CONSTEXPR (is_pointer<_Tp>::value) {
       return reinterpret_cast<uintptr_t>(__x.__id_) < reinterpret_cast<uintptr_t>(__y.__id_);
     } else {
       // For integral thread IDs, assume 0 is always less than any other thread_id.
@@ -90,9 +91,9 @@ class __thread_id {
   // was added to the Standard, our logic mimics what the stream
   // operator does (i.e. prints thread-id as integer, but uses a hexadecimal
   // format if it's represented by a pointer)
-  using _FormatterTp = conditional_t<is_integral_v<_Tp>, _Tp, uintptr_t>;
+  using _FormatterTp = conditional<is_integral<_Tp>::value, _Tp, uintptr_t>::type;
 
-  static _LIBCPP_HIDE_FROM_ABI constexpr bool __PRINT_AS_HEX = is_pointer_v<_Tp>;
+  static _LIBCPP_CONSTEXPR const bool __PRINT_AS_HEX = is_pointer<_Tp>::value;
 
   _LIBCPP_HIDE_FROM_ABI _FormatterTp __get_formatter_value() const { return reinterpret_cast<_FormatterTp>(__id_); }
 
diff --git a/libcxx/include/__thread/support/windows.h b/libcxx/include/__thread/support/windows.h
index 558b5c81dc191..e9f72ed9a9a7d 100644
--- a/libcxx/include/__thread/support/windows.h
+++ b/libcxx/include/__thread/support/windows.h
@@ -86,10 +86,6 @@ _LIBCPP_EXPORTED_FROM_ABI int __libcpp_execute_once(__libcpp_exec_once_flag* __f
 //
 typedef long __libcpp_thread_id;
 
-_LIBCPP_EXPORTED_FROM_ABI bool __libcpp_thread_id_equal(__libcpp_thread_id __t1, __libcpp_thread_id __t2);
-
-_LIBCPP_EXPORTED_FROM_ABI bool __libcpp_thread_id_less(__libcpp_thread_id __t1, __libcpp_thread_id __t2);
-
 //
 // Thread
 //
diff --git a/libcxx/src/support/win32/thread_win32.cpp b/libcxx/src/support/win32/thread_win32.cpp
index 3a67d759f0f5e..734e173a40f40 100644
--- a/libcxx/src/support/win32/thread_win32.cpp
+++ b/libcxx/src/support/win32/thread_win32.cpp
@@ -135,6 +135,7 @@ int __libcpp_execute_once(__libcpp_exec_once_flag* __flag, void (*__init_routine
 }
 
 // Thread ID
+// TODO: Clarify whether to keep these functions since they were _LIBCPP_EXPORTED_FROM_ABI.
 bool __libcpp_thread_id_equal(__libcpp_thread_id __lhs, __libcpp_thread_id __rhs) { return __lhs == __rhs; }
 
 bool __libcpp_thread_id_less(__libcpp_thread_id __lhs, __libcpp_thread_id __rhs) { return __lhs < __rhs; }

>From 8f2b26c26e38c9164a0aa02075aa40bdda65d003 Mon Sep 17 00:00:00 2001
From: Alexey Samsonov <vonosmas at gmail.com>
Date: Tue, 5 May 2026 05:13:58 +0000
Subject: [PATCH 3/5] Remove if-constexpr to support c++11

---
 libcxx/include/__thread/id.h | 36 +++++++++++++++++++-----------------
 1 file changed, 19 insertions(+), 17 deletions(-)

diff --git a/libcxx/include/__thread/id.h b/libcxx/include/__thread/id.h
index a52a525da839c..1a990c796f3bc 100644
--- a/libcxx/include/__thread/id.h
+++ b/libcxx/include/__thread/id.h
@@ -59,25 +59,27 @@ class __thread_id {
   // Strong total order implementation.
   // Here we provide a best-effort implementation of strong total order, comparing
   // integral types as-is and routing pointers through uintptr_t for a well-defined comparison.
-  static _LIBCPP_HIDE_FROM_ABI bool __eq_impl(__thread_id __x, __thread_id __y) _NOEXCEPT {
-    if _LIBCPP_CONSTEXPR (is_pointer<_Tp>::value) {
-      return reinterpret_cast<uintptr_t>(__x.__id_) == reinterpret_cast<uintptr_t>(__y.__id_);
-    } else {
-      return __x.__id_ == __y.__id_;
-    }
+  template <typename Thr, enable_if<is_pointer<typename Thr::_Tp>::value, int>::type = 0>
+  static _LIBCPP_HIDE_FROM_ABI bool __eq_impl(Thr __x, Thr __y) _NOEXCEPT {
+    return reinterpret_cast<uintptr_t>(__x.__id_) == reinterpret_cast<uintptr_t>(__y.__id_);
+  }
+  template <typename Thr, enable_if<is_integral<typename Thr::_Tp>::value, int>::type = 0>
+  static _LIBCPP_HIDE_FROM_ABI bool __eq_impl(Thr __x, Thr __y) _NOEXCEPT {
+    return __x.__id_ == __y.__id_;
   }
 
-  static _LIBCPP_HIDE_FROM_ABI bool __lt_impl(__thread_id __x, __thread_id __y) _NOEXCEPT {
-    if _LIBCPP_CONSTEXPR (is_pointer<_Tp>::value) {
-      return reinterpret_cast<uintptr_t>(__x.__id_) < reinterpret_cast<uintptr_t>(__y.__id_);
-    } else {
-      // For integral thread IDs, assume 0 is always less than any other thread_id.
-      if (__x.__id_ == 0)
-        return __y.__id_ != 0;
-      if (__y.__id_ == 0)
-        return false;
-      return __x.__id_ < __y.__id_;
-    }
+  template <typename Thr, enable_if<is_pointer<typename Thr::_Tp>::value, int>::type = 0>
+  static _LIBCPP_HIDE_FROM_ABI bool __lt_impl(Thr __x, Thr __y) _NOEXCEPT {
+    return reinterpret_cast<uintptr_t>(__x.__id_) < reinterpret_cast<uintptr_t>(__y.__id_);
+  }
+  template <typename Thr, enable_if<is_integral<typename Thr::_Tp>::value, int>::type = 0>
+  static _LIBCPP_HIDE_FROM_ABI bool __lt_impl(Thr __x, Thr __y) _NOEXCEPT {
+    // For integral thread IDs, assume 0 is always less than any other thread_id.
+    if (__x.__id_ == 0)
+      return __y.__id_ != 0;
+    if (__y.__id_ == 0)
+      return false;
+    return __x.__id_ < __y.__id_;
   }
 
   // Hashing implementation.

>From 3e389d4d0daac123337fc1814c18725d12645440 Mon Sep 17 00:00:00 2001
From: Alexey Samsonov <vonosmas at gmail.com>
Date: Tue, 5 May 2026 05:53:48 +0000
Subject: [PATCH 4/5] More fixes for gcc and older c++ standards.

---
 libcxx/include/__thread/id.h | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/libcxx/include/__thread/id.h b/libcxx/include/__thread/id.h
index 1a990c796f3bc..e925d2660005b 100644
--- a/libcxx/include/__thread/id.h
+++ b/libcxx/include/__thread/id.h
@@ -59,20 +59,20 @@ class __thread_id {
   // Strong total order implementation.
   // Here we provide a best-effort implementation of strong total order, comparing
   // integral types as-is and routing pointers through uintptr_t for a well-defined comparison.
-  template <typename Thr, enable_if<is_pointer<typename Thr::_Tp>::value, int>::type = 0>
+  template <typename Thr, typename enable_if<is_pointer<typename Thr::_Tp>::value, int>::type = 0>
   static _LIBCPP_HIDE_FROM_ABI bool __eq_impl(Thr __x, Thr __y) _NOEXCEPT {
     return reinterpret_cast<uintptr_t>(__x.__id_) == reinterpret_cast<uintptr_t>(__y.__id_);
   }
-  template <typename Thr, enable_if<is_integral<typename Thr::_Tp>::value, int>::type = 0>
+  template <typename Thr, typename enable_if<is_integral<typename Thr::_Tp>::value, int>::type = 0>
   static _LIBCPP_HIDE_FROM_ABI bool __eq_impl(Thr __x, Thr __y) _NOEXCEPT {
     return __x.__id_ == __y.__id_;
   }
 
-  template <typename Thr, enable_if<is_pointer<typename Thr::_Tp>::value, int>::type = 0>
+  template <typename Thr, typename enable_if<is_pointer<typename Thr::_Tp>::value, int>::type = 0>
   static _LIBCPP_HIDE_FROM_ABI bool __lt_impl(Thr __x, Thr __y) _NOEXCEPT {
     return reinterpret_cast<uintptr_t>(__x.__id_) < reinterpret_cast<uintptr_t>(__y.__id_);
   }
-  template <typename Thr, enable_if<is_integral<typename Thr::_Tp>::value, int>::type = 0>
+  template <typename Thr, typename enable_if<is_integral<typename Thr::_Tp>::value, int>::type = 0>
   static _LIBCPP_HIDE_FROM_ABI bool __lt_impl(Thr __x, Thr __y) _NOEXCEPT {
     // For integral thread IDs, assume 0 is always less than any other thread_id.
     if (__x.__id_ == 0)
@@ -100,9 +100,9 @@ class __thread_id {
   _LIBCPP_HIDE_FROM_ABI _FormatterTp __get_formatter_value() const { return reinterpret_cast<_FormatterTp>(__id_); }
 
 public:
-  _LIBCPP_HIDE_FROM_ABI __thread_id() _NOEXCEPT : __id_{} {}
+  _LIBCPP_HIDE_FROM_ABI __thread_id() _NOEXCEPT : __id_(__libcpp_thread_id()) {}
 
-  _LIBCPP_HIDE_FROM_ABI void __reset() { __id_ = __libcpp_thread_id{}; }
+  _LIBCPP_HIDE_FROM_ABI void __reset() { __id_ = __libcpp_thread_id(); }
 
   friend _LIBCPP_HIDE_FROM_ABI bool operator==(__thread_id __x, __thread_id __y) _NOEXCEPT;
 #  if _LIBCPP_STD_VER <= 17
@@ -116,7 +116,7 @@ class __thread_id {
   operator<<(basic_ostream<_CharT, _Traits>& __os, __thread_id __id);
 
 private:
-  _LIBCPP_HIDE_FROM_ABI __thread_id(__libcpp_thread_id __id) : __id_{__id} {}
+  _LIBCPP_HIDE_FROM_ABI __thread_id(__libcpp_thread_id __id) : __id_(__id) {}
 
   friend __thread_id this_thread::get_id() _NOEXCEPT;
   friend class _LIBCPP_EXPORTED_FROM_ABI thread;

>From 3e10839a6bb63e1539b1a5632f7b5675a44121e7 Mon Sep 17 00:00:00 2001
From: Alexey Samsonov <vonosmas at gmail.com>
Date: Tue, 5 May 2026 06:53:33 +0000
Subject: [PATCH 5/5] Restore function decls on Windows to fix build.

---
 libcxx/include/__thread/support/windows.h | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/libcxx/include/__thread/support/windows.h b/libcxx/include/__thread/support/windows.h
index e9f72ed9a9a7d..109ee3b989a43 100644
--- a/libcxx/include/__thread/support/windows.h
+++ b/libcxx/include/__thread/support/windows.h
@@ -86,6 +86,11 @@ _LIBCPP_EXPORTED_FROM_ABI int __libcpp_execute_once(__libcpp_exec_once_flag* __f
 //
 typedef long __libcpp_thread_id;
 
+// TODO: Clarify whether to keep these functions since they were _LIBCPP_EXPORTED_FROM_ABI.
+_LIBCPP_EXPORTED_FROM_ABI bool __libcpp_thread_id_equal(__libcpp_thread_id __t1, __libcpp_thread_id __t2);
+
+_LIBCPP_EXPORTED_FROM_ABI bool __libcpp_thread_id_less(__libcpp_thread_id __t1, __libcpp_thread_id __t2);
+
 //
 // Thread
 //



More information about the libcxx-commits mailing list