[libcxx-commits] [libcxx] [libc++] Create a `constexpr` hash to use during constant evaluation (PR #214946)

Vinay Deshmukh via libcxx-commits libcxx-commits at lists.llvm.org
Fri Aug 28 01:09:10 PDT 2026


https://github.com/vinay-deshmukh updated https://github.com/llvm/llvm-project/pull/214946

>From 817a1a5c598f182cee778de8822cad23a6264568 Mon Sep 17 00:00:00 2001
From: Vinay Deshmukh <vinay_deshmukh at outlook.com>
Date: Sat, 8 Aug 2026 10:33:03 +0100
Subject: [PATCH 01/10] Parameterize first hash test for different hash
 function

---
 .../unord.hash/integral.pass.cpp              | 138 +++++++++---------
 1 file changed, 71 insertions(+), 67 deletions(-)

diff --git a/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp b/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp
index c798b3aa77ea5..0c09ec5fc9f0e 100644
--- a/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp
+++ b/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp
@@ -30,17 +30,16 @@
 #  include "poisoned_hash_helper.h"
 #endif
 
-template <class T>
-void
-test()
-{
+template <class T, template <typename> typename THash = std::hash >
+void test() {
 #if TEST_STD_VER >= 11
-    test_hash_disabled<const T>();
-    test_hash_disabled<volatile T>();
-    test_hash_disabled<const volatile T>();
+  test_hash_disabled<const T, THash<const T>>();
+  test_hash_disabled<volatile T, THash<volatile T>>();
+  test_hash_disabled<const volatile T, THash<const volatile T>>();
 #endif
 
-    typedef std::hash<T> H;
+  // typedef std::hash<T> H;
+  typedef THash<T> H;
 #if TEST_STD_VER <= 17
     static_assert((std::is_same<typename H::argument_type, T>::value), "");
     static_assert((std::is_same<typename H::result_type, std::size_t>::value), "");
@@ -61,70 +60,75 @@ test()
     }
 }
 
-int main(int, char**)
-{
-    test<bool>();
-    test<char>();
-    test<signed char>();
-    test<unsigned char>();
-    test<char16_t>();
-    test<char32_t>();
+template <template <typename> typename THash >
+bool test_with_hash() {
+  test<bool, THash>();
+  test<char, THash>();
+  test<signed char, THash>();
+  test<unsigned char, THash>();
+  test<char16_t, THash>();
+  test<char32_t, THash>();
 #ifndef TEST_HAS_NO_WIDE_CHARACTERS
-    test<wchar_t>();
+  test<wchar_t, THash>();
 #endif
-    test<short>();
-    test<unsigned short>();
-    test<int>();
-    test<unsigned int>();
-    test<long>();
-    test<unsigned long>();
-    test<long long>();
-    test<unsigned long long>();
-
-//  LWG #2119
-    test<std::ptrdiff_t>();
-    test<std::size_t>();
-
-    test<std::int8_t>();
-    test<std::int16_t>();
-    test<std::int32_t>();
-    test<std::int64_t>();
-
-    test<std::int_fast8_t>();
-    test<std::int_fast16_t>();
-    test<std::int_fast32_t>();
-    test<std::int_fast64_t>();
-
-    test<std::int_least8_t>();
-    test<std::int_least16_t>();
-    test<std::int_least32_t>();
-    test<std::int_least64_t>();
-
-    test<std::intmax_t>();
-    test<std::intptr_t>();
-
-    test<std::uint8_t>();
-    test<std::uint16_t>();
-    test<std::uint32_t>();
-    test<std::uint64_t>();
-
-    test<std::uint_fast8_t>();
-    test<std::uint_fast16_t>();
-    test<std::uint_fast32_t>();
-    test<std::uint_fast64_t>();
-
-    test<std::uint_least8_t>();
-    test<std::uint_least16_t>();
-    test<std::uint_least32_t>();
-    test<std::uint_least64_t>();
-
-    test<std::uintmax_t>();
-    test<std::uintptr_t>();
+  test<short, THash>();
+  test<unsigned short, THash>();
+  test<int, THash>();
+  test<unsigned int, THash>();
+  test<long, THash>();
+  test<unsigned long, THash>();
+  test<long long, THash>();
+  test<unsigned long long, THash>();
+
+  //  LWG #2119
+  test<std::ptrdiff_t, THash>();
+  test<std::size_t, THash>();
+
+  test<std::int8_t, THash>();
+  test<std::int16_t, THash>();
+  test<std::int32_t, THash>();
+  test<std::int64_t, THash>();
+
+  test<std::int_fast8_t, THash>();
+  test<std::int_fast16_t, THash>();
+  test<std::int_fast32_t, THash>();
+  test<std::int_fast64_t, THash>();
+
+  test<std::int_least8_t, THash>();
+  test<std::int_least16_t, THash>();
+  test<std::int_least32_t, THash>();
+  test<std::int_least64_t, THash>();
+
+  test<std::intmax_t, THash>();
+  test<std::intptr_t, THash>();
+
+  test<std::uint8_t, THash>();
+  test<std::uint16_t, THash>();
+  test<std::uint32_t, THash>();
+  test<std::uint64_t, THash>();
+
+  test<std::uint_fast8_t, THash>();
+  test<std::uint_fast16_t, THash>();
+  test<std::uint_fast32_t, THash>();
+  test<std::uint_fast64_t, THash>();
+
+  test<std::uint_least8_t, THash>();
+  test<std::uint_least16_t, THash>();
+  test<std::uint_least32_t, THash>();
+  test<std::uint_least64_t, THash>();
+
+  test<std::uintmax_t, THash>();
+  test<std::uintptr_t, THash>();
 
 #ifndef TEST_HAS_NO_INT128
-    test<__int128_t>();
-    test<__uint128_t>();
+  test<__int128_t, THash>();
+  test<__uint128_t, THash>();
 #endif
 
+  return true;
+}
+
+int main(int, char**) {
+  assert(test_with_hash<std::hash>());
   return 0;
 }

>From 1b6383a715716d0ba9c787097311e24640433867 Mon Sep 17 00:00:00 2001
From: Vinay Deshmukh <vinay_deshmukh at outlook.com>
Date: Sat, 8 Aug 2026 10:43:07 +0100
Subject: [PATCH 02/10] Setup a failing test for std::__constexpr_hash

---
 .../function.objects/unord.hash/integral.pass.cpp   | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp b/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp
index 0c09ec5fc9f0e..d4d9bb4fb5041 100644
--- a/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp
+++ b/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp
@@ -128,7 +128,20 @@ bool test_with_hash() {
   return true;
 }
 
+// TODO: move to libcxx/include/__functional/hash.h
+namespace std {
+
+template <class _Key>
+struct __constexpr_hash {};
+
+} // namespace std
+
 int main(int, char**) {
   assert(test_with_hash<std::hash>());
+
+#if TEST_STD_VER >= 26
+  static_assert(test_with_hash<std::__constexpr_hash>());
+#endif
+
   return 0;
 }

>From ce52820da7d6dfe85ff92cfad1b69bcf07fcb33d Mon Sep 17 00:00:00 2001
From: Vinay Deshmukh <vinay_deshmukh at outlook.com>
Date: Sat, 8 Aug 2026 14:15:15 +0100
Subject: [PATCH 03/10] Pass integer.pass.cpp

---
 .../unord.hash/integral.pass.cpp              | 50 +++++++++++++++++--
 libcxx/test/support/poisoned_hash_helper.h    |  2 +-
 2 files changed, 47 insertions(+), 5 deletions(-)

diff --git a/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp b/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp
index d4d9bb4fb5041..7bc1425cb291f 100644
--- a/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp
+++ b/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp
@@ -31,7 +31,7 @@
 #endif
 
 template <class T, template <typename> typename THash = std::hash >
-void test() {
+TEST_CONSTEXPR_CXX26 void test() {
 #if TEST_STD_VER >= 11
   test_hash_disabled<const T, THash<const T>>();
   test_hash_disabled<volatile T, THash<volatile T>>();
@@ -61,7 +61,7 @@ void test() {
 }
 
 template <template <typename> typename THash >
-bool test_with_hash() {
+TEST_CONSTEXPR_CXX26 bool test_with_hash() {
   test<bool, THash>();
   test<char, THash>();
   test<signed char, THash>();
@@ -128,13 +128,55 @@ bool test_with_hash() {
   return true;
 }
 
+#if TEST_STD_VER >= 26
 // TODO: move to libcxx/include/__functional/hash.h
 namespace std {
 
-template <class _Key>
-struct __constexpr_hash {};
+// TODO: use _ prefix
+template <typename _Tp>
+concept EnabledForHash = requires(_Tp t) {
+  { std::bool_constant<__is_unqualified_v<_Tp>>() } -> std::same_as<std::true_type>;
+};
+
+template <typename _Tp>
+concept DisabledForHash = not EnabledForHash<_Tp>;
+
+// TODO: document the constraints of using this at runtime OR make it consteval only
+template <typename _Tp>
+struct __constexpr_hash;
+
+template <DisabledForHash _Tp>
+struct __constexpr_hash<_Tp> {
+  __constexpr_hash()                                   = delete;
+  __constexpr_hash(const __constexpr_hash&)            = delete;
+  __constexpr_hash& operator=(const __constexpr_hash&) = delete;
+};
+
+template <EnabledForHash _Tp>
+struct __constexpr_hash<_Tp> {
+  [[__nodiscard__]] constexpr _LIBCPP_HIDE_FROM_ABI size_t operator()(const _Tp& __v) const noexcept {
+    if constexpr (std::is_same_v<_Tp, nullptr_t>) {
+      return 662607004ull;
+    } else if constexpr (std::is_integral_v<_Tp>) {
+      if constexpr (sizeof(_Tp) <= sizeof(size_t)) {
+        return static_cast<size_t>(__v);
+      } else {
+        constexpr size_t multiple = sizeof(_Tp) / sizeof(size_t);
+        char region[multiple];
+
+        // TODO: 0, 1, 2, 3, 4
+        return region[multiple - 1]; // TODO: hash-ing
+      }
+    }
+    __builtin_unreachable(); // todo: revisit
+  }
+
+  __constexpr_hash() noexcept                          = default;
+  __constexpr_hash& operator=(const __constexpr_hash&) = default;
+};
 
 } // namespace std
+#endif
 
 int main(int, char**) {
   assert(test_with_hash<std::hash>());
diff --git a/libcxx/test/support/poisoned_hash_helper.h b/libcxx/test/support/poisoned_hash_helper.h
index cd71cd70d6a84..ec3f6e9af813f 100644
--- a/libcxx/test/support/poisoned_hash_helper.h
+++ b/libcxx/test/support/poisoned_hash_helper.h
@@ -87,7 +87,7 @@ TEST_CONSTEXPR_CXX20 void test_hash_enabled(Key const& key = Key{}) {
 
 // Test that the specified Hash meets the requirements of a disabled hash.
 template <class Key, class Hash = std::hash<Key>>
-void test_hash_disabled() {
+TEST_CONSTEXPR_CXX26 void test_hash_disabled() {
   // Disabled hash requirements
   static_assert(!std::is_default_constructible<Hash>::value, "");
   static_assert(!std::is_copy_constructible<Hash>::value, "");

>From ae2da4414f761421c0eee17cd97cbeae03ee6c2b Mon Sep 17 00:00:00 2001
From: Vinay Deshmukh <vinay_deshmukh at outlook.com>
Date: Mon, 10 Aug 2026 08:43:19 +0100
Subject: [PATCH 04/10] -Wc++17 extensions

---
 .../std/utilities/function.objects/unord.hash/integral.pass.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp b/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp
index 7bc1425cb291f..4974916ed4c80 100644
--- a/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp
+++ b/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp
@@ -30,7 +30,7 @@
 #  include "poisoned_hash_helper.h"
 #endif
 
-template <class T, template <typename> typename THash = std::hash >
+template <class T, template <class> class THash = std::hash >
 TEST_CONSTEXPR_CXX26 void test() {
 #if TEST_STD_VER >= 11
   test_hash_disabled<const T, THash<const T>>();

>From 190e96559aecedd698d270d0778508bbc0e83323 Mon Sep 17 00:00:00 2001
From: Vinay Deshmukh <vinay_deshmukh at outlook.com>
Date: Sun, 23 Aug 2026 18:03:46 +0100
Subject: [PATCH 05/10] Move to test header

---
 .../unord.hash/integral.pass.cpp              | 53 +----------------
 libcxx/test/support/constexpr_hash.h          | 57 +++++++++++++++++++
 2 files changed, 59 insertions(+), 51 deletions(-)
 create mode 100644 libcxx/test/support/constexpr_hash.h

diff --git a/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp b/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp
index 4974916ed4c80..f2c217f10db96 100644
--- a/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp
+++ b/libcxx/test/std/utilities/function.objects/unord.hash/integral.pass.cpp
@@ -24,6 +24,7 @@
 #include <limits>
 #include <type_traits>
 
+#include "constexpr_hash.h"
 #include "test_macros.h"
 
 #if TEST_STD_VER >= 11
@@ -128,61 +129,11 @@ TEST_CONSTEXPR_CXX26 bool test_with_hash() {
   return true;
 }
 
-#if TEST_STD_VER >= 26
-// TODO: move to libcxx/include/__functional/hash.h
-namespace std {
-
-// TODO: use _ prefix
-template <typename _Tp>
-concept EnabledForHash = requires(_Tp t) {
-  { std::bool_constant<__is_unqualified_v<_Tp>>() } -> std::same_as<std::true_type>;
-};
-
-template <typename _Tp>
-concept DisabledForHash = not EnabledForHash<_Tp>;
-
-// TODO: document the constraints of using this at runtime OR make it consteval only
-template <typename _Tp>
-struct __constexpr_hash;
-
-template <DisabledForHash _Tp>
-struct __constexpr_hash<_Tp> {
-  __constexpr_hash()                                   = delete;
-  __constexpr_hash(const __constexpr_hash&)            = delete;
-  __constexpr_hash& operator=(const __constexpr_hash&) = delete;
-};
-
-template <EnabledForHash _Tp>
-struct __constexpr_hash<_Tp> {
-  [[__nodiscard__]] constexpr _LIBCPP_HIDE_FROM_ABI size_t operator()(const _Tp& __v) const noexcept {
-    if constexpr (std::is_same_v<_Tp, nullptr_t>) {
-      return 662607004ull;
-    } else if constexpr (std::is_integral_v<_Tp>) {
-      if constexpr (sizeof(_Tp) <= sizeof(size_t)) {
-        return static_cast<size_t>(__v);
-      } else {
-        constexpr size_t multiple = sizeof(_Tp) / sizeof(size_t);
-        char region[multiple];
-
-        // TODO: 0, 1, 2, 3, 4
-        return region[multiple - 1]; // TODO: hash-ing
-      }
-    }
-    __builtin_unreachable(); // todo: revisit
-  }
-
-  __constexpr_hash() noexcept                          = default;
-  __constexpr_hash& operator=(const __constexpr_hash&) = default;
-};
-
-} // namespace std
-#endif
-
 int main(int, char**) {
   assert(test_with_hash<std::hash>());
 
 #if TEST_STD_VER >= 26
-  static_assert(test_with_hash<std::__constexpr_hash>());
+  static_assert(test_with_hash<support::constexpr_hash>());
 #endif
 
   return 0;
diff --git a/libcxx/test/support/constexpr_hash.h b/libcxx/test/support/constexpr_hash.h
new file mode 100644
index 0000000000000..b7881f0c73df9
--- /dev/null
+++ b/libcxx/test/support/constexpr_hash.h
@@ -0,0 +1,57 @@
+
+
+#include <__type_traits/is_unqualified.h>
+
+#include "test_macros.h"
+#include <type_traits>
+
+namespace support {
+
+#if TEST_STD_VER >= 26
+
+// TODO: use _ prefix
+template <typename _Tp>
+concept EnabledForHash = requires(_Tp t) {
+  { std::bool_constant<std::__is_unqualified_v<_Tp>>() } -> std::same_as<std::true_type>;
+};
+
+template <typename _Tp>
+concept DisabledForHash = not EnabledForHash<_Tp>;
+
+// TODO: document the constraints of using this at runtime OR make it consteval only
+template <typename _Tp>
+struct constexpr_hash;
+
+template <DisabledForHash _Tp>
+struct constexpr_hash<_Tp> {
+  constexpr_hash()                                   = delete;
+  constexpr_hash(const constexpr_hash&)            = delete;
+  constexpr_hash& operator=(const constexpr_hash&) = delete;
+};
+
+template <EnabledForHash _Tp>
+struct constexpr_hash<_Tp> {
+  [[__nodiscard__]] constexpr _LIBCPP_HIDE_FROM_ABI size_t operator()(const _Tp& __v) const noexcept {
+    if constexpr (std::is_same_v<_Tp, nullptr_t>) {
+      return 662607004ull;
+    } else if constexpr (std::is_integral_v<_Tp>) {
+      if constexpr (sizeof(_Tp) <= sizeof(size_t)) {
+        return static_cast<size_t>(__v);
+      } else {
+        constexpr size_t multiple = sizeof(_Tp) / sizeof(size_t);
+        char region[multiple];
+
+        // TODO: 0, 1, 2, 3, 4
+        return region[multiple - 1]; // TODO: hash-ing
+      }
+    }
+    __builtin_unreachable(); // todo: revisit
+  }
+
+  constexpr_hash() noexcept                          = default;
+  constexpr_hash& operator=(const constexpr_hash&) = default;
+};
+
+} // namespace support
+
+#endif

>From ae90cb4d7dbfd421d9af691293c35f596bad828b Mon Sep 17 00:00:00 2001
From: Vinay Deshmukh <vinay_deshmukh at outlook.com>
Date: Sun, 23 Aug 2026 18:31:40 +0100
Subject: [PATCH 06/10] floating.pass.cpp

---
 .../unord.hash/floating.pass.cpp              | 32 ++++++++++++-------
 libcxx/test/support/constexpr_hash.h          | 20 ++++++++++++
 2 files changed, 40 insertions(+), 12 deletions(-)

diff --git a/libcxx/test/std/utilities/function.objects/unord.hash/floating.pass.cpp b/libcxx/test/std/utilities/function.objects/unord.hash/floating.pass.cpp
index f6c31d068b231..c377aab5e869d 100644
--- a/libcxx/test/std/utilities/function.objects/unord.hash/floating.pass.cpp
+++ b/libcxx/test/std/utilities/function.objects/unord.hash/floating.pass.cpp
@@ -25,23 +25,23 @@
 #include <limits>
 #include <cmath>
 
+#include "constexpr_hash.h"
 #include "test_macros.h"
 
 #if TEST_STD_VER >= 11
 #  include "poisoned_hash_helper.h"
 #endif
 
-template <class T>
-void
-test()
-{
+template <class T, template <class> class THash = std::hash >
+TEST_CONSTEXPR_CXX26 void test() {
 #if TEST_STD_VER >= 11
-    test_hash_disabled<const T>();
-    test_hash_disabled<volatile T>();
-    test_hash_disabled<const volatile T>();
+  test_hash_disabled<const T, THash<const T>>();
+  test_hash_disabled<volatile T, THash<volatile T>>();
+  test_hash_disabled<const volatile T, THash<const volatile T>>();
 #endif
 
-    typedef std::hash<T> H;
+  // typedef std::hash<T> H;
+  typedef THash<T> H;
 #if TEST_STD_VER <= 17
     static_assert((std::is_same<typename H::argument_type, T>::value), "");
     static_assert((std::is_same<typename H::result_type, std::size_t>::value), "");
@@ -78,11 +78,19 @@ test()
     assert(pinf != ninf);
 }
 
+template <template <typename> typename THash >
+TEST_CONSTEXPR_CXX26 bool test_with_hash() {
+  test<float, THash>();
+  test<double, THash>();
+  test<long double, THash>();
+  return true;
+}
+
 int main(int, char**)
 {
-    test<float>();
-    test<double>();
-    test<long double>();
-
+  assert(test_with_hash<std::hash>());
+#if TEST_STD_VER >= 26
+  static_assert(test_with_hash<support::constexpr_hash>());
+#endif
   return 0;
 }
diff --git a/libcxx/test/support/constexpr_hash.h b/libcxx/test/support/constexpr_hash.h
index b7881f0c73df9..e298500f60ad5 100644
--- a/libcxx/test/support/constexpr_hash.h
+++ b/libcxx/test/support/constexpr_hash.h
@@ -4,6 +4,7 @@
 
 #include "test_macros.h"
 #include <type_traits>
+#include <bit>
 
 namespace support {
 
@@ -44,6 +45,25 @@ struct constexpr_hash<_Tp> {
         // TODO: 0, 1, 2, 3, 4
         return region[multiple - 1]; // TODO: hash-ing
       }
+    } else if constexpr (std::is_floating_point_v<_Tp>) {
+    if (__v == 0.0f)
+      return 0;
+    // return __scalar_hash<_Tp>::operator()(__v);
+
+        constexpr size_t multiple = sizeof(_Tp) / sizeof(char);
+        std::array<char, multiple> region = std::bit_cast<std::array<char, multiple>>(__v);
+
+        // TODO: pack indexing?
+        size_t res = 0;
+        for(size_t i = 0; i < multiple; ++i) {
+            res ^= region[i];
+        }
+        return res;
+
+        // TODO: 0, 1, 2, 3, 4
+        // return region[multiple - 1]; // TODO: hash-ing
+        // return 1234ULL;
+
     }
     __builtin_unreachable(); // todo: revisit
   }

>From 69d7d90dfa9e9690218e7efbb9b24a846fe056cf Mon Sep 17 00:00:00 2001
From: Vinay Deshmukh <vinay_deshmukh at outlook.com>
Date: Mon, 24 Aug 2026 08:31:18 +0100
Subject: [PATCH 07/10] disable pointer types explicitly

---
 .../unord.hash/pointer.pass.cpp               | 30 +++++++++++++------
 libcxx/test/support/constexpr_hash.h          | 25 +++++++++++++---
 2 files changed, 42 insertions(+), 13 deletions(-)

diff --git a/libcxx/test/std/utilities/function.objects/unord.hash/pointer.pass.cpp b/libcxx/test/std/utilities/function.objects/unord.hash/pointer.pass.cpp
index 784831018a006..c6c2a6e805f2b 100644
--- a/libcxx/test/std/utilities/function.objects/unord.hash/pointer.pass.cpp
+++ b/libcxx/test/std/utilities/function.objects/unord.hash/pointer.pass.cpp
@@ -24,13 +24,13 @@
 #include <functional>
 #include <type_traits>
 
+#include "constexpr_hash.h"
 #include "test_macros.h"
 
-template <class T>
-void
-test()
-{
-    typedef std::hash<T> H;
+template <class T, template <class> class THash = std::hash >
+TEST_CONSTEXPR_CXX26 void test() {
+  // typedef std::hash<T> H;
+  typedef THash<T> H;
 #if TEST_STD_VER <= 17
     static_assert((std::is_same<typename H::argument_type, T>::value), "");
     static_assert((std::is_same<typename H::result_type, std::size_t>::value), "");
@@ -44,9 +44,11 @@ test()
     assert(h(&i) != h(&j));
 }
 
-void test_nullptr() {
+template < template <class> class THash = std::hash >
+TEST_CONSTEXPR_CXX26 void test_nullptr() {
   typedef std::nullptr_t T;
-  typedef std::hash<T> H;
+  // typedef std::hash<T> H;
+  typedef THash<T> H;
 #if TEST_STD_VER <= 17
   static_assert((std::is_same<typename H::argument_type, T>::value), "");
   static_assert((std::is_same<typename H::result_type, std::size_t>::value), "");
@@ -54,10 +56,20 @@ void test_nullptr() {
   ASSERT_NOEXCEPT(H()(T()));
 }
 
+template <template <typename> typename THash >
+TEST_CONSTEXPR_CXX26 bool test_with_hash() {
+  test_nullptr< THash>();
+  return true;
+}
+
 int main(int, char**)
 {
-    test<int*>();
-    test_nullptr();
+  assert(test_with_hash<std::hash>());
+  test<int*>();
+
+#if TEST_STD_VER >= 26
+  static_assert(test_with_hash<support::constexpr_hash>());
+#endif
 
   return 0;
 }
diff --git a/libcxx/test/support/constexpr_hash.h b/libcxx/test/support/constexpr_hash.h
index e298500f60ad5..0debee277ed0a 100644
--- a/libcxx/test/support/constexpr_hash.h
+++ b/libcxx/test/support/constexpr_hash.h
@@ -14,10 +14,26 @@ namespace support {
 template <typename _Tp>
 concept EnabledForHash = requires(_Tp t) {
   { std::bool_constant<std::__is_unqualified_v<_Tp>>() } -> std::same_as<std::true_type>;
-};
+}
+
+// we need to disable hashing for `pointer` types, since we can't cast a pointer to a fixed width integer during constant evaluation via std::bit_cast, std::memcpy or union type-aliasing
+//  note: bit_cast from a pointer type is not allowed in a constant expression
+// read of member '__a' of union with active member '__t' is not allowed in a constant expression
+/*
+
+    union {
+      _Tp __t;
+      size_t __a;
+    } __u;
+    __u.__a = 0;
+    __u.__t = __v;
+    return __u.__a;
+*/
+and not std::is_pointer_v<_Tp>;
+
 
 template <typename _Tp>
-concept DisabledForHash = not EnabledForHash<_Tp>;
+concept DisabledForHash = not EnabledForHash<_Tp> ;
 
 // TODO: document the constraints of using this at runtime OR make it consteval only
 template <typename _Tp>
@@ -45,8 +61,9 @@ struct constexpr_hash<_Tp> {
         // TODO: 0, 1, 2, 3, 4
         return region[multiple - 1]; // TODO: hash-ing
       }
-    } else if constexpr (std::is_floating_point_v<_Tp>) {
+    } else if constexpr (std::is_floating_point_v<_Tp> ) {
     if (__v == 0.0f)
+
       return 0;
     // return __scalar_hash<_Tp>::operator()(__v);
 
@@ -64,7 +81,7 @@ struct constexpr_hash<_Tp> {
         // return region[multiple - 1]; // TODO: hash-ing
         // return 1234ULL;
 
-    }
+    } 
     __builtin_unreachable(); // todo: revisit
   }
 

>From e6378f30fff8c24a6233e7fde3d5dcf83ab1999d Mon Sep 17 00:00:00 2001
From: Vinay Deshmukh <vinay_deshmukh at outlook.com>
Date: Mon, 24 Aug 2026 08:48:13 +0100
Subject: [PATCH 08/10] enum.pass.cpp

---
 .../function.objects/unord.hash/enum.pass.cpp | 39 +++++++-----
 libcxx/test/support/constexpr_hash.h          | 60 ++++++++++---------
 2 files changed, 57 insertions(+), 42 deletions(-)

diff --git a/libcxx/test/std/utilities/function.objects/unord.hash/enum.pass.cpp b/libcxx/test/std/utilities/function.objects/unord.hash/enum.pass.cpp
index 05a19329f909a..cdc982f12c638 100644
--- a/libcxx/test/std/utilities/function.objects/unord.hash/enum.pass.cpp
+++ b/libcxx/test/std/utilities/function.objects/unord.hash/enum.pass.cpp
@@ -19,6 +19,7 @@
 #include <limits>
 #include <type_traits>
 
+#include "constexpr_hash.h"
 #include "test_macros.h"
 
 #if TEST_STD_VER >= 11
@@ -33,17 +34,16 @@ enum class EightBitColors : std::uint8_t { red, orange, yellow, green, blue, ind
 
 enum Fruits { apple, pear, grape, mango, cantaloupe };
 
-template <class T>
-void
-test()
-{
+template <class T, template <class> typename THash>
+TEST_CONSTEXPR_CXX26 void test() {
 #if TEST_STD_VER >= 11
     test_hash_disabled<const T>();
     test_hash_disabled<volatile T>();
     test_hash_disabled<const volatile T>();
 #endif
 
-    typedef std::hash<T> H;
+    // typedef std::hash<T> H;
+    typedef THash<T> H;
 #if TEST_STD_VER <= 17
     static_assert((std::is_same<typename H::argument_type, T>::value), "");
     static_assert((std::is_same<typename H::result_type, std::size_t>::value), "");
@@ -52,7 +52,8 @@ test()
     typedef typename std::underlying_type<T>::type under_type;
 
     H h1;
-    std::hash<under_type> h2;
+    // std::hash<under_type> h2;
+    THash<under_type> h2;
     for (int i = 0; i <= 5; ++i)
     {
         T t(static_cast<T> (i));
@@ -62,16 +63,26 @@ test()
     }
 }
 
-int main(int, char**)
-{
-    test<Cardinals>();
+template <template <typename> typename THash >
+TEST_CONSTEXPR_CXX26 bool test_with_hash() {
+  test<Cardinals, THash>();
 
-    test<Colors>();
-    test<ShortColors>();
-    test<LongColors>();
-    test<EightBitColors>();
+  test<Colors, THash>();
+  test<ShortColors, THash>();
+  test<LongColors, THash>();
+  test<EightBitColors, THash>();
 
-    test<Fruits>();
+  test<Fruits, THash>();
+
+  return true;
+}
+
+int main(int, char**) {
+  assert(test_with_hash<std::hash>());
+
+#if TEST_STD_VER >= 26
+  static_assert(test_with_hash<support::constexpr_hash>());
+#endif
 
   return 0;
 }
diff --git a/libcxx/test/support/constexpr_hash.h b/libcxx/test/support/constexpr_hash.h
index 0debee277ed0a..8c79b15db87d6 100644
--- a/libcxx/test/support/constexpr_hash.h
+++ b/libcxx/test/support/constexpr_hash.h
@@ -1,6 +1,7 @@
 
 
 #include <__type_traits/is_unqualified.h>
+#include <__type_traits/underlying_type.h>
 
 #include "test_macros.h"
 #include <type_traits>
@@ -12,14 +13,15 @@ namespace support {
 
 // TODO: use _ prefix
 template <typename _Tp>
-concept EnabledForHash = requires(_Tp t) {
-  { std::bool_constant<std::__is_unqualified_v<_Tp>>() } -> std::same_as<std::true_type>;
-}
+concept EnabledForHash =
+    requires(_Tp t) {
+      { std::bool_constant<std::__is_unqualified_v<_Tp>>() } -> std::same_as<std::true_type>;
+    }
 
-// we need to disable hashing for `pointer` types, since we can't cast a pointer to a fixed width integer during constant evaluation via std::bit_cast, std::memcpy or union type-aliasing
-//  note: bit_cast from a pointer type is not allowed in a constant expression
-// read of member '__a' of union with active member '__t' is not allowed in a constant expression
-/*
+    // we need to disable hashing for `pointer` types, since we can't cast a pointer to a fixed width integer during constant evaluation via std::bit_cast, std::memcpy or union type-aliasing
+    //  note: bit_cast from a pointer type is not allowed in a constant expression
+    // read of member '__a' of union with active member '__t' is not allowed in a constant expression
+    /*
 
     union {
       _Tp __t;
@@ -29,11 +31,10 @@ concept EnabledForHash = requires(_Tp t) {
     __u.__t = __v;
     return __u.__a;
 */
-and not std::is_pointer_v<_Tp>;
-
+    and not std::is_pointer_v<_Tp>;
 
 template <typename _Tp>
-concept DisabledForHash = not EnabledForHash<_Tp> ;
+concept DisabledForHash = not EnabledForHash<_Tp>;
 
 // TODO: document the constraints of using this at runtime OR make it consteval only
 template <typename _Tp>
@@ -41,7 +42,7 @@ struct constexpr_hash;
 
 template <DisabledForHash _Tp>
 struct constexpr_hash<_Tp> {
-  constexpr_hash()                                   = delete;
+  constexpr_hash()                                 = delete;
   constexpr_hash(const constexpr_hash&)            = delete;
   constexpr_hash& operator=(const constexpr_hash&) = delete;
 };
@@ -61,31 +62,34 @@ struct constexpr_hash<_Tp> {
         // TODO: 0, 1, 2, 3, 4
         return region[multiple - 1]; // TODO: hash-ing
       }
-    } else if constexpr (std::is_floating_point_v<_Tp> ) {
-    if (__v == 0.0f)
+    } else if constexpr (std::is_floating_point_v<_Tp>) {
+      if (__v == 0.0f)
 
-      return 0;
-    // return __scalar_hash<_Tp>::operator()(__v);
+        return 0;
+      // return __scalar_hash<_Tp>::operator()(__v);
 
-        constexpr size_t multiple = sizeof(_Tp) / sizeof(char);
-        std::array<char, multiple> region = std::bit_cast<std::array<char, multiple>>(__v);
+      constexpr size_t multiple         = sizeof(_Tp) / sizeof(char);
+      std::array<char, multiple> region = std::bit_cast<std::array<char, multiple>>(__v);
 
-        // TODO: pack indexing?
-        size_t res = 0;
-        for(size_t i = 0; i < multiple; ++i) {
-            res ^= region[i];
-        }
-        return res;
+      // TODO: pack indexing?
+      size_t res = 0;
+      for (size_t i = 0; i < multiple; ++i) {
+        res ^= region[i];
+      }
+      return res;
 
-        // TODO: 0, 1, 2, 3, 4
-        // return region[multiple - 1]; // TODO: hash-ing
-        // return 1234ULL;
+      // TODO: 0, 1, 2, 3, 4
+      // return region[multiple - 1]; // TODO: hash-ing
+      // return 1234ULL;
 
-    } 
+    } else if constexpr (std::is_enum_v<_Tp>) {
+      using type = std::__underlying_type_t<_Tp>;
+      return constexpr_hash<type>()(static_cast<type>(__v));
+    }
     __builtin_unreachable(); // todo: revisit
   }
 
-  constexpr_hash() noexcept                          = default;
+  constexpr_hash() noexcept                        = default;
   constexpr_hash& operator=(const constexpr_hash&) = default;
 };
 

>From 89fc017527db8466fdf95f8983ee5bb2760a9e5d Mon Sep 17 00:00:00 2001
From: Vinay Deshmukh <vinay_deshmukh at outlook.com>
Date: Wed, 26 Aug 2026 08:54:26 +0100
Subject: [PATCH 09/10] non_enum passes

---
 .../unord.hash/non_enum.pass.cpp              | 30 ++++++---
 libcxx/test/support/constexpr_hash.h          | 62 +++++++++----------
 2 files changed, 53 insertions(+), 39 deletions(-)

diff --git a/libcxx/test/std/utilities/function.objects/unord.hash/non_enum.pass.cpp b/libcxx/test/std/utilities/function.objects/unord.hash/non_enum.pass.cpp
index 94cc9d912f3a7..8632e907f1caf 100644
--- a/libcxx/test/std/utilities/function.objects/unord.hash/non_enum.pass.cpp
+++ b/libcxx/test/std/utilities/function.objects/unord.hash/non_enum.pass.cpp
@@ -18,22 +18,36 @@
 #include <cassert>
 #include <type_traits>
 
+#include "constexpr_hash.h"
 #include "test_macros.h"
 
 struct X {};
 
-int main(int, char**)
-{
-    using H = std::hash<X>;
-    static_assert(!std::is_default_constructible<H>::value, "");
-    static_assert(!std::is_copy_constructible<H>::value, "");
-    static_assert(!std::is_move_constructible<H>::value, "");
-    static_assert(!std::is_copy_assignable<H>::value, "");
-    static_assert(!std::is_move_assignable<H>::value, "");
+template <template <typename> typename THash >
+TEST_CONSTEXPR_CXX26 bool test_with_hash() {
+  using H = THash<X>;
+
+  static_assert(!std::is_default_constructible<H>::value, "");
+  static_assert(!std::is_copy_constructible<H>::value, "");
+  static_assert(!std::is_move_constructible<H>::value, "");
+  static_assert(!std::is_copy_assignable<H>::value, "");
+  static_assert(!std::is_move_assignable<H>::value, "");
 #if TEST_STD_VER > 14
     static_assert(!std::is_invocable<H, X&>::value, "");
     static_assert(!std::is_invocable<H, X const&>::value, "");
 #endif
 
+    return true;
+}
+
+int main(int, char**) {
+  assert(test_with_hash<std::hash>());
+
+#if TEST_STD_VER >= 26
+  static_assert(test_with_hash<support::constexpr_hash>());
+#endif
+  // using H = std::hash<X>;
+  // using H = support::constexpr_hash<X>;
+
   return 0;
 }
diff --git a/libcxx/test/support/constexpr_hash.h b/libcxx/test/support/constexpr_hash.h
index 8c79b15db87d6..9281384e1f30b 100644
--- a/libcxx/test/support/constexpr_hash.h
+++ b/libcxx/test/support/constexpr_hash.h
@@ -11,48 +11,41 @@ namespace support {
 
 #if TEST_STD_VER >= 26
 
-// TODO: use _ prefix
-template <typename _Tp>
-concept EnabledForHash =
-    requires(_Tp t) {
-      { std::bool_constant<std::__is_unqualified_v<_Tp>>() } -> std::same_as<std::true_type>;
-    }
-
-    // we need to disable hashing for `pointer` types, since we can't cast a pointer to a fixed width integer during constant evaluation via std::bit_cast, std::memcpy or union type-aliasing
-    //  note: bit_cast from a pointer type is not allowed in a constant expression
-    // read of member '__a' of union with active member '__t' is not allowed in a constant expression
-    /*
-
-    union {
-      _Tp __t;
-      size_t __a;
-    } __u;
-    __u.__a = 0;
-    __u.__t = __v;
-    return __u.__a;
-*/
-    and not std::is_pointer_v<_Tp>;
-
-template <typename _Tp>
-concept DisabledForHash = not EnabledForHash<_Tp>;
-
 // TODO: document the constraints of using this at runtime OR make it consteval only
 template <typename _Tp>
 struct constexpr_hash;
 
-template <DisabledForHash _Tp>
-struct constexpr_hash<_Tp> {
+// we need to disable hashing for `pointer` types, since we can't cast a pointer to a fixed width integer during constant evaluation via std::bit_cast, std::memcpy or union type-aliasing
+template <class _Tp>
+struct constexpr_hash<_Tp*> {
   constexpr_hash()                                 = delete;
   constexpr_hash(const constexpr_hash&)            = delete;
   constexpr_hash& operator=(const constexpr_hash&) = delete;
 };
 
-template <EnabledForHash _Tp>
+template <>
+struct constexpr_hash<std::nullptr_t> {
+  constexpr_hash() noexcept                        = default;
+  constexpr_hash& operator=(const constexpr_hash&) = default;
+
+  [[__nodiscard__]] constexpr _LIBCPP_HIDE_FROM_ABI size_t operator()(std::nullptr_t) const noexcept {
+    return 662607004ull;
+  }
+};
+
+template <typename _Tp>
+  requires requires(_Tp t) {
+    { std::bool_constant<std::__is_unqualified_v<_Tp>>() } -> std::same_as<std::true_type>;
+
+    {
+      std::bool_constant< std::bool_constant<std::is_integral_v<_Tp>>() ||
+                          std::bool_constant<std::is_floating_point_v<_Tp>>() ||
+                          std::bool_constant<std::is_enum_v<_Tp>>() >()
+    } -> std::same_as<std::true_type>;
+  }
 struct constexpr_hash<_Tp> {
   [[__nodiscard__]] constexpr _LIBCPP_HIDE_FROM_ABI size_t operator()(const _Tp& __v) const noexcept {
-    if constexpr (std::is_same_v<_Tp, nullptr_t>) {
-      return 662607004ull;
-    } else if constexpr (std::is_integral_v<_Tp>) {
+    if constexpr (std::is_integral_v<_Tp>) {
       if constexpr (sizeof(_Tp) <= sizeof(size_t)) {
         return static_cast<size_t>(__v);
       } else {
@@ -93,6 +86,13 @@ struct constexpr_hash<_Tp> {
   constexpr_hash& operator=(const constexpr_hash&) = default;
 };
 
+template <class _Tp>
+struct constexpr_hash {
+  constexpr_hash()                                 = delete;
+  constexpr_hash(const constexpr_hash&)            = delete;
+  constexpr_hash& operator=(const constexpr_hash&) = delete;
+};
+
 } // namespace support
 
 #endif

>From df2ad7abc83d6f4fbfc82b4f16a4fd924034d61d Mon Sep 17 00:00:00 2001
From: Vinay Deshmukh <vinay_deshmukh at outlook.com>
Date: Fri, 28 Aug 2026 09:08:45 +0100
Subject: [PATCH 10/10] enabled_hashes.pass.cpp

---
 .../unord.hash/enabled_hashes.pass.cpp        |  5 ++++
 libcxx/test/support/constexpr_hash.h          |  8 +++--
 libcxx/test/support/poisoned_hash_helper.h    | 30 +++++++++++++++++--
 3 files changed, 39 insertions(+), 4 deletions(-)

diff --git a/libcxx/test/std/utilities/function.objects/unord.hash/enabled_hashes.pass.cpp b/libcxx/test/std/utilities/function.objects/unord.hash/enabled_hashes.pass.cpp
index 53d8ed788d866..43afca3407174 100644
--- a/libcxx/test/std/utilities/function.objects/unord.hash/enabled_hashes.pass.cpp
+++ b/libcxx/test/std/utilities/function.objects/unord.hash/enabled_hashes.pass.cpp
@@ -18,9 +18,14 @@
 #include "poisoned_hash_helper.h"
 
 #include "test_macros.h"
+#include "constexpr_hash.h"
 
 int main(int, char**) {
   test_library_hash_specializations_available();
 
+#if TEST_STD_VER >= 26
+  static_assert(test_constexpr_hash_specializations_available());
+#endif
+
   return 0;
 }
diff --git a/libcxx/test/support/constexpr_hash.h b/libcxx/test/support/constexpr_hash.h
index 9281384e1f30b..b3278ea28c382 100644
--- a/libcxx/test/support/constexpr_hash.h
+++ b/libcxx/test/support/constexpr_hash.h
@@ -1,4 +1,6 @@
 
+#ifndef SUPPORT_CONSTEXPR_HASH_H
+#define SUPPORT_CONSTEXPR_HASH_H
 
 #include <__type_traits/is_unqualified.h>
 #include <__type_traits/underlying_type.h>
@@ -49,8 +51,8 @@ struct constexpr_hash<_Tp> {
       if constexpr (sizeof(_Tp) <= sizeof(size_t)) {
         return static_cast<size_t>(__v);
       } else {
-        constexpr size_t multiple = sizeof(_Tp) / sizeof(size_t);
-        char region[multiple];
+        constexpr size_t multiple           = sizeof(_Tp) / sizeof(size_t);
+        std::array<size_t, multiple> region = std::bit_cast<std::array<size_t, multiple>>(__v);
 
         // TODO: 0, 1, 2, 3, 4
         return region[multiple - 1]; // TODO: hash-ing
@@ -96,3 +98,5 @@ struct constexpr_hash {
 } // namespace support
 
 #endif
+
+#endif // SUPPORT_CONSTEXPR_HASH_H
\ No newline at end of file
diff --git a/libcxx/test/support/poisoned_hash_helper.h b/libcxx/test/support/poisoned_hash_helper.h
index ec3f6e9af813f..e1cc82244200e 100644
--- a/libcxx/test/support/poisoned_hash_helper.h
+++ b/libcxx/test/support/poisoned_hash_helper.h
@@ -16,6 +16,7 @@
 #include <type_traits>
 #include <utility>
 
+#include "constexpr_hash.h"
 #include "test_macros.h"
 #include "type_algorithms.h"
 
@@ -128,9 +129,9 @@ using LibraryHashTypes =
                          types::type_list<Enum, EnumClass, void*, void const*, Class*, std::nullptr_t>>;
 
 struct TestHashEnabled {
-  template <class T>
+  template <class Key>
   void operator()() const {
-    test_hash_enabled<T>();
+    test_hash_enabled<Key>();
   }
 };
 
@@ -141,4 +142,29 @@ void test_library_hash_specializations_available() {
   types::for_each(Types(), TestHashEnabled());
 }
 
+#if TEST_STD_VER >= 26
+
+struct TestConstexprHashEnabled {
+  template <class Key>
+  constexpr void operator()() const {
+    test_hash_enabled<Key, support::constexpr_hash<Key>>();
+  }
+};
+
+// We use the same types that std::hash uses, except for pointers since pointers can't be hashed during constant evaluation
+using SupportedConstexprHashTypes =
+    types::concatenate_t<types::arithmetic_types,
+                         types::type_list<Enum,
+                                          EnumClass, //void*, void const*, Class*,
+                                          std::nullptr_t>>;
+
+// Test that each of the library hash specializations for arithmetic types,
+// enum types, and pointer types are available and enabled.
+template <class Types = SupportedConstexprHashTypes>
+constexpr bool test_constexpr_hash_specializations_available() {
+  types::for_each(Types(), TestConstexprHashEnabled());
+  return true;
+}
+#endif
+
 #endif // SUPPORT_POISONED_HASH_HELPER_H



More information about the libcxx-commits mailing list