[libcxx-commits] [libcxx] [libc++][support] Create a `constexpr` hash to use during constant evaluation (PR #214946)
Vinay Deshmukh via libcxx-commits
libcxx-commits at lists.llvm.org
Sun Nov 1 03:45:54 PST 2026
================
@@ -0,0 +1,135 @@
+//===- constexpr_hash.h - A hash function to use during constant evaluation -------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines a constexpr `hash`
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef SUPPORT_CONSTEXPR_HASH_H
+#define SUPPORT_CONSTEXPR_HASH_H
+
+#include <utility>
+
+#include "test_macros.h"
+#include <type_traits>
+#include <concepts>
+#include <bit>
+#include <cstddef>
+#include <cmath>
+#include <array>
+
+#if TEST_STD_VER >= 26
+
+namespace support {
+
+template <class Key>
+struct constexpr_hash {
+ constexpr_hash() = delete;
+ constexpr_hash(const constexpr_hash&) = delete;
+ constexpr_hash& operator=(const constexpr_hash&) = delete;
+};
+
+// 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 Key>
+struct constexpr_hash<Key*> {
+ constexpr_hash() = delete;
+ constexpr_hash(const constexpr_hash&) = delete;
+ constexpr_hash& operator=(const constexpr_hash&) = delete;
+};
+
+template <>
+struct constexpr_hash<std::nullptr_t> {
+ constexpr_hash() noexcept = default;
+ constexpr_hash& operator=(const constexpr_hash&) = default;
+
+ [[nodiscard]] constexpr _LIBCPP_HIDE_FROM_ABI std::size_t operator()(std::nullptr_t) const noexcept {
+ return 662607004ull;
+ }
+};
+
+template <typename Key>
+concept UnqualifiedArithmeticOrEnum =
+ std::is_same_v<Key, std::remove_cvref_t<Key>> &&
+ (std::integral<Key> || std::floating_point<Key> || std::is_enum_v<Key>);
+
+template <size_t N>
+constexpr std::size_t array_hash(std::array<std::byte, N> arr) {
+ // based on fnv-1
+ std::size_t res = (std::size_t)14695981039346656037ULL;
+ for (auto c : arr) {
+ res ^= (std::size_t)c;
+ res *= 1099511628211ULL;
+ }
+ return res;
+}
+
+template <UnqualifiedArithmeticOrEnum Key>
+struct constexpr_hash<Key> {
+ [[nodiscard]] constexpr _LIBCPP_HIDE_FROM_ABI std::size_t operator()(const Key& key) const noexcept {
+ if constexpr (std::integral<Key>) {
+ if constexpr (sizeof(Key) <= sizeof(size_t)) {
+ return static_cast<size_t>(key);
+ } else {
+ return array_hash(std::bit_cast<std::array<std::byte, sizeof(Key)>>(key));
+ }
+ } else if constexpr (std::floating_point<Key>) {
+ if (key == 0.0f)
+ return 0;
+# if defined(_WIN32)
+ // FIXME:
+ // std::isnan, std::isinf, std::signbit is not constexpr on Windows
+ // https://github.com/llvm/llvm-project/issues/214358
+
+ if (__builtin_isnan(key)) {
+ return (std::size_t)0xc3a5c85c97cb3127ULL;
+ }
+ if (__builtin_isinf(key)) {
+ return __builtin_signbit(key) ? (std::size_t)0xb492b66fbe98f273ULL : (std::size_t)0x9ae16a3b2f90404fULL;
+ }
+
+# else
+ if (std::isnan(key)) {
+ return 0xc3a5c85c97cb3127ULL;
+ }
+ if (std::isinf(key)) {
+ return std::signbit(key) ? 0xb492b66fbe98f273ULL : 0x9ae16a3b2f90404fULL;
+ }
+# endif
+
+ if constexpr (std::is_same_v<Key, long double>) {
+ // long double can have padding on certain platforms which are uninitialized, and hence, can't be used during constant evaluation
+ // to get around that we break it down to 2 double (which have all bits initialized).
----------------
vinay-deshmukh wrote:
an alternative could be using `std::frexp` but that isn't constexpr yet (in clang).
To clarify, this section only exists to get rid of uninitialized bytes so we can hash the initialized bytes.
`std::hash` works around this because of type-punning & padding-bytes not being copied when assigning to a double/float.
https://github.com/llvm/llvm-project/pull/214946
More information about the libcxx-commits
mailing list