From 1b845ac72ce5022fb022855b2a578e522a9563e8 Mon Sep 17 00:00:00 2001 From: Jeremy Nimmer Date: Tue, 7 Jul 2026 14:03:22 -0700 Subject: [PATCH] Mark static_empty_bucket_ptr storage as const(expr) Even though the returned pointer must be non-const, it would be a mistake if any code in this library ever wrote into it. Marking the storage const (and casting away the const-ness on return) is a clear way to indicate that. (It may even show the fault during unit testing in case such a bug was ever introduced.) Furthermore, when C++20 is enabled we can mark it constexpr, which means that the storage is defined as part of the compiled library's readonly data and doesn't need the atomic-initialize-upon-first-use guard variable. This also helps to avoid ODR hazards when linking this header-only library into multiple shared libraries (as can happen with nanobind when in NB_STATIC mode). While we're here, we might as well mark all static constants constexpr as a cleanup -- even those not needed by the bucket_entry code. --- include/tsl/robin_growth_policy.h | 2 +- include/tsl/robin_hash.h | 36 +++++++++++++++++++++++-------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/include/tsl/robin_growth_policy.h b/include/tsl/robin_growth_policy.h index 9abba3b..aa44928 100644 --- a/include/tsl/robin_growth_policy.h +++ b/include/tsl/robin_growth_policy.h @@ -233,7 +233,7 @@ class mod_growth_policy { private: static constexpr double REHASH_SIZE_MULTIPLICATION_FACTOR = 1.0 * GrowthFactor::num / GrowthFactor::den; - static const std::size_t MAX_BUCKET_COUNT = + static constexpr std::size_t MAX_BUCKET_COUNT = std::size_t(double(std::numeric_limits::max() / REHASH_SIZE_MULTIPLICATION_FACTOR)); diff --git a/include/tsl/robin_hash.h b/include/tsl/robin_hash.h index 51662be..9846057 100644 --- a/include/tsl/robin_hash.h +++ b/include/tsl/robin_hash.h @@ -142,6 +142,11 @@ class bucket_entry_hash { truncated_hash_type m_hash; }; +/** + * Tag type to select the constexpr bucket_entry constructor. + */ +struct constexpr_last_bucket_t {}; + /** * Each bucket entry has: * - A value of type `ValueType`. @@ -182,6 +187,12 @@ class bucket_entry : public bucket_entry_hash { tsl_rh_assert(empty()); } + constexpr bucket_entry(constexpr_last_bucket_t /* ignored */) noexcept + : bucket_hash(), + m_dist_from_ideal_bucket(EMPTY_MARKER_DIST_FROM_IDEAL_BUCKET), + m_last_bucket(true), + m_value() {} + bucket_entry(const bucket_entry& other) noexcept( std::is_nothrow_copy_constructible::value) : bucket_hash(other), @@ -233,16 +244,19 @@ class bucket_entry : public bucket_entry_hash { bucket_entry& operator=(bucket_entry&&) = delete; +#if defined(__cpp_constexpr) && __cpp_constexpr >= 201907L + constexpr +#endif ~bucket_entry() noexcept { clear(); } - void clear() noexcept { + constexpr void clear() noexcept { if (!empty()) { destroy_value(); m_dist_from_ideal_bucket = EMPTY_MARKER_DIST_FROM_IDEAL_BUCKET; } } - bool empty() const noexcept { + constexpr bool empty() const noexcept { return m_dist_from_ideal_bucket == EMPTY_MARKER_DIST_FROM_IDEAL_BUCKET; } @@ -311,8 +325,8 @@ class bucket_entry : public bucket_entry_hash { } public: - static const distance_type EMPTY_MARKER_DIST_FROM_IDEAL_BUCKET = -1; - static const distance_type DIST_FROM_IDEAL_BUCKET_LIMIT = 8192; + static constexpr distance_type EMPTY_MARKER_DIST_FROM_IDEAL_BUCKET = -1; + static constexpr distance_type DIST_FROM_IDEAL_BUCKET_LIMIT = 8192; static_assert(DIST_FROM_IDEAL_BUCKET_LIMIT <= std::numeric_limits::max() - 1, "DIST_FROM_IDEAL_BUCKET_LIMIT must be <= " @@ -1560,7 +1574,7 @@ class robin_hash : private Hash, private KeyEqual, private GrowthPolicy { } public: - static const size_type DEFAULT_INIT_BUCKETS_SIZE = 0; + static constexpr size_type DEFAULT_INIT_BUCKETS_SIZE = 0; static constexpr float DEFAULT_MAX_LOAD_FACTOR = 0.5f; static constexpr float MINIMUM_MAX_LOAD_FACTOR = 0.2f; @@ -1581,16 +1595,20 @@ class robin_hash : private Hash, private KeyEqual, private GrowthPolicy { /** * Protocol version currenlty used for serialization. */ - static const slz_size_type SERIALIZATION_PROTOCOL_VERSION = 1; + static constexpr slz_size_type SERIALIZATION_PROTOCOL_VERSION = 1; /** * Return an always valid pointer to an static empty bucket_entry with - * last_bucket() == true. + * last_bucket() == true. The bucket_entry must be treated as `const`. */ bucket_entry* static_empty_bucket_ptr() noexcept { - static bucket_entry empty_bucket(true); +#if defined(__cpp_constexpr) && __cpp_constexpr >= 201907L + static constexpr bucket_entry empty_bucket(constexpr_last_bucket_t{}); +#else + static const bucket_entry empty_bucket(constexpr_last_bucket_t{}); +#endif tsl_rh_assert(empty_bucket.empty()); - return &empty_bucket; + return const_cast(&empty_bucket); } private: