[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
Sun Aug 23 10:32:05 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 1/6] 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 2/6] 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 3/6] 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 4/6] -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 5/6] 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 6/6] 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
}
More information about the libcxx-commits
mailing list