From 35f444e18cfd7409d34a0861398cf4722fa133c2 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Tue, 1 Aug 2023 11:14:18 +0200 Subject: [PATCH 01/41] formatting --- .../dice/sparse-map/boost_offset_pointer.hpp | 34 +- .../dice/sparse-map/sparse_growth_policy.hpp | 518 +- include/dice/sparse-map/sparse_hash.hpp | 4408 ++++++++--------- include/dice/sparse-map/sparse_map.hpp | 1497 +++--- include/dice/sparse-map/sparse_set.hpp | 1207 +++-- 5 files changed, 3793 insertions(+), 3871 deletions(-) diff --git a/include/dice/sparse-map/boost_offset_pointer.hpp b/include/dice/sparse-map/boost_offset_pointer.hpp index 41b329d..d50c432 100644 --- a/include/dice/sparse-map/boost_offset_pointer.hpp +++ b/include/dice/sparse-map/boost_offset_pointer.hpp @@ -1,24 +1,24 @@ #ifndef DICE_SPARSE_MAP_BOOST_OFFSET_POINTER_HPP #define DICE_SPARSE_MAP_BOOST_OFFSET_POINTER_HPP -#include "dice/sparse-map/sparse_hash.hpp" //needed, so the basic template is already included +#include "dice/sparse-map/sparse_hash.hpp"//needed, so the basic template is already included #include namespace dice::sparse_map { -/* Template specialisation for a "const_cast" of a boost offset_ptr. - * @tparam PT PointedType - * @tparam DT DifferenceType - * @tparam OT OffsetType - * @tparam OA OffsetAlignment - */ -template -struct Remove_Const> { - template - static boost::interprocess::offset_ptr - remove(T const &const_iter) { - return boost::interprocess::const_pointer_cast(const_iter); - } -}; -} // namespace dice + /* Template specialisation for a "const_cast" of a boost offset_ptr. + * @tparam PT PointedType + * @tparam DT DifferenceType + * @tparam OT OffsetType + * @tparam OA OffsetAlignment + */ + template + struct Remove_Const> { + template + static boost::interprocess::offset_ptr + remove(T const &const_iter) { + return boost::interprocess::const_pointer_cast(const_iter); + } + }; +}// namespace dice::sparse_map -#endif // DICE_SPARSE_MAP_BOOST_OFFSET_POINTER_HPP +#endif// DICE_SPARSE_MAP_BOOST_OFFSET_POINTER_HPP diff --git a/include/dice/sparse-map/sparse_growth_policy.hpp b/include/dice/sparse-map/sparse_growth_policy.hpp index de69281..0642ef0 100644 --- a/include/dice/sparse-map/sparse_growth_policy.hpp +++ b/include/dice/sparse-map/sparse_growth_policy.hpp @@ -36,264 +36,264 @@ namespace dice::sparse_map::sh { -/** - * Grow the hash table by a factor of GrowthFactor keeping the bucket count to a - * power of two. It allows the table to use a mask operation instead of a modulo - * operation to map a hash to a bucket. - * - * GrowthFactor must be a power of two >= 2. - */ -template -class power_of_two_growth_policy { - public: - /** - * Called on the hash table creation and on rehash. The number of buckets for - * the table is passed in parameter. This number is a minimum, the policy may - * update this value with a higher value if needed (but not lower). - * - * If 0 is given, min_bucket_count_in_out must still be 0 after the policy - * creation and bucket_for_hash must always return 0 in this case. - */ - explicit power_of_two_growth_policy(std::size_t &min_bucket_count_in_out) { - if (min_bucket_count_in_out > max_bucket_count()) { - throw std::length_error("The hash table exceeds its maximum size."); - } - - if (min_bucket_count_in_out > 0) { - min_bucket_count_in_out = - round_up_to_power_of_two(min_bucket_count_in_out); - m_mask = min_bucket_count_in_out - 1; - } else { - m_mask = 0; - } - } - - /** - * Return the bucket [0, bucket_count()) to which the hash belongs. - * If bucket_count() is 0, it must always return 0. - */ - std::size_t bucket_for_hash(std::size_t hash) const noexcept { - return hash & m_mask; - } - - /** - * Return the number of buckets that should be used on next growth. - */ - std::size_t next_bucket_count() const { - if ((m_mask + 1) > max_bucket_count() / GrowthFactor) { - throw std::length_error("The hash table exceeds its maximum size."); - } - - return (m_mask + 1) * GrowthFactor; - } - - /** - * Return the maximum number of buckets supported by the policy. - */ - std::size_t max_bucket_count() const { - // Largest power of two. - return (std::numeric_limits::max() / 2) + 1; - } - - /** - * Reset the growth policy as if it was created with a bucket count of 0. - * After a clear, the policy must always return 0 when bucket_for_hash is - * called. - */ - void clear() noexcept { m_mask = 0; } - - private: - static std::size_t round_up_to_power_of_two(std::size_t value) { - if (is_power_of_two(value)) { - return value; - } - - if (value == 0) { - return 1; - } - - --value; - for (std::size_t i = 1; i < sizeof(std::size_t) * CHAR_BIT; i *= 2) { - value |= value >> i; - } - - return value + 1; - } - - static constexpr bool is_power_of_two(std::size_t value) { - return value != 0 && (value & (value - 1)) == 0; - } - - protected: - static_assert(is_power_of_two(GrowthFactor) && GrowthFactor >= 2, - "GrowthFactor must be a power of two >= 2."); - - std::size_t m_mask; -}; - -/** - * Grow the hash table by GrowthFactor::num / GrowthFactor::den and use a modulo - * to map a hash to a bucket. Slower but it can be useful if you want a slower - * growth. - */ -template > -class mod_growth_policy { - public: - explicit mod_growth_policy(std::size_t &min_bucket_count_in_out) { - if (min_bucket_count_in_out > max_bucket_count()) { - throw std::length_error("The hash table exceeds its maximum size."); - } - - if (min_bucket_count_in_out > 0) { - m_mod = min_bucket_count_in_out; - } else { - m_mod = 1; - } - } - - std::size_t bucket_for_hash(std::size_t hash) const noexcept { - return hash % m_mod; - } - - std::size_t next_bucket_count() const { - if (m_mod == max_bucket_count()) { - throw std::length_error("The hash table exceeds its maximum size."); - } - - const double next_bucket_count = - std::ceil(double(m_mod) * REHASH_SIZE_MULTIPLICATION_FACTOR); - if (!std::isnormal(next_bucket_count)) { - throw std::length_error("The hash table exceeds its maximum size."); - } - - if (next_bucket_count > double(max_bucket_count())) { - return max_bucket_count(); - } else { - return std::size_t(next_bucket_count); - } - } - - std::size_t max_bucket_count() const { return MAX_BUCKET_COUNT; } - - void clear() noexcept { m_mod = 1; } - - private: - static constexpr double REHASH_SIZE_MULTIPLICATION_FACTOR = - 1.0 * GrowthFactor::num / GrowthFactor::den; - static const std::size_t MAX_BUCKET_COUNT = - std::size_t(double(std::numeric_limits::max() / - REHASH_SIZE_MULTIPLICATION_FACTOR)); - - static_assert(REHASH_SIZE_MULTIPLICATION_FACTOR >= 1.1, - "Growth factor should be >= 1.1."); - - std::size_t m_mod; -}; - -/** - * Grow the hash table by using prime numbers as bucket count. Slower than - * dice::sh::power_of_two_growth_policy in general but will probably distribute - * the values around better in the buckets with a poor hash function. - * - * To allow the compiler to optimize the modulo operation, a lookup table is - * used with constant primes numbers. - * - * With a switch the code would look like: - * \code - * switch(iprime) { // iprime is the current prime of the hash table - * case 0: hash % 5ul; - * break; - * case 1: hash % 17ul; - * break; - * case 2: hash % 29ul; - * break; - * ... - * } - * \endcode - * - * Due to the constant variable in the modulo the compiler is able to optimize - * the operation by a series of multiplications, substractions and shifts. - * - * The 'hash % 5' could become something like 'hash - (hash * 0xCCCCCCCD) >> 34) - * * 5' in a 64 bits environment. - */ -class prime_growth_policy { - public: - explicit prime_growth_policy(std::size_t &min_bucket_count_in_out) { - auto it_prime = std::lower_bound(primes().begin(), primes().end(), - min_bucket_count_in_out); - if (it_prime == primes().end()) { - throw std::length_error("The hash table exceeds its maximum size."); - } - - m_iprime = - static_cast(std::distance(primes().begin(), it_prime)); - if (min_bucket_count_in_out > 0) { - min_bucket_count_in_out = *it_prime; - } else { - min_bucket_count_in_out = 0; - } - } - - std::size_t bucket_for_hash(std::size_t hash) const noexcept { - return mod_prime()[m_iprime](hash); - } - - std::size_t next_bucket_count() const { - if (m_iprime + 1 >= primes().size()) { - throw std::length_error("The hash table exceeds its maximum size."); - } - - return primes()[m_iprime + 1]; - } - - std::size_t max_bucket_count() const { return primes().back(); } - - void clear() noexcept { m_iprime = 0; } - - private: - static const std::array &primes() { - static const std::array PRIMES = { - {1ul, 5ul, 17ul, 29ul, 37ul, - 53ul, 67ul, 79ul, 97ul, 131ul, - 193ul, 257ul, 389ul, 521ul, 769ul, - 1031ul, 1543ul, 2053ul, 3079ul, 6151ul, - 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, - 393241ul, 786433ul, 1572869ul, 3145739ul, 6291469ul, - 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, - 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul}}; - - static_assert( - std::numeric_limits::max() >= PRIMES.size(), - "The type of m_iprime is not big enough."); - - return PRIMES; - } - - static const std::array &mod_prime() { - // MOD_PRIME[iprime](hash) returns hash % PRIMES[iprime]. This table allows - // for faster modulo as the compiler can optimize the modulo code better - // with a constant known at the compilation. - static const std::array MOD_PRIME = { - {&mod<0>, &mod<1>, &mod<2>, &mod<3>, &mod<4>, &mod<5>, &mod<6>, - &mod<7>, &mod<8>, &mod<9>, &mod<10>, &mod<11>, &mod<12>, &mod<13>, - &mod<14>, &mod<15>, &mod<16>, &mod<17>, &mod<18>, &mod<19>, &mod<20>, - &mod<21>, &mod<22>, &mod<23>, &mod<24>, &mod<25>, &mod<26>, &mod<27>, - &mod<28>, &mod<29>, &mod<30>, &mod<31>, &mod<32>, &mod<33>, &mod<34>, - &mod<35>, &mod<36>, &mod<37>, &mod<38>, &mod<39>}}; - - return MOD_PRIME; - } - - template - static std::size_t mod(std::size_t hash) { - return hash % primes()[IPrime]; - } - - private: - unsigned int m_iprime; -}; - -} // namespace dice + /** + * Grow the hash table by a factor of GrowthFactor keeping the bucket count to a + * power of two. It allows the table to use a mask operation instead of a modulo + * operation to map a hash to a bucket. + * + * GrowthFactor must be a power of two >= 2. + */ + template + class power_of_two_growth_policy { + public: + /** + * Called on the hash table creation and on rehash. The number of buckets for + * the table is passed in parameter. This number is a minimum, the policy may + * update this value with a higher value if needed (but not lower). + * + * If 0 is given, min_bucket_count_in_out must still be 0 after the policy + * creation and bucket_for_hash must always return 0 in this case. + */ + explicit power_of_two_growth_policy(std::size_t &min_bucket_count_in_out) { + if (min_bucket_count_in_out > max_bucket_count()) { + throw std::length_error("The hash table exceeds its maximum size."); + } + + if (min_bucket_count_in_out > 0) { + min_bucket_count_in_out = + round_up_to_power_of_two(min_bucket_count_in_out); + m_mask = min_bucket_count_in_out - 1; + } else { + m_mask = 0; + } + } + + /** + * Return the bucket [0, bucket_count()) to which the hash belongs. + * If bucket_count() is 0, it must always return 0. + */ + std::size_t bucket_for_hash(std::size_t hash) const noexcept { + return hash & m_mask; + } + + /** + * Return the number of buckets that should be used on next growth. + */ + std::size_t next_bucket_count() const { + if ((m_mask + 1) > max_bucket_count() / GrowthFactor) { + throw std::length_error("The hash table exceeds its maximum size."); + } + + return (m_mask + 1) * GrowthFactor; + } + + /** + * Return the maximum number of buckets supported by the policy. + */ + std::size_t max_bucket_count() const { + // Largest power of two. + return (std::numeric_limits::max() / 2) + 1; + } + + /** + * Reset the growth policy as if it was created with a bucket count of 0. + * After a clear, the policy must always return 0 when bucket_for_hash is + * called. + */ + void clear() noexcept { m_mask = 0; } + + private: + static std::size_t round_up_to_power_of_two(std::size_t value) { + if (is_power_of_two(value)) { + return value; + } + + if (value == 0) { + return 1; + } + + --value; + for (std::size_t i = 1; i < sizeof(std::size_t) * CHAR_BIT; i *= 2) { + value |= value >> i; + } + + return value + 1; + } + + static constexpr bool is_power_of_two(std::size_t value) { + return value != 0 && (value & (value - 1)) == 0; + } + + protected: + static_assert(is_power_of_two(GrowthFactor) && GrowthFactor >= 2, + "GrowthFactor must be a power of two >= 2."); + + std::size_t m_mask; + }; + + /** + * Grow the hash table by GrowthFactor::num / GrowthFactor::den and use a modulo + * to map a hash to a bucket. Slower but it can be useful if you want a slower + * growth. + */ + template> + class mod_growth_policy { + public: + explicit mod_growth_policy(std::size_t &min_bucket_count_in_out) { + if (min_bucket_count_in_out > max_bucket_count()) { + throw std::length_error("The hash table exceeds its maximum size."); + } + + if (min_bucket_count_in_out > 0) { + m_mod = min_bucket_count_in_out; + } else { + m_mod = 1; + } + } + + std::size_t bucket_for_hash(std::size_t hash) const noexcept { + return hash % m_mod; + } + + std::size_t next_bucket_count() const { + if (m_mod == max_bucket_count()) { + throw std::length_error("The hash table exceeds its maximum size."); + } + + const double next_bucket_count = + std::ceil(double(m_mod) * REHASH_SIZE_MULTIPLICATION_FACTOR); + if (!std::isnormal(next_bucket_count)) { + throw std::length_error("The hash table exceeds its maximum size."); + } + + if (next_bucket_count > double(max_bucket_count())) { + return max_bucket_count(); + } else { + return std::size_t(next_bucket_count); + } + } + + std::size_t max_bucket_count() const { return MAX_BUCKET_COUNT; } + + void clear() noexcept { m_mod = 1; } + + private: + static constexpr double REHASH_SIZE_MULTIPLICATION_FACTOR = + 1.0 * GrowthFactor::num / GrowthFactor::den; + static const std::size_t MAX_BUCKET_COUNT = + std::size_t(double(std::numeric_limits::max() / + REHASH_SIZE_MULTIPLICATION_FACTOR)); + + static_assert(REHASH_SIZE_MULTIPLICATION_FACTOR >= 1.1, + "Growth factor should be >= 1.1."); + + std::size_t m_mod; + }; + + /** + * Grow the hash table by using prime numbers as bucket count. Slower than + * dice::sh::power_of_two_growth_policy in general but will probably distribute + * the values around better in the buckets with a poor hash function. + * + * To allow the compiler to optimize the modulo operation, a lookup table is + * used with constant primes numbers. + * + * With a switch the code would look like: + * \code + * switch(iprime) { // iprime is the current prime of the hash table + * case 0: hash % 5ul; + * break; + * case 1: hash % 17ul; + * break; + * case 2: hash % 29ul; + * break; + * ... + * } + * \endcode + * + * Due to the constant variable in the modulo the compiler is able to optimize + * the operation by a series of multiplications, substractions and shifts. + * + * The 'hash % 5' could become something like 'hash - (hash * 0xCCCCCCCD) >> 34) + * * 5' in a 64 bits environment. + */ + class prime_growth_policy { + public: + explicit prime_growth_policy(std::size_t &min_bucket_count_in_out) { + auto it_prime = std::lower_bound(primes().begin(), primes().end(), + min_bucket_count_in_out); + if (it_prime == primes().end()) { + throw std::length_error("The hash table exceeds its maximum size."); + } + + m_iprime = + static_cast(std::distance(primes().begin(), it_prime)); + if (min_bucket_count_in_out > 0) { + min_bucket_count_in_out = *it_prime; + } else { + min_bucket_count_in_out = 0; + } + } + + std::size_t bucket_for_hash(std::size_t hash) const noexcept { + return mod_prime()[m_iprime](hash); + } + + std::size_t next_bucket_count() const { + if (m_iprime + 1 >= primes().size()) { + throw std::length_error("The hash table exceeds its maximum size."); + } + + return primes()[m_iprime + 1]; + } + + std::size_t max_bucket_count() const { return primes().back(); } + + void clear() noexcept { m_iprime = 0; } + + private: + static const std::array &primes() { + static const std::array PRIMES = { + {1ul, 5ul, 17ul, 29ul, 37ul, + 53ul, 67ul, 79ul, 97ul, 131ul, + 193ul, 257ul, 389ul, 521ul, 769ul, + 1031ul, 1543ul, 2053ul, 3079ul, 6151ul, + 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, + 393241ul, 786433ul, 1572869ul, 3145739ul, 6291469ul, + 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, + 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul}}; + + static_assert( + std::numeric_limits::max() >= PRIMES.size(), + "The type of m_iprime is not big enough."); + + return PRIMES; + } + + static const std::array &mod_prime() { + // MOD_PRIME[iprime](hash) returns hash % PRIMES[iprime]. This table allows + // for faster modulo as the compiler can optimize the modulo code better + // with a constant known at the compilation. + static const std::array MOD_PRIME = { + {&mod<0>, &mod<1>, &mod<2>, &mod<3>, &mod<4>, &mod<5>, &mod<6>, + &mod<7>, &mod<8>, &mod<9>, &mod<10>, &mod<11>, &mod<12>, &mod<13>, + &mod<14>, &mod<15>, &mod<16>, &mod<17>, &mod<18>, &mod<19>, &mod<20>, + &mod<21>, &mod<22>, &mod<23>, &mod<24>, &mod<25>, &mod<26>, &mod<27>, + &mod<28>, &mod<29>, &mod<30>, &mod<31>, &mod<32>, &mod<33>, &mod<34>, + &mod<35>, &mod<36>, &mod<37>, &mod<38>, &mod<39>}}; + + return MOD_PRIME; + } + + template + static std::size_t mod(std::size_t hash) { + return hash % primes()[IPrime]; + } + + private: + unsigned int m_iprime; + }; + +}// namespace dice::sparse_map::sh #endif diff --git a/include/dice/sparse-map/sparse_hash.hpp b/include/dice/sparse-map/sparse_hash.hpp index b966305..eea3c3a 100644 --- a/include/dice/sparse-map/sparse_hash.hpp +++ b/include/dice/sparse-map/sparse_hash.hpp @@ -39,15 +39,15 @@ #include #include -#include "dice/sparse-map/sparse_growth_policy.hpp" #include "boost/container/vector.hpp" +#include "dice/sparse-map/sparse_growth_policy.hpp" #ifdef __INTEL_COMPILER -#include // For _popcnt32 and _popcnt64 +#include // For _popcnt32 and _popcnt64 #endif #ifdef _MSC_VER -#include // For __cpuid, __popcnt and __popcnt64 +#include // For __cpuid, __popcnt and __popcnt64 #endif #ifdef TSL_DEBUG @@ -58,2250 +58,2226 @@ namespace dice::sparse_map { -namespace sh { -enum class probing { linear, quadratic }; - -enum class exception_safety { basic, strong }; - -enum class sparsity { high, medium, low }; -} // namespace sh - -namespace detail_popcount { -/** - * Define the popcount(ll) methods and pick-up the best depending on the - * compiler. - */ - -// From Wikipedia: https://en.wikipedia.org/wiki/Hamming_weight -inline int fallback_popcountll(unsigned long long int x) { - static_assert( - sizeof(unsigned long long int) == sizeof(std::uint64_t), - "sizeof(unsigned long long int) must be equal to sizeof(std::uint64_t). " - "Open a feature request if you need support for a platform where it " - "isn't the case."); - - const std::uint64_t m1 = 0x5555555555555555ull; - const std::uint64_t m2 = 0x3333333333333333ull; - const std::uint64_t m4 = 0x0f0f0f0f0f0f0f0full; - const std::uint64_t h01 = 0x0101010101010101ull; - - x -= (x >> 1ull) & m1; - x = (x & m2) + ((x >> 2ull) & m2); - x = (x + (x >> 4ull)) & m4; - return static_cast((x * h01) >> (64ull - 8ull)); -} - -inline int fallback_popcount(unsigned int x) { - static_assert(sizeof(unsigned int) == sizeof(std::uint32_t) || - sizeof(unsigned int) == sizeof(std::uint64_t), - "sizeof(unsigned int) must be equal to sizeof(std::uint32_t) " - "or sizeof(std::uint64_t). " - "Open a feature request if you need support for a platform " - "where it isn't the case."); - - if (sizeof(unsigned int) == sizeof(std::uint32_t)) { - const std::uint32_t m1 = 0x55555555; - const std::uint32_t m2 = 0x33333333; - const std::uint32_t m4 = 0x0f0f0f0f; - const std::uint32_t h01 = 0x01010101; - - x -= (x >> 1) & m1; - x = (x & m2) + ((x >> 2) & m2); - x = (x + (x >> 4)) & m4; - return static_cast((x * h01) >> (32 - 8)); - } else { - return fallback_popcountll(x); - } -} + namespace sh { + enum class probing { linear, + quadratic }; + + enum class exception_safety { basic, + strong }; + + enum class sparsity { high, + medium, + low }; + }// namespace sh + + namespace detail_popcount { + /** + * Define the popcount(ll) methods and pick-up the best depending on the + * compiler. + */ + + // From Wikipedia: https://en.wikipedia.org/wiki/Hamming_weight + inline int fallback_popcountll(unsigned long long int x) { + static_assert( + sizeof(unsigned long long int) == sizeof(std::uint64_t), + "sizeof(unsigned long long int) must be equal to sizeof(std::uint64_t). " + "Open a feature request if you need support for a platform where it " + "isn't the case."); + + const std::uint64_t m1 = 0x5555555555555555ull; + const std::uint64_t m2 = 0x3333333333333333ull; + const std::uint64_t m4 = 0x0f0f0f0f0f0f0f0full; + const std::uint64_t h01 = 0x0101010101010101ull; + + x -= (x >> 1ull) & m1; + x = (x & m2) + ((x >> 2ull) & m2); + x = (x + (x >> 4ull)) & m4; + return static_cast((x * h01) >> (64ull - 8ull)); + } + + inline int fallback_popcount(unsigned int x) { + static_assert(sizeof(unsigned int) == sizeof(std::uint32_t) || + sizeof(unsigned int) == sizeof(std::uint64_t), + "sizeof(unsigned int) must be equal to sizeof(std::uint32_t) " + "or sizeof(std::uint64_t). " + "Open a feature request if you need support for a platform " + "where it isn't the case."); + + if (sizeof(unsigned int) == sizeof(std::uint32_t)) { + const std::uint32_t m1 = 0x55555555; + const std::uint32_t m2 = 0x33333333; + const std::uint32_t m4 = 0x0f0f0f0f; + const std::uint32_t h01 = 0x01010101; + + x -= (x >> 1) & m1; + x = (x & m2) + ((x >> 2) & m2); + x = (x + (x >> 4)) & m4; + return static_cast((x * h01) >> (32 - 8)); + } else { + return fallback_popcountll(x); + } + } #if defined(__clang__) || defined(__GNUC__) -inline int popcountll(unsigned long long int value) { - return __builtin_popcountll(value); -} + inline int popcountll(unsigned long long int value) { + return __builtin_popcountll(value); + } -inline int popcount(unsigned int value) { return __builtin_popcount(value); } + inline int popcount(unsigned int value) { return __builtin_popcount(value); } #elif defined(_MSC_VER) -/** + /** * We need to check for popcount support at runtime on Windows with __cpuid * See https://msdn.microsoft.com/en-us/library/bb385231.aspx */ -inline bool has_popcount_support() { - int cpu_infos[4]; - __cpuid(cpu_infos, 1); - return (cpu_infos[2] & (1 << 23)) != 0; -} + inline bool has_popcount_support() { + int cpu_infos[4]; + __cpuid(cpu_infos, 1); + return (cpu_infos[2] & (1 << 23)) != 0; + } -inline int popcountll(unsigned long long int value) { + inline int popcountll(unsigned long long int value) { #ifdef _WIN64 - static_assert( - sizeof(unsigned long long int) == sizeof(std::int64_t), - "sizeof(unsigned long long int) must be equal to sizeof(std::int64_t). "); - - static const bool has_popcount = has_popcount_support(); - return has_popcount - ? static_cast(__popcnt64(static_cast(value))) - : fallback_popcountll(value); + static_assert( + sizeof(unsigned long long int) == sizeof(std::int64_t), + "sizeof(unsigned long long int) must be equal to sizeof(std::int64_t). "); + + static const bool has_popcount = has_popcount_support(); + return has_popcount + ? static_cast(__popcnt64(static_cast(value))) + : fallback_popcountll(value); #else - return fallback_popcountll(value); + return fallback_popcountll(value); #endif -} + } -inline int popcount(unsigned int value) { - static_assert(sizeof(unsigned int) == sizeof(std::int32_t), - "sizeof(unsigned int) must be equal to sizeof(std::int32_t). "); + inline int popcount(unsigned int value) { + static_assert(sizeof(unsigned int) == sizeof(std::int32_t), + "sizeof(unsigned int) must be equal to sizeof(std::int32_t). "); - static const bool has_popcount = has_popcount_support(); - return has_popcount - ? static_cast(__popcnt(static_cast(value))) - : fallback_popcount(value); -} + static const bool has_popcount = has_popcount_support(); + return has_popcount + ? static_cast(__popcnt(static_cast(value))) + : fallback_popcount(value); + } #elif defined(__INTEL_COMPILER) -inline int popcountll(unsigned long long int value) { - static_assert(sizeof(unsigned long long int) == sizeof(__int64), ""); - return _popcnt64(static_cast<__int64>(value)); -} + inline int popcountll(unsigned long long int value) { + static_assert(sizeof(unsigned long long int) == sizeof(__int64), ""); + return _popcnt64(static_cast<__int64>(value)); + } -inline int popcount(unsigned int value) { - return _popcnt32(static_cast(value)); -} + inline int popcount(unsigned int value) { + return _popcnt32(static_cast(value)); + } #else -inline int popcountll(unsigned long long int x) { - return fallback_popcountll(x); -} + inline int popcountll(unsigned long long int x) { + return fallback_popcountll(x); + } -inline int popcount(unsigned int x) { return fallback_popcount(x); } + inline int popcount(unsigned int x) { return fallback_popcount(x); } #endif -} // namespace detail_popcount - - -/* Replacement for const_cast in sparse_array. - * Can be overloaded for specific fancy pointers - * (see: include/dice/boost_offset_pointer.h). - * This is just a workaround. - * The clean way would be to change the implementation to stop using const_cast. - */ - template - struct Remove_Const { - template - static T remove(V iter) { - return const_cast(iter); - } - }; - -namespace detail_sparse_hash { - /* to_address can convert any raw or fancy pointer into a raw pointer. - * It is needed for the allocator construct and destroy calls. - * This specific implementation is based on boost 1.71.0. - */ -#if __cplusplus >= 201400L // with 14-features - template - T *to_address(T *v) noexcept { return v; } - - namespace fancy_ptr_detail { - template - inline T *ptr_address(T *v, int) noexcept { return v; } - - template - inline auto ptr_address(const T &v, int) noexcept - -> decltype(std::pointer_traits::to_address(v)) { - return std::pointer_traits::to_address(v); - } - template - inline auto ptr_address(const T &v, long) noexcept { - return fancy_ptr_detail::ptr_address(v.operator->(), 0); - } - } // namespace detail - - template inline auto to_address(const T &v) noexcept { - return fancy_ptr_detail::ptr_address(v, 0); - } -#else // without 14-features - template - inline T *to_address(T *v) noexcept { return v; } - - template - inline typename std::pointer_traits::element_type * to_address(const T &v) noexcept { - return detail_sparse_hash::to_address(v.operator->()); - } + }// namespace detail_popcount + + + /* Replacement for const_cast in sparse_array. + * Can be overloaded for specific fancy pointers + * (see: include/dice/boost_offset_pointer.h). + * This is just a workaround. + * The clean way would be to change the implementation to stop using const_cast. + */ + template + struct Remove_Const { + template + static T remove(V iter) { + return const_cast(iter); + } + }; + + namespace detail_sparse_hash { + /* to_address can convert any raw or fancy pointer into a raw pointer. + * It is needed for the allocator construct and destroy calls. + * This specific implementation is based on boost 1.71.0. + */ +#if __cplusplus >= 201400L// with 14-features + template + T *to_address(T *v) noexcept { return v; } + + namespace fancy_ptr_detail { + template + inline T *ptr_address(T *v, int) noexcept { return v; } + + template + inline auto ptr_address(const T &v, int) noexcept + -> decltype(std::pointer_traits::to_address(v)) { + return std::pointer_traits::to_address(v); + } + template + inline auto ptr_address(const T &v, long) noexcept { + return fancy_ptr_detail::ptr_address(v.operator->(), 0); + } + }// namespace fancy_ptr_detail + + template + inline auto to_address(const T &v) noexcept { + return fancy_ptr_detail::ptr_address(v, 0); + } +#else// without 14-features + template + inline T *to_address(T *v) noexcept { return v; } + + template + inline typename std::pointer_traits::element_type *to_address(const T &v) noexcept { + return detail_sparse_hash::to_address(v.operator->()); + } #endif -template -struct make_void { - using type = void; -}; - -template -struct has_is_transparent : std::false_type {}; - -template -struct has_is_transparent::type> - : std::true_type {}; - -template -struct is_power_of_two_policy : std::false_type {}; - -template -struct is_power_of_two_policy> - : std::true_type {}; - -inline constexpr bool is_power_of_two(std::size_t value) { - return value != 0 && (value & (value - 1)) == 0; -} - -inline std::size_t round_up_to_power_of_two(std::size_t value) { - if (is_power_of_two(value)) { - return value; - } - - if (value == 0) { - return 1; - } - - --value; - for (std::size_t i = 1; i < sizeof(std::size_t) * CHAR_BIT; i *= 2) { - value |= value >> i; - } - - return value + 1; -} - -template -static T numeric_cast(U value, - const char *error_message = "numeric_cast() failed.") { - T ret = static_cast(value); - if (static_cast(ret) != value) { - throw std::runtime_error(error_message); - } - - const bool is_same_signedness = - (std::is_unsigned::value && std::is_unsigned::value) || - (std::is_signed::value && std::is_signed::value); - if (!is_same_signedness && (ret < T{}) != (value < U{})) { - throw std::runtime_error(error_message); - } - - return ret; -} - -/** - * Fixed size type used to represent size_type values on serialization. Need to - * be big enough to represent a std::size_t on 32 and 64 bits platforms, and - * must be the same size on both platforms. - */ -using slz_size_type = std::uint64_t; -static_assert(std::numeric_limits::max() >= - std::numeric_limits::max(), - "slz_size_type must be >= std::size_t"); - -template -static T deserialize_value(Deserializer &deserializer) { - // MSVC < 2017 is not conformant, circumvent the problem by removing the - // template keyword + template + struct make_void { + using type = void; + }; + + template + struct has_is_transparent : std::false_type {}; + + template + struct has_is_transparent::type> + : std::true_type {}; + + template + struct is_power_of_two_policy : std::false_type {}; + + template + struct is_power_of_two_policy> + : std::true_type {}; + + inline constexpr bool is_power_of_two(std::size_t value) { + return value != 0 && (value & (value - 1)) == 0; + } + + inline std::size_t round_up_to_power_of_two(std::size_t value) { + if (is_power_of_two(value)) { + return value; + } + + if (value == 0) { + return 1; + } + + --value; + for (std::size_t i = 1; i < sizeof(std::size_t) * CHAR_BIT; i *= 2) { + value |= value >> i; + } + + return value + 1; + } + + template + static T numeric_cast(U value, + const char *error_message = "numeric_cast() failed.") { + T ret = static_cast(value); + if (static_cast(ret) != value) { + throw std::runtime_error(error_message); + } + + const bool is_same_signedness = + (std::is_unsigned::value && std::is_unsigned::value) || + (std::is_signed::value && std::is_signed::value); + if (!is_same_signedness && (ret < T{}) != (value < U{})) { + throw std::runtime_error(error_message); + } + + return ret; + } + + /** + * Fixed size type used to represent size_type values on serialization. Need to + * be big enough to represent a std::size_t on 32 and 64 bits platforms, and + * must be the same size on both platforms. + */ + using slz_size_type = std::uint64_t; + static_assert(std::numeric_limits::max() >= + std::numeric_limits::max(), + "slz_size_type must be >= std::size_t"); + + template + static T deserialize_value(Deserializer &deserializer) { + // MSVC < 2017 is not conformant, circumvent the problem by removing the + // template keyword #if defined(_MSC_VER) && _MSC_VER < 1910 - return deserializer.Deserializer::operator()(); + return deserializer.Deserializer::operator()(); #else - return deserializer.Deserializer::template operator()(); + return deserializer.Deserializer::template operator()(); #endif -} - -/** - * WARNING: the sparse_array class doesn't free the ressources allocated through - * the allocator passed in parameter in each method. You have to manually call - * `clear(Allocator&)` when you don't need a sparse_array object anymore. - * - * The reason is that the sparse_array doesn't store the allocator to avoid - * wasting space in each sparse_array when the allocator has a size > 0. It only - * allocates/deallocates objects with the allocator that is passed in parameter. - * - * - * - * Index denotes a value between [0, BITMAP_NB_BITS), it is an index similar to - * std::vector. Offset denotes the real position in `m_values` corresponding to - * an index. - * - * We are using raw pointers instead of std::vector to avoid loosing - * 2*sizeof(size_t) bytes to store the capacity and size of the vector in each - * sparse_array. We know we can only store up to BITMAP_NB_BITS elements in the - * array, we don't need such big types. - * - * - * T must be nothrow move constructible and/or copy constructible. - * Behaviour is undefined if the destructor of T throws an exception. - * - * See https://smerity.com/articles/2015/google_sparsehash.html for details on - * the idea behinds the implementation. - * - * TODO Check to use std::realloc and std::memmove when possible - */ -template -class sparse_array { - public: - using value_type = T; - using size_type = std::uint_least8_t; - using allocator_type = Allocator; - using allocator_traits = std::allocator_traits; - using pointer = typename allocator_traits::pointer; - using const_pointer = typename allocator_traits::const_pointer; - using iterator = pointer; - using const_iterator = const_pointer; - - private: - static const size_type CAPACITY_GROWTH_STEP = - (Sparsity == dice::sparse_map::sh::sparsity::high) ? 2 - : (Sparsity == dice::sparse_map::sh::sparsity::medium) - ? 4 - : 8; // (Sparsity == dice::sh::sparsity::low) - - /** - * Bitmap size configuration. - * Use 32 bits for the bitmap on 32-bits or less environnement as popcount on - * 64 bits numbers is slow on these environnement. Use 64 bits bitmap - * otherwise. - */ + } + + /** + * WARNING: the sparse_array class doesn't free the ressources allocated through + * the allocator passed in parameter in each method. You have to manually call + * `clear(Allocator&)` when you don't need a sparse_array object anymore. + * + * The reason is that the sparse_array doesn't store the allocator to avoid + * wasting space in each sparse_array when the allocator has a size > 0. It only + * allocates/deallocates objects with the allocator that is passed in parameter. + * + * + * + * Index denotes a value between [0, BITMAP_NB_BITS), it is an index similar to + * std::vector. Offset denotes the real position in `m_values` corresponding to + * an index. + * + * We are using raw pointers instead of std::vector to avoid loosing + * 2*sizeof(size_t) bytes to store the capacity and size of the vector in each + * sparse_array. We know we can only store up to BITMAP_NB_BITS elements in the + * array, we don't need such big types. + * + * + * T must be nothrow move constructible and/or copy constructible. + * Behaviour is undefined if the destructor of T throws an exception. + * + * See https://smerity.com/articles/2015/google_sparsehash.html for details on + * the idea behinds the implementation. + * + * TODO Check to use std::realloc and std::memmove when possible + */ + template + class sparse_array { + public: + using value_type = T; + using size_type = std::uint_least8_t; + using allocator_type = Allocator; + using allocator_traits = std::allocator_traits; + using pointer = typename allocator_traits::pointer; + using const_pointer = typename allocator_traits::const_pointer; + using iterator = pointer; + using const_iterator = const_pointer; + + private: + static const size_type CAPACITY_GROWTH_STEP = + (Sparsity == dice::sparse_map::sh::sparsity::high) ? 2 + : (Sparsity == dice::sparse_map::sh::sparsity::medium) + ? 4 + : 8;// (Sparsity == dice::sh::sparsity::low) + + /** + * Bitmap size configuration. + * Use 32 bits for the bitmap on 32-bits or less environnement as popcount on + * 64 bits numbers is slow on these environnement. Use 64 bits bitmap + * otherwise. + */ #if SIZE_MAX <= UINT32_MAX - using bitmap_type = std::uint_least32_t; - static const std::size_t BITMAP_NB_BITS = 32; - static const std::size_t BUCKET_SHIFT = 5; + using bitmap_type = std::uint_least32_t; + static const std::size_t BITMAP_NB_BITS = 32; + static const std::size_t BUCKET_SHIFT = 5; #else - using bitmap_type = std::uint_least64_t; - static const std::size_t BITMAP_NB_BITS = 64; - static const std::size_t BUCKET_SHIFT = 6; + using bitmap_type = std::uint_least64_t; + static const std::size_t BITMAP_NB_BITS = 64; + static const std::size_t BUCKET_SHIFT = 6; #endif - static const std::size_t BUCKET_MASK = BITMAP_NB_BITS - 1; - - static_assert(is_power_of_two(BITMAP_NB_BITS), - "BITMAP_NB_BITS must be a power of two."); - static_assert(std::numeric_limits::digits >= BITMAP_NB_BITS, - "bitmap_type must be able to hold at least BITMAP_NB_BITS."); - static_assert((std::size_t(1) << BUCKET_SHIFT) == BITMAP_NB_BITS, - "(1 << BUCKET_SHIFT) must be equal to BITMAP_NB_BITS."); - static_assert(std::numeric_limits::max() >= BITMAP_NB_BITS, - "size_type must be big enough to hold BITMAP_NB_BITS."); - static_assert(std::is_unsigned::value, - "bitmap_type must be unsigned."); - static_assert((std::numeric_limits::max() & BUCKET_MASK) == - BITMAP_NB_BITS - 1, - ""); - - public: - /** - * Map an ibucket [0, bucket_count) in the hash table to a sparse_ibucket - * (a sparse_array holds multiple buckets, so there is less sparse_array than - * bucket_count). - * - * The bucket ibucket is in - * m_sparse_buckets[sparse_ibucket(ibucket)][index_in_sparse_bucket(ibucket)] - * instead of something like m_buckets[ibucket] in a classical hash table. - */ - static std::size_t sparse_ibucket(std::size_t ibucket) { - return ibucket >> BUCKET_SHIFT; - } - - /** - * Map an ibucket [0, bucket_count) in the hash table to an index in the - * sparse_array which corresponds to the bucket. - * - * The bucket ibucket is in - * m_sparse_buckets[sparse_ibucket(ibucket)][index_in_sparse_bucket(ibucket)] - * instead of something like m_buckets[ibucket] in a classical hash table. - */ - static typename sparse_array::size_type index_in_sparse_bucket( - std::size_t ibucket) { - return static_cast( - ibucket & sparse_array::BUCKET_MASK); - } - - static std::size_t nb_sparse_buckets(std::size_t bucket_count) noexcept { - if (bucket_count == 0) { - return 0; - } - - return std::max( - 1, sparse_ibucket(dice::sparse_map::detail_sparse_hash::round_up_to_power_of_two( - bucket_count))); - } - - public: - sparse_array() noexcept - : m_values(nullptr), - m_bitmap_vals(0), - m_bitmap_deleted_vals(0), - m_nb_elements(0), - m_capacity(0), - m_last_array(false) {} - - //needed for "is_constructible" with no parameters - sparse_array(std::allocator_arg_t, Allocator const&) noexcept : sparse_array() {} - - explicit sparse_array(bool last_bucket) noexcept - : m_values(nullptr), - m_bitmap_vals(0), - m_bitmap_deleted_vals(0), - m_nb_elements(0), - m_capacity(0), - m_last_array(last_bucket) {} - - //const Allocator needed for MoveInsertable requirement - sparse_array(size_type capacity, Allocator const &const_alloc) - : m_values(nullptr), - m_bitmap_vals(0), - m_bitmap_deleted_vals(0), - m_nb_elements(0), - m_capacity(capacity), - m_last_array(false) { - if (m_capacity > 0) { - auto alloc = const_cast(const_alloc); - m_values = alloc.allocate(m_capacity); - tsl_sh_assert(m_values != - nullptr); // allocate should throw if there is a failure - } - } - - //const Allocator needed for MoveInsertable requirement - sparse_array(const sparse_array &other, Allocator const &const_alloc) - : m_values(nullptr), - m_bitmap_vals(other.m_bitmap_vals), - m_bitmap_deleted_vals(other.m_bitmap_deleted_vals), - m_nb_elements(0), - m_capacity(other.m_capacity), - m_last_array(other.m_last_array) { - tsl_sh_assert(other.m_capacity >= other.m_nb_elements); - if (m_capacity == 0) { - return; - } - - auto alloc = const_cast(const_alloc); - m_values = alloc.allocate(m_capacity); - tsl_sh_assert(m_values != - nullptr); // allocate should throw if there is a failure - try { - for (size_type i = 0; i < other.m_nb_elements; i++) { - construct_value(alloc, m_values + i, other.m_values[i]); - m_nb_elements++; - } - } catch (...) { - clear(alloc); - throw; - } - } - - sparse_array(sparse_array &&other) noexcept - : m_values(other.m_values), - m_bitmap_vals(other.m_bitmap_vals), - m_bitmap_deleted_vals(other.m_bitmap_deleted_vals), - m_nb_elements(other.m_nb_elements), - m_capacity(other.m_capacity), - m_last_array(other.m_last_array) { - other.m_values = nullptr; - other.m_bitmap_vals = 0; - other.m_bitmap_deleted_vals = 0; - other.m_nb_elements = 0; - other.m_capacity = 0; - } - - //const Allocator needed for MoveInsertable requirement - sparse_array(sparse_array &&other, Allocator const &const_alloc) - : m_values(nullptr), - m_bitmap_vals(other.m_bitmap_vals), - m_bitmap_deleted_vals(other.m_bitmap_deleted_vals), - m_nb_elements(0), - m_capacity(other.m_capacity), - m_last_array(other.m_last_array) { - tsl_sh_assert(other.m_capacity >= other.m_nb_elements); - if (m_capacity == 0) { - return; - } - - auto alloc = const_cast(const_alloc); - m_values = alloc.allocate(m_capacity); - tsl_sh_assert(m_values != - nullptr); // allocate should throw if there is a failure - try { - for (size_type i = 0; i < other.m_nb_elements; i++) { - construct_value(alloc, m_values + i, std::move(other.m_values[i])); - m_nb_elements++; - } - } catch (...) { - clear(alloc); - throw; - } - } - - sparse_array &operator=(const sparse_array &) = delete; - sparse_array &operator=(sparse_array &&other) noexcept{ - this->m_values = other.m_values; - this->m_bitmap_vals = other.m_bitmap_vals; - this->m_bitmap_deleted_vals = other.m_bitmap_deleted_vals; - this->m_nb_elements = other.m_nb_elements; - this->m_capacity = other.m_capacity; - other.m_values = nullptr; - other.m_bitmap_vals = 0; - other.m_bitmap_deleted_vals = 0; - other.m_nb_elements = 0; - other.m_capacity = 0; - return *this; - } - - - - ~sparse_array() noexcept { - // The code that manages the sparse_array must have called clear before - // destruction. See documentation of sparse_array for more details. - tsl_sh_assert(m_capacity == 0 && m_nb_elements == 0 && m_values == nullptr); - } - - iterator begin() noexcept { return m_values; } - iterator end() noexcept { return m_values + m_nb_elements; } - const_iterator begin() const noexcept { return cbegin(); } - const_iterator end() const noexcept { return cend(); } - const_iterator cbegin() const noexcept { return m_values; } - const_iterator cend() const noexcept { return m_values + m_nb_elements; } - - bool empty() const noexcept { return m_nb_elements == 0; } - - size_type size() const noexcept { return m_nb_elements; } - - void clear(allocator_type &alloc) noexcept { - destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); - - m_values = nullptr; - m_bitmap_vals = 0; - m_bitmap_deleted_vals = 0; - m_nb_elements = 0; - m_capacity = 0; - } - - bool last() const noexcept { return m_last_array; } - - void set_as_last() noexcept { m_last_array = true; } - - bool has_value(size_type index) const noexcept { - tsl_sh_assert(index < BITMAP_NB_BITS); - return (m_bitmap_vals & (bitmap_type(1) << index)) != 0; - } - - bool has_deleted_value(size_type index) const noexcept { - tsl_sh_assert(index < BITMAP_NB_BITS); - return (m_bitmap_deleted_vals & (bitmap_type(1) << index)) != 0; - } - - iterator value(size_type index) noexcept { - tsl_sh_assert(has_value(index)); - return m_values + index_to_offset(index); - } - - const_iterator value(size_type index) const noexcept { - tsl_sh_assert(has_value(index)); - return m_values + index_to_offset(index); - } - - /** - * Return iterator to set value. - */ - template - iterator set(allocator_type &alloc, size_type index, Args &&...value_args) { - tsl_sh_assert(!has_value(index)); - - const size_type offset = index_to_offset(index); - insert_at_offset(alloc, offset, std::forward(value_args)...); - - m_bitmap_vals = (m_bitmap_vals | (bitmap_type(1) << index)); - m_bitmap_deleted_vals = - (m_bitmap_deleted_vals & ~(bitmap_type(1) << index)); - - m_nb_elements++; - - tsl_sh_assert(has_value(index)); - tsl_sh_assert(!has_deleted_value(index)); - - return m_values + offset; - } - - iterator erase(allocator_type &alloc, iterator position) { - const size_type offset = - static_cast(std::distance(begin(), position)); - return erase(alloc, position, offset_to_index(offset)); - } - - // Return the next value or end if no next value - iterator erase(allocator_type &alloc, iterator position, size_type index) { - tsl_sh_assert(has_value(index)); - tsl_sh_assert(!has_deleted_value(index)); - - const size_type offset = - static_cast(std::distance(begin(), position)); - erase_at_offset(alloc, offset); - - m_bitmap_vals = (m_bitmap_vals & ~(bitmap_type(1) << index)); - m_bitmap_deleted_vals = (m_bitmap_deleted_vals | (bitmap_type(1) << index)); - - m_nb_elements--; - - tsl_sh_assert(!has_value(index)); - tsl_sh_assert(has_deleted_value(index)); - - return m_values + offset; - } - - void swap(sparse_array &other) { - using std::swap; - - swap(m_values, other.m_values); - swap(m_bitmap_vals, other.m_bitmap_vals); - swap(m_bitmap_deleted_vals, other.m_bitmap_deleted_vals); - swap(m_nb_elements, other.m_nb_elements); - swap(m_capacity, other.m_capacity); - swap(m_last_array, other.m_last_array); - } - - static iterator mutable_iterator(const_iterator pos) { - return ::dice::sparse_map::Remove_Const::template remove(pos); - } - - template - void serialize(Serializer &serializer) const { - const slz_size_type sparse_bucket_size = m_nb_elements; - serializer(sparse_bucket_size); - - const slz_size_type bitmap_vals = m_bitmap_vals; - serializer(bitmap_vals); - - const slz_size_type bitmap_deleted_vals = m_bitmap_deleted_vals; - serializer(bitmap_deleted_vals); - - for (const value_type &value : *this) { - serializer(value); - } - } - - template - static sparse_array deserialize_hash_compatible(Deserializer &deserializer, - Allocator &alloc) { - const slz_size_type sparse_bucket_size = - deserialize_value(deserializer); - const slz_size_type bitmap_vals = - deserialize_value(deserializer); - const slz_size_type bitmap_deleted_vals = - deserialize_value(deserializer); - - if (sparse_bucket_size > BITMAP_NB_BITS) { - throw std::runtime_error( - "Deserialized sparse_bucket_size is too big for the platform. " - "Maximum should be BITMAP_NB_BITS."); - } - - sparse_array sarray; - if (sparse_bucket_size == 0) { - return sarray; - } - - sarray.m_bitmap_vals = numeric_cast( - bitmap_vals, "Deserialized bitmap_vals is too big."); - sarray.m_bitmap_deleted_vals = numeric_cast( - bitmap_deleted_vals, "Deserialized bitmap_deleted_vals is too big."); - - sarray.m_capacity = numeric_cast( - sparse_bucket_size, "Deserialized sparse_bucket_size is too big."); - sarray.m_values = alloc.allocate(sarray.m_capacity); - - try { - for (size_type ivalue = 0; ivalue < sarray.m_capacity; ivalue++) { - construct_value(alloc, sarray.m_values + ivalue, - deserialize_value(deserializer)); - sarray.m_nb_elements++; - } - } catch (...) { - sarray.clear(alloc); - throw; - } - - return sarray; - } - - /** - * Deserialize the values of the bucket and insert them all in sparse_hash - * through sparse_hash.insert(...). - */ - template - static void deserialize_values_into_sparse_hash(Deserializer &deserializer, - SparseHash &sparse_hash) { - const slz_size_type sparse_bucket_size = - deserialize_value(deserializer); - - const slz_size_type bitmap_vals = - deserialize_value(deserializer); - static_cast(bitmap_vals); // Ignore, not needed - - const slz_size_type bitmap_deleted_vals = - deserialize_value(deserializer); - static_cast(bitmap_deleted_vals); // Ignore, not needed - - for (slz_size_type ivalue = 0; ivalue < sparse_bucket_size; ivalue++) { - sparse_hash.insert(deserialize_value(deserializer)); - } - } - - private: - template - static void construct_value(allocator_type &alloc, pointer value, - Args &&... value_args) { - std::allocator_traits::construct( - alloc, detail_sparse_hash::to_address(value), std::forward(value_args)...); - } - - static void destroy_value(allocator_type &alloc, pointer value) noexcept { - std::allocator_traits::destroy(alloc, detail_sparse_hash::to_address(value)); - } - - static void destroy_and_deallocate_values( - allocator_type &alloc, pointer values, size_type nb_values, - size_type capacity_values) noexcept { - for (size_type i = 0; i < nb_values; i++) { - destroy_value(alloc, values + i); - } - - alloc.deallocate(values, capacity_values); - } - - static size_type popcount(bitmap_type val) noexcept { - if (sizeof(bitmap_type) <= sizeof(unsigned int)) { - return static_cast( - dice::sparse_map::detail_popcount::popcount(static_cast(val))); - } else { - return static_cast(dice::sparse_map::detail_popcount::popcountll(val)); - } - } - - size_type index_to_offset(size_type index) const noexcept { - tsl_sh_assert(index < BITMAP_NB_BITS); - return popcount(m_bitmap_vals & - ((bitmap_type(1) << index) - bitmap_type(1))); - } - - // TODO optimize - size_type offset_to_index(size_type offset) const noexcept { - tsl_sh_assert(offset < m_nb_elements); - - bitmap_type bitmap_vals = m_bitmap_vals; - size_type index = 0; - size_type nb_ones = 0; - - while (bitmap_vals != 0) { - if ((bitmap_vals & 0x1) == 1) { - if (nb_ones == offset) { - break; - } - - nb_ones++; - } - - index++; - bitmap_vals = bitmap_vals >> 1; - } - - return index; - } - - size_type next_capacity() const noexcept { - return static_cast(m_capacity + CAPACITY_GROWTH_STEP); - } - - /** - * Insertion - * - * Two situations: - * - Either we are in a situation where - * std::is_nothrow_move_constructible::value is true. In this - * case, on insertion we just reallocate m_values when we reach its capacity - * (i.e. m_nb_elements == m_capacity), otherwise we just put the new value at - * its appropriate place. We can easily keep the strong exception guarantee as - * moving the values around is safe. - * - Otherwise we are in a situation where - * std::is_nothrow_move_constructible::value is false. In this - * case on EACH insertion we allocate a new area of m_nb_elements + 1 where we - * copy the values of m_values into it and put the new value there. On - * success, we set m_values to this new area. Even if slower, it's the only - * way to preserve to strong exception guarantee. - */ - template ::value>::type * = nullptr> - void insert_at_offset(allocator_type &alloc, size_type offset, - Args &&...value_args) { - if (m_nb_elements < m_capacity) { - insert_at_offset_no_realloc(alloc, offset, - std::forward(value_args)...); - } else { - insert_at_offset_realloc(alloc, offset, next_capacity(), - std::forward(value_args)...); - } - } - - template ::value>::type * = nullptr> - void insert_at_offset(allocator_type &alloc, size_type offset, - Args &&...value_args) { - insert_at_offset_realloc(alloc, offset, m_nb_elements + 1, - std::forward(value_args)...); - } - - template ::value>::type * = nullptr> - void insert_at_offset_no_realloc(allocator_type &alloc, size_type offset, - Args &&...value_args) { - tsl_sh_assert(offset <= m_nb_elements); - tsl_sh_assert(m_nb_elements < m_capacity); - - for (size_type i = m_nb_elements; i > offset; i--) { - construct_value(alloc, m_values + i, std::move(m_values[i - 1])); - destroy_value(alloc, m_values + i - 1); - } - - try { - construct_value(alloc, m_values + offset, - std::forward(value_args)...); - } catch (...) { - for (size_type i = offset; i < m_nb_elements; i++) { - construct_value(alloc, m_values + i, std::move(m_values[i + 1])); - destroy_value(alloc, m_values + i + 1); - } - throw; - } - } - - template ::value>::type * = nullptr> - void insert_at_offset_realloc(allocator_type &alloc, size_type offset, - size_type new_capacity, Args &&...value_args) { - tsl_sh_assert(new_capacity > m_nb_elements); - - pointer new_values = alloc.allocate(new_capacity); - // Allocate should throw if there is a failure - tsl_sh_assert(new_values != nullptr); - - try { - construct_value(alloc, new_values + offset, - std::forward(value_args)...); - } catch (...) { - alloc.deallocate(new_values, new_capacity); - throw; - } - - // Should not throw from here - for (size_type i = 0; i < offset; i++) { - construct_value(alloc, new_values + i, std::move(m_values[i])); - } - - for (size_type i = offset; i < m_nb_elements; i++) { - construct_value(alloc, new_values + i + 1, std::move(m_values[i])); - } - - destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); - - m_values = new_values; - m_capacity = new_capacity; - } - - template ::value>::type * = nullptr> - void insert_at_offset_realloc(allocator_type &alloc, size_type offset, - size_type new_capacity, Args &&...value_args) { - tsl_sh_assert(new_capacity > m_nb_elements); - - value_type *new_values = alloc.allocate(new_capacity); - // Allocate should throw if there is a failure - tsl_sh_assert(new_values != nullptr); - - size_type nb_new_values = 0; - try { - for (size_type i = 0; i < offset; i++) { - construct_value(alloc, new_values + i, m_values[i]); - nb_new_values++; - } - - construct_value(alloc, new_values + offset, - std::forward(value_args)...); - nb_new_values++; - - for (size_type i = offset; i < m_nb_elements; i++) { - construct_value(alloc, new_values + i + 1, m_values[i]); - nb_new_values++; - } - } catch (...) { - destroy_and_deallocate_values(alloc, new_values, nb_new_values, - new_capacity); - throw; - } - - tsl_sh_assert(nb_new_values == m_nb_elements + 1); - - destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); - - m_values = new_values; - m_capacity = new_capacity; - } - - /** - * Erasure - * - * Two situations: - * - Either we are in a situation where - * std::is_nothrow_move_constructible::value is true. Simply - * destroy the value and left-shift move the value on the right of offset. - * - Otherwise we are in a situation where - * std::is_nothrow_move_constructible::value is false. Copy all - * the values except the one at offset into a new heap area. On success, we - * set m_values to this new area. Even if slower, it's the only way to - * preserve to strong exception guarantee. - */ - template ::value>::type * = nullptr> - void erase_at_offset(allocator_type &alloc, size_type offset) noexcept { - tsl_sh_assert(offset < m_nb_elements); - - destroy_value(alloc, m_values + offset); - - for (size_type i = offset + 1; i < m_nb_elements; i++) { - construct_value(alloc, m_values + i - 1, std::move(m_values[i])); - destroy_value(alloc, m_values + i); - } - } - - template ::value>::type * = nullptr> - void erase_at_offset(allocator_type &alloc, size_type offset) { - tsl_sh_assert(offset < m_nb_elements); - - // Erasing the last element, don't need to reallocate. We keep the capacity. - if (offset + 1 == m_nb_elements) { - destroy_value(alloc, m_values + offset); - return; - } - - tsl_sh_assert(m_nb_elements > 1); - const size_type new_capacity = m_nb_elements - 1; - - value_type *new_values = alloc.allocate(new_capacity); - // Allocate should throw if there is a failure - tsl_sh_assert(new_values != nullptr); - - size_type nb_new_values = 0; - try { - for (size_type i = 0; i < m_nb_elements; i++) { - if (i != offset) { - construct_value(alloc, new_values + nb_new_values, m_values[i]); - nb_new_values++; - } - } - } catch (...) { - destroy_and_deallocate_values(alloc, new_values, nb_new_values, - new_capacity); - throw; - } - - tsl_sh_assert(nb_new_values == m_nb_elements - 1); - - destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); - - m_values = new_values; - m_capacity = new_capacity; - } - - private: - pointer m_values; - - bitmap_type m_bitmap_vals; - bitmap_type m_bitmap_deleted_vals; - - size_type m_nb_elements; - size_type m_capacity; - bool m_last_array; -}; - -/** - * Internal common class used by `sparse_map` and `sparse_set`. - * - * `ValueType` is what will be stored by `sparse_hash` (usually `std::pair` for map and `Key` for set). - * - * `KeySelect` should be a `FunctionObject` which takes a `ValueType` in - * parameter and returns a reference to the key. - * - * `ValueSelect` should be a `FunctionObject` which takes a `ValueType` in - * parameter and returns a reference to the value. `ValueSelect` should be void - * if there is no value (in a set for example). - * - * The strong exception guarantee only holds if `ExceptionSafety` is set to - * `dice::sh::exception_safety::strong`. - * - * `ValueType` must be nothrow move constructible and/or copy constructible. - * Behaviour is undefined if the destructor of `ValueType` throws. - * - * - * The class holds its buckets in a 2-dimensional fashion. Instead of having a - * linear `std::vector` for [0, bucket_count) where each bucket stores - * one value, we have a `std::vector` (m_sparse_buckets_data) - * where each `sparse_array` stores multiple values (up to - * `sparse_array::BITMAP_NB_BITS`). To convert a one dimensional `ibucket` - * position to a position in `std::vector` and a position in - * `sparse_array`, use respectively the methods - * `sparse_array::sparse_ibucket(ibucket)` and - * `sparse_array::index_in_sparse_bucket(ibucket)`. - */ -template -class sparse_hash : private Allocator, - private Hash, - private KeyEqual, - private GrowthPolicy { - private: - template - using has_mapped_type = - typename std::integral_constant::value>; - - static_assert( - noexcept(std::declval().bucket_for_hash(std::size_t(0))), - "GrowthPolicy::bucket_for_hash must be noexcept."); - static_assert(noexcept(std::declval().clear()), - "GrowthPolicy::clear must be noexcept."); - - public: - template - class sparse_iterator; - - using key_type = typename KeySelect::key_type; - using value_type = ValueType; - using hasher = Hash; - using key_equal = KeyEqual; - using allocator_type = Allocator; - using reference = value_type &; - using const_reference = const value_type &; - using size_type = typename std::allocator_traits::size_type; - using pointer = typename std::allocator_traits::pointer; - using const_pointer = typename std::allocator_traits::const_pointer; - using difference_type = typename std::allocator_traits::difference_type; - using iterator = sparse_iterator; - using const_iterator = sparse_iterator; - - private: - using sparse_array = - dice::sparse_map::detail_sparse_hash::sparse_array; - - using sparse_buckets_allocator = typename std::allocator_traits< - allocator_type>::template rebind_alloc; - using sparse_buckets_container = - boost::container::vector; - public: - /** - * The `operator*()` and `operator->()` methods return a const reference and - * const pointer respectively to the stored value type (`Key` for a set, - * `std::pair` for a map). - * - * In case of a map, to get a mutable reference to the value `T` associated to - * a key (the `.second` in the stored pair), you have to call `value()`. - */ - template - class sparse_iterator { - friend class sparse_hash; - - private: - using sparse_bucket_iterator = typename std::conditional< - IsConst, typename sparse_buckets_container::const_iterator, - typename sparse_buckets_container::iterator>::type; - - using sparse_array_iterator = - typename std::conditional::type; - - /** - * sparse_array_it should be nullptr if sparse_bucket_it == - * m_sparse_buckets_data.end(). (TODO better way?) - */ - sparse_iterator(sparse_bucket_iterator sparse_bucket_it, - sparse_array_iterator sparse_array_it) - : m_sparse_buckets_it(sparse_bucket_it), - m_sparse_array_it(sparse_array_it) {} - - public: - using iterator_category = std::forward_iterator_tag; - using value_type = const typename sparse_hash::value_type; - using difference_type = std::ptrdiff_t; - using reference = value_type &; - using pointer = typename sparse_hash::const_pointer; - - sparse_iterator() noexcept {} - - // Copy constructor from iterator to const_iterator. - template ::type * = nullptr> - sparse_iterator(const sparse_iterator &other) noexcept - : m_sparse_buckets_it(other.m_sparse_buckets_it), - m_sparse_array_it(other.m_sparse_array_it) {} - - sparse_iterator(const sparse_iterator &other) = default; - sparse_iterator(sparse_iterator &&other) = default; - sparse_iterator &operator=(const sparse_iterator &other) = default; - sparse_iterator &operator=(sparse_iterator &&other) = default; - - const typename sparse_hash::key_type &key() const { - return KeySelect()(*m_sparse_array_it); - } - - template ::value && - IsConst>::type * = nullptr> - const typename U::value_type &value() const { - return U()(*m_sparse_array_it); - } - - template ::value && - !IsConst>::type * = nullptr> - typename U::value_type &value() { - return U()(*m_sparse_array_it); - } - - reference operator*() const { return *m_sparse_array_it; } - - //with fancy pointers addressof might be problematic. - pointer operator->() const { return std::addressof(*m_sparse_array_it); } - - sparse_iterator &operator++() { - tsl_sh_assert(m_sparse_array_it != nullptr); - ++m_sparse_array_it; - - //vector iterator with fancy pointers have a problem with -> - if (m_sparse_array_it == (*m_sparse_buckets_it).end()) { - do { - if ((*m_sparse_buckets_it).last()) { - ++m_sparse_buckets_it; - m_sparse_array_it = nullptr; - return *this; - } - - ++m_sparse_buckets_it; - } while ((*m_sparse_buckets_it).empty()); - - m_sparse_array_it = (*m_sparse_buckets_it).begin(); - } - - return *this; - } - - sparse_iterator operator++(int) { - sparse_iterator tmp(*this); - ++*this; - - return tmp; - } - - friend bool operator==(const sparse_iterator &lhs, - const sparse_iterator &rhs) { - return lhs.m_sparse_buckets_it == rhs.m_sparse_buckets_it && - lhs.m_sparse_array_it == rhs.m_sparse_array_it; - } - - friend bool operator!=(const sparse_iterator &lhs, - const sparse_iterator &rhs) { - return !(lhs == rhs); - } - - private: - sparse_bucket_iterator m_sparse_buckets_it; - sparse_array_iterator m_sparse_array_it; - }; - - public: - sparse_hash(size_type bucket_count, const Hash &hash, const KeyEqual &equal, - const Allocator &alloc, float max_load_factor) - : Allocator(alloc), - Hash(hash), - KeyEqual(equal), - GrowthPolicy(bucket_count), - m_sparse_buckets_data(alloc), - // m_sparse_buckets_data(std::allocator_traits::rebind_alloc(alloc)), - m_sparse_buckets(static_empty_sparse_bucket_ptr()), - m_bucket_count(bucket_count), - m_nb_elements(0), - m_nb_deleted_buckets(0) { - if (m_bucket_count > max_bucket_count()) { - throw std::length_error("The map exceeds its maximum size."); - } - - if (m_bucket_count > 0) { - /* - * We can't use the `vector(size_type count, const Allocator& alloc)` - * constructor as it's only available in C++14 and we need to support - * C++11. We thus must resize after using the `vector(const Allocator& - * alloc)` constructor. - * - * We can't use `vector(size_type count, const T& value, const Allocator& - * alloc)` as it requires the value T to be copyable. - */ - m_sparse_buckets_data.resize( - sparse_array::nb_sparse_buckets(bucket_count)); - m_sparse_buckets = m_sparse_buckets_data.data(); - - tsl_sh_assert(!m_sparse_buckets_data.empty()); - m_sparse_buckets_data.back().set_as_last(); - } - - this->max_load_factor(max_load_factor); - - // Check in the constructor instead of outside of a function to avoid - // compilation issues when value_type is not complete. - static_assert(std::is_nothrow_move_constructible::value || - std::is_copy_constructible::value, - "Key, and T if present, must be nothrow move constructible " - "and/or copy constructible."); - } - - ~sparse_hash() { clear(); } - - sparse_hash(const sparse_hash &other) - : Allocator(std::allocator_traits< - Allocator>::select_on_container_copy_construction(other)), - Hash(other), - KeyEqual(other), - GrowthPolicy(other), - m_sparse_buckets_data( - std::allocator_traits< - Allocator>::select_on_container_copy_construction(other)), - m_bucket_count(other.m_bucket_count), - m_nb_elements(other.m_nb_elements), - m_nb_deleted_buckets(other.m_nb_deleted_buckets), - m_load_threshold_rehash(other.m_load_threshold_rehash), - m_load_threshold_clear_deleted(other.m_load_threshold_clear_deleted), - m_max_load_factor(other.m_max_load_factor) { - copy_buckets_from(other), - m_sparse_buckets = m_sparse_buckets_data.empty() - ? static_empty_sparse_bucket_ptr() - : m_sparse_buckets_data.data(); - } - - sparse_hash(sparse_hash &&other) noexcept( - std::is_nothrow_move_constructible::value - &&std::is_nothrow_move_constructible::value - &&std::is_nothrow_move_constructible::value - &&std::is_nothrow_move_constructible::value - &&std::is_nothrow_move_constructible< - sparse_buckets_container>::value) - : Allocator(std::move(other)), - Hash(std::move(other)), - KeyEqual(std::move(other)), - GrowthPolicy(std::move(other)), - m_sparse_buckets_data(std::move(other.m_sparse_buckets_data)), - m_sparse_buckets(m_sparse_buckets_data.empty() - ? static_empty_sparse_bucket_ptr() - : m_sparse_buckets_data.data()), - m_bucket_count(other.m_bucket_count), - m_nb_elements(other.m_nb_elements), - m_nb_deleted_buckets(other.m_nb_deleted_buckets), - m_load_threshold_rehash(other.m_load_threshold_rehash), - m_load_threshold_clear_deleted(other.m_load_threshold_clear_deleted), - m_max_load_factor(other.m_max_load_factor) { - other.GrowthPolicy::clear(); - other.m_sparse_buckets_data.clear(); - other.m_sparse_buckets = static_empty_sparse_bucket_ptr(); - other.m_bucket_count = 0; - other.m_nb_elements = 0; - other.m_nb_deleted_buckets = 0; - other.m_load_threshold_rehash = 0; - other.m_load_threshold_clear_deleted = 0; - } - - sparse_hash &operator=(const sparse_hash &other) { - if (this != &other) { - clear(); - - if (std::allocator_traits< - Allocator>::propagate_on_container_copy_assignment::value) { - Allocator::operator=(other); - } - - Hash::operator=(other); - KeyEqual::operator=(other); - GrowthPolicy::operator=(other); - - if (std::allocator_traits< - Allocator>::propagate_on_container_copy_assignment::value) { - m_sparse_buckets_data = - sparse_buckets_container(static_cast(other)); - } else { - if (m_sparse_buckets_data.size() != - other.m_sparse_buckets_data.size()) { - m_sparse_buckets_data = - sparse_buckets_container(static_cast(*this)); - } else { - m_sparse_buckets_data.clear(); - } - } - - copy_buckets_from(other); - m_sparse_buckets = m_sparse_buckets_data.empty() - ? static_empty_sparse_bucket_ptr() - : m_sparse_buckets_data.data(); - - m_bucket_count = other.m_bucket_count; - m_nb_elements = other.m_nb_elements; - m_nb_deleted_buckets = other.m_nb_deleted_buckets; - m_load_threshold_rehash = other.m_load_threshold_rehash; - m_load_threshold_clear_deleted = other.m_load_threshold_clear_deleted; - m_max_load_factor = other.m_max_load_factor; - } - - return *this; - } - - sparse_hash &operator=(sparse_hash &&other) noexcept { - clear(); - - if (not std::allocator_traits< - Allocator>::propagate_on_container_move_assignment::value - and (static_cast(*this) != static_cast(other))) { - move_buckets_from(std::move(other)); - } else { - static_cast(*this) = std::move(static_cast(other)); - m_sparse_buckets_data = std::move(other.m_sparse_buckets_data); - } - - m_sparse_buckets = m_sparse_buckets_data.empty() - ? static_empty_sparse_bucket_ptr() - : m_sparse_buckets_data.data(); - - static_cast(*this) = std::move(static_cast(other)); - static_cast(*this) = std::move(static_cast(other)); - static_cast(*this) = - std::move(static_cast(other)); - m_bucket_count = other.m_bucket_count; - m_nb_elements = other.m_nb_elements; - m_nb_deleted_buckets = other.m_nb_deleted_buckets; - m_load_threshold_rehash = other.m_load_threshold_rehash; - m_load_threshold_clear_deleted = other.m_load_threshold_clear_deleted; - m_max_load_factor = other.m_max_load_factor; - - other.GrowthPolicy::clear(); - other.m_sparse_buckets_data.clear(); - other.m_sparse_buckets = static_empty_sparse_bucket_ptr(); - other.m_bucket_count = 0; - other.m_nb_elements = 0; - other.m_nb_deleted_buckets = 0; - other.m_load_threshold_rehash = 0; - other.m_load_threshold_clear_deleted = 0; - - return *this; - } - - allocator_type get_allocator() const { - return static_cast(*this); - } - - /* - * Iterators - */ - iterator begin() noexcept { - auto begin = m_sparse_buckets_data.begin(); - //vector iterator with fancy pointers have a problem with -> - while (begin != m_sparse_buckets_data.end() && (*begin).empty()) { - ++begin; - } - - //vector iterator with fancy pointers have a problem with -> - return iterator(begin, (begin != m_sparse_buckets_data.end()) - ? (*begin).begin() - : nullptr); - } - - const_iterator begin() const noexcept { return cbegin(); } - - const_iterator cbegin() const noexcept { - auto begin = m_sparse_buckets_data.cbegin(); - //vector iterator with fancy pointers have a problem with -> - while (begin != m_sparse_buckets_data.cend() && (*begin).empty()) { - ++begin; - } - - return const_iterator(begin, (begin != m_sparse_buckets_data.cend()) - ? (*begin).cbegin() - : nullptr); - } - - iterator end() noexcept { - return iterator(m_sparse_buckets_data.end(), nullptr); - } - - const_iterator end() const noexcept { return cend(); } - - const_iterator cend() const noexcept { - return const_iterator(m_sparse_buckets_data.cend(), nullptr); - } - - /* - * Capacity - */ - bool empty() const noexcept { return m_nb_elements == 0; } - - size_type size() const noexcept { return m_nb_elements; } - - size_type max_size() const noexcept { - return std::min(std::allocator_traits::max_size(), - m_sparse_buckets_data.max_size()); - } - - /* - * Modifiers - */ - void clear() noexcept { - for (auto &bucket : m_sparse_buckets_data) { - bucket.clear(*this); - } - - m_nb_elements = 0; - m_nb_deleted_buckets = 0; - } - - template - std::pair insert(P &&value) { - return insert_impl(KeySelect()(value), std::forward

(value)); - } - - template - iterator insert_hint(const_iterator hint, P &&value) { - if (hint != cend() && - compare_keys(KeySelect()(*hint), KeySelect()(value))) { - return mutable_iterator(hint); - } - - return insert(std::forward

(value)).first; - } - - template - void insert(InputIt first, InputIt last) { - if (std::is_base_of< - std::forward_iterator_tag, - typename std::iterator_traits::iterator_category>::value) { - const auto nb_elements_insert = std::distance(first, last); - const size_type nb_free_buckets = m_load_threshold_rehash - size(); - tsl_sh_assert(m_load_threshold_rehash >= size()); - - if (nb_elements_insert > 0 && - nb_free_buckets < size_type(nb_elements_insert)) { - reserve(size() + size_type(nb_elements_insert)); - } - } - - for (; first != last; ++first) { - insert(*first); - } - } - - template - std::pair insert_or_assign(K &&key, M &&obj) { - auto it = try_emplace(std::forward(key), std::forward(obj)); - if (!it.second) { - it.first.value() = std::forward(obj); - } - - return it; - } - - template - iterator insert_or_assign(const_iterator hint, K &&key, M &&obj) { - if (hint != cend() && compare_keys(KeySelect()(*hint), key)) { - auto it = mutable_iterator(hint); - it.value() = std::forward(obj); - - return it; - } - - return insert_or_assign(std::forward(key), std::forward(obj)).first; - } - - template - std::pair emplace(Args &&...args) { - return insert(value_type(std::forward(args)...)); - } - - template - iterator emplace_hint(const_iterator hint, Args &&...args) { - return insert_hint(hint, value_type(std::forward(args)...)); - } - - template - std::pair try_emplace(K &&key, Args &&...args) { - return insert_impl(key, std::piecewise_construct, - std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(args)...)); - } - - template - iterator try_emplace_hint(const_iterator hint, K &&key, Args &&...args) { - if (hint != cend() && compare_keys(KeySelect()(*hint), key)) { - return mutable_iterator(hint); - } - - return try_emplace(std::forward(key), std::forward(args)...).first; - } - - /** - * Here to avoid `template size_type erase(const K& key)` being used - * when we use an iterator instead of a const_iterator. - */ - iterator erase(iterator pos) { - tsl_sh_assert(pos != end() && m_nb_elements > 0); - //vector iterator with fancy pointers have a problem with -> - auto it_sparse_array_next = - (*pos.m_sparse_buckets_it).erase(*this, pos.m_sparse_array_it); - m_nb_elements--; - m_nb_deleted_buckets++; - - if (it_sparse_array_next == (*pos.m_sparse_buckets_it).end()) { - auto it_sparse_buckets_next = pos.m_sparse_buckets_it; - do { - ++it_sparse_buckets_next; - } while (it_sparse_buckets_next != m_sparse_buckets_data.end() && - (*it_sparse_buckets_next).empty()); - - if (it_sparse_buckets_next == m_sparse_buckets_data.end()) { - return end(); - } else { - return iterator(it_sparse_buckets_next, - (*it_sparse_buckets_next).begin()); - } - } else { - return iterator(pos.m_sparse_buckets_it, it_sparse_array_next); - } - } - - iterator erase(const_iterator pos) { return erase(mutable_iterator(pos)); } - - iterator erase(const_iterator first, const_iterator last) { - if (first == last) { - return mutable_iterator(first); - } - - // TODO Optimize, could avoid the call to std::distance. - const size_type nb_elements_to_erase = - static_cast(std::distance(first, last)); - auto to_delete = mutable_iterator(first); - for (size_type i = 0; i < nb_elements_to_erase; i++) { - to_delete = erase(to_delete); - } - - return to_delete; - } - - template - size_type erase(const K &key) { - return erase(key, hash_key(key)); - } - - template - size_type erase(const K &key, std::size_t hash) { - return erase_impl(key, hash); - } - - void swap(sparse_hash &other) { - using std::swap; - - if (std::allocator_traits::propagate_on_container_swap::value) { - swap(static_cast(*this), static_cast(other)); - } else { - tsl_sh_assert(static_cast(*this) == - static_cast(other)); - } - - swap(static_cast(*this), static_cast(other)); - swap(static_cast(*this), static_cast(other)); - swap(static_cast(*this), - static_cast(other)); - swap(m_sparse_buckets_data, other.m_sparse_buckets_data); - swap(m_sparse_buckets, other.m_sparse_buckets); - swap(m_bucket_count, other.m_bucket_count); - swap(m_nb_elements, other.m_nb_elements); - swap(m_nb_deleted_buckets, other.m_nb_deleted_buckets); - swap(m_load_threshold_rehash, other.m_load_threshold_rehash); - swap(m_load_threshold_clear_deleted, other.m_load_threshold_clear_deleted); - swap(m_max_load_factor, other.m_max_load_factor); - } - - /* - * Lookup - */ - template < - class K, class U = ValueSelect, - typename std::enable_if::value>::type * = nullptr> - typename U::value_type &at(const K &key) { - return at(key, hash_key(key)); - } - - template < - class K, class U = ValueSelect, - typename std::enable_if::value>::type * = nullptr> - typename U::value_type &at(const K &key, std::size_t hash) { - return const_cast( - static_cast(this)->at(key, hash)); - } - - template < - class K, class U = ValueSelect, - typename std::enable_if::value>::type * = nullptr> - const typename U::value_type &at(const K &key) const { - return at(key, hash_key(key)); - } - - template < - class K, class U = ValueSelect, - typename std::enable_if::value>::type * = nullptr> - const typename U::value_type &at(const K &key, std::size_t hash) const { - auto it = find(key, hash); - if (it != cend()) { - return it.value(); - } else { - throw std::out_of_range("Couldn't find key."); - } - } - - template < - class K, class U = ValueSelect, - typename std::enable_if::value>::type * = nullptr> - typename U::value_type &operator[](K &&key) { - return try_emplace(std::forward(key)).first.value(); - } - - template - bool contains(const K &key) const { - return contains(key, hash_key(key)); - } - - template - bool contains(const K &key, std::size_t hash) const { - return count(key, hash) != 0; - } - - template - size_type count(const K &key) const { - return count(key, hash_key(key)); - } - - template - size_type count(const K &key, std::size_t hash) const { - if (find(key, hash) != cend()) { - return 1; - } else { - return 0; - } - } - - template - iterator find(const K &key) { - return find_impl(key, hash_key(key)); - } - - template - iterator find(const K &key, std::size_t hash) { - return find_impl(key, hash); - } - - template - const_iterator find(const K &key) const { - return find_impl(key, hash_key(key)); - } - - template - const_iterator find(const K &key, std::size_t hash) const { - return find_impl(key, hash); - } - - template - std::pair equal_range(const K &key) { - return equal_range(key, hash_key(key)); - } - - template - std::pair equal_range(const K &key, std::size_t hash) { - iterator it = find(key, hash); - return std::make_pair(it, (it == end()) ? it : std::next(it)); - } - - template - std::pair equal_range(const K &key) const { - return equal_range(key, hash_key(key)); - } - - template - std::pair equal_range( - const K &key, std::size_t hash) const { - const_iterator it = find(key, hash); - return std::make_pair(it, (it == cend()) ? it : std::next(it)); - } - - /* - * Bucket interface - */ - size_type bucket_count() const { return m_bucket_count; } - - size_type max_bucket_count() const { - return m_sparse_buckets_data.max_size(); - } - - /* - * Hash policy - */ - float load_factor() const { - if (bucket_count() == 0) { - return 0; - } - - return float(m_nb_elements) / float(bucket_count()); - } - - float max_load_factor() const { return m_max_load_factor; } - - void max_load_factor(float ml) { - m_max_load_factor = std::max(0.1f, std::min(ml, 0.8f)); - m_load_threshold_rehash = - size_type(float(bucket_count()) * m_max_load_factor); - - const float max_load_factor_with_deleted_buckets = - m_max_load_factor + 0.5f * (1.0f - m_max_load_factor); - tsl_sh_assert(max_load_factor_with_deleted_buckets > 0.0f && - max_load_factor_with_deleted_buckets <= 1.0f); - m_load_threshold_clear_deleted = - size_type(float(bucket_count()) * max_load_factor_with_deleted_buckets); - } - - void rehash(size_type count) { - count = std::max(count, - size_type(std::ceil(float(size()) / max_load_factor()))); - rehash_impl(count); - } - - void reserve(size_type count) { - rehash(size_type(std::ceil(float(count) / max_load_factor()))); - } - - /* - * Observers - */ - hasher hash_function() const { return static_cast(*this); } - - key_equal key_eq() const { return static_cast(*this); } - - /* - * Other - */ - iterator mutable_iterator(const_iterator pos) { - auto it_sparse_buckets = - m_sparse_buckets_data.begin() + - std::distance(m_sparse_buckets_data.cbegin(), pos.m_sparse_buckets_it); - - return iterator(it_sparse_buckets, - sparse_array::mutable_iterator(pos.m_sparse_array_it)); - } - - template - void serialize(Serializer &serializer) const { - serialize_impl(serializer); - } - - template - void deserialize(Deserializer &deserializer, bool hash_compatible) { - deserialize_impl(deserializer, hash_compatible); - } - - private: - template - std::size_t hash_key(const K &key) const { - return Hash::operator()(key); - } - - template - bool compare_keys(const K1 &key1, const K2 &key2) const { - return KeyEqual::operator()(key1, key2); - } - - size_type bucket_for_hash(std::size_t hash) const { - const std::size_t bucket = GrowthPolicy::bucket_for_hash(hash); - tsl_sh_assert(sparse_array::sparse_ibucket(bucket) < - m_sparse_buckets_data.size() || - (bucket == 0 && m_sparse_buckets_data.empty())); - - return bucket; - } - - template ::value>::type * = - nullptr> - size_type next_bucket(size_type ibucket, size_type iprobe) const { - (void)iprobe; - if (Probing == dice::sparse_map::sh::probing::linear) { - return (ibucket + 1) & this->m_mask; - } else { - tsl_sh_assert(Probing == dice::sparse_map::sh::probing::quadratic); - return (ibucket + iprobe) & this->m_mask; - } - } - - template ::value>::type * = - nullptr> - size_type next_bucket(size_type ibucket, size_type iprobe) const { - (void)iprobe; - if (Probing == dice::sparse_map::sh::probing::linear) { - ibucket++; - return (ibucket != bucket_count()) ? ibucket : 0; - } else { - tsl_sh_assert(Probing == dice::sparse_map::sh::probing::quadratic); - ibucket += iprobe; - return (ibucket < bucket_count()) ? ibucket : ibucket % bucket_count(); - } - } - - // TODO encapsulate m_sparse_buckets_data to avoid the managing the allocator - void copy_buckets_from(const sparse_hash &other) { - m_sparse_buckets_data.reserve(other.m_sparse_buckets_data.size()); - - try { - for (const auto &bucket : other.m_sparse_buckets_data) { - m_sparse_buckets_data.emplace_back(bucket, - static_cast(*this)); - } - } catch (...) { - clear(); - throw; - } - - tsl_sh_assert(m_sparse_buckets_data.empty() || - m_sparse_buckets_data.back().last()); - } - - void move_buckets_from(sparse_hash &&other) { - m_sparse_buckets_data.reserve(other.m_sparse_buckets_data.size()); - - try { - for (auto &&bucket : other.m_sparse_buckets_data) { - m_sparse_buckets_data.emplace_back(std::move(bucket), - static_cast(*this)); - } - } catch (...) { - clear(); - throw; - } - - tsl_sh_assert(m_sparse_buckets_data.empty() || - m_sparse_buckets_data.back().last()); - } - - template - std::pair insert_impl(const K &key, - Args &&...value_type_args) { - if (size() >= m_load_threshold_rehash) { - rehash_impl(GrowthPolicy::next_bucket_count()); - } else if (size() + m_nb_deleted_buckets >= - m_load_threshold_clear_deleted) { - clear_deleted_buckets(); - } - tsl_sh_assert(!m_sparse_buckets_data.empty()); - - /** - * We must insert the value in the first empty or deleted bucket we find. If - * we first find a deleted bucket, we still have to continue the search - * until we find an empty bucket or until we have searched all the buckets - * to be sure that the value is not in the hash table. We thus remember the - * position, if any, of the first deleted bucket we have encountered so we - * can insert it there if needed. - */ - bool found_first_deleted_bucket = false; - std::size_t sparse_ibucket_first_deleted = 0; - typename sparse_array::size_type index_in_sparse_bucket_first_deleted = 0; - - const std::size_t hash = hash_key(key); - std::size_t ibucket = bucket_for_hash(hash); - - std::size_t probe = 0; - while (true) { - std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); - auto index_in_sparse_bucket = - sparse_array::index_in_sparse_bucket(ibucket); - - if (m_sparse_buckets != static_empty_sparse_bucket_ptr()) { - if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { - auto value_it = - m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); - if (compare_keys(key, KeySelect()(*value_it))) { - return std::make_pair( - iterator(m_sparse_buckets_data.begin() + sparse_ibucket, - value_it), - false); - } - } else if (m_sparse_buckets[sparse_ibucket].has_deleted_value( - index_in_sparse_bucket) && - probe < m_bucket_count) { - if (!found_first_deleted_bucket) { - found_first_deleted_bucket = true; - sparse_ibucket_first_deleted = sparse_ibucket; - index_in_sparse_bucket_first_deleted = index_in_sparse_bucket; - } - } else if (found_first_deleted_bucket) { - auto it = insert_in_bucket(sparse_ibucket_first_deleted, - index_in_sparse_bucket_first_deleted, - std::forward(value_type_args)...); - m_nb_deleted_buckets--; - - return it; - } - else { - return insert_in_bucket(sparse_ibucket, index_in_sparse_bucket, - std::forward(value_type_args)...); - } - }else { - return insert_in_bucket(sparse_ibucket, index_in_sparse_bucket, - std::forward(value_type_args)...); - } - - probe++; - ibucket = next_bucket(ibucket, probe); - } - } - - template - std::pair insert_in_bucket( - std::size_t sparse_ibucket, - typename sparse_array::size_type index_in_sparse_bucket, - Args &&...value_type_args) { - // is not called when empty - auto value_it = m_sparse_buckets[sparse_ibucket].set( - *this, index_in_sparse_bucket, std::forward(value_type_args)...); - m_nb_elements++; - - return std::make_pair( - iterator(m_sparse_buckets_data.begin() + sparse_ibucket, value_it), - true); - } - - template - size_type erase_impl(const K &key, std::size_t hash) { - std::size_t ibucket = bucket_for_hash(hash); - - std::size_t probe = 0; - - if (m_sparse_buckets == static_empty_sparse_bucket_ptr()) - return 0; - while (true) { - const std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); - const auto index_in_sparse_bucket = - sparse_array::index_in_sparse_bucket(ibucket); - - if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { - auto value_it = - m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); - if (compare_keys(key, KeySelect()(*value_it))) { - m_sparse_buckets[sparse_ibucket].erase(*this, value_it, - index_in_sparse_bucket); - m_nb_elements--; - m_nb_deleted_buckets++; - - return 1; - } - } else if (!m_sparse_buckets[sparse_ibucket].has_deleted_value( - index_in_sparse_bucket) || - probe >= m_bucket_count) { - return 0; - } - - probe++; - ibucket = next_bucket(ibucket, probe); - } - } - - template - iterator find_impl(const K &key, std::size_t hash) { - return mutable_iterator( - static_cast(this)->find(key, hash)); - } - - template - const_iterator find_impl(const K &key, std::size_t hash) const { - std::size_t ibucket = bucket_for_hash(hash); - - std::size_t probe = 0; - while (true) { - const std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); - const auto index_in_sparse_bucket = - sparse_array::index_in_sparse_bucket(ibucket); - - if (m_sparse_buckets == static_empty_sparse_bucket_ptr()) { - return cend(); - }if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { - auto value_it = - m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); - if (compare_keys(key, KeySelect()(*value_it))) { - return const_iterator(m_sparse_buckets_data.cbegin() + sparse_ibucket, - value_it); - } - } else if (!m_sparse_buckets[sparse_ibucket].has_deleted_value( - index_in_sparse_bucket) || - probe >= m_bucket_count) { - return cend(); - } - - probe++; - ibucket = next_bucket(ibucket, probe); - } - } - - void clear_deleted_buckets() { - // TODO could be optimized, we could do it in-place instead of allocating a - // new bucket array. - rehash_impl(m_bucket_count); - tsl_sh_assert(m_nb_deleted_buckets == 0); - } - - template ::type - * = nullptr> - void rehash_impl(size_type count) { - sparse_hash new_table(count, static_cast(*this), - static_cast(*this), - static_cast(*this), m_max_load_factor); - - for (auto &bucket : m_sparse_buckets_data) { - for (auto &val : bucket) { - new_table.insert_on_rehash(std::move(val)); - } - - // TODO try to reuse some of the memory - bucket.clear(*this); - } - - new_table.swap(*this); - } - - /** - * TODO: For now we copy each element into the new map. We could move - * them if they are nothrow_move_constructible without triggering - * any exception if we reserve enough space in the sparse arrays beforehand. - */ - template ::type * = nullptr> - void rehash_impl(size_type count) { - sparse_hash new_table(count, static_cast(*this), - static_cast(*this), - static_cast(*this), m_max_load_factor); - - for (const auto &bucket : m_sparse_buckets_data) { - for (const auto &val : bucket) { - new_table.insert_on_rehash(val); - } - } - - new_table.swap(*this); - } - - template - void insert_on_rehash(K &&key_value) { - const key_type &key = KeySelect()(key_value); - - const std::size_t hash = hash_key(key); - std::size_t ibucket = bucket_for_hash(hash); - - std::size_t probe = 0; - while (true) { - std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); - auto index_in_sparse_bucket = - sparse_array::index_in_sparse_bucket(ibucket); - - if (!m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { - m_sparse_buckets[sparse_ibucket].set(*this, index_in_sparse_bucket, - std::forward(key_value)); - m_nb_elements++; - - return; - } else { - tsl_sh_assert(!compare_keys( - key, KeySelect()(*m_sparse_buckets[sparse_ibucket].value( - index_in_sparse_bucket)))); - } - - probe++; - ibucket = next_bucket(ibucket, probe); - } - } - - template - void serialize_impl(Serializer &serializer) const { - const slz_size_type version = SERIALIZATION_PROTOCOL_VERSION; - serializer(version); - - const slz_size_type bucket_count = m_bucket_count; - serializer(bucket_count); - - const slz_size_type nb_sparse_buckets = m_sparse_buckets_data.size(); - serializer(nb_sparse_buckets); - - const slz_size_type nb_elements = m_nb_elements; - serializer(nb_elements); - - const slz_size_type nb_deleted_buckets = m_nb_deleted_buckets; - serializer(nb_deleted_buckets); - - const float max_load_factor = m_max_load_factor; - serializer(max_load_factor); - - for (const auto &bucket : m_sparse_buckets_data) { - bucket.serialize(serializer); - } - } - - template - void deserialize_impl(Deserializer &deserializer, bool hash_compatible) { - tsl_sh_assert( - m_bucket_count == 0 && - m_sparse_buckets_data.empty()); // Current hash table must be empty - - const slz_size_type version = - deserialize_value(deserializer); - // For now we only have one version of the serialization protocol. - // If it doesn't match there is a problem with the file. - if (version != SERIALIZATION_PROTOCOL_VERSION) { - throw std::runtime_error( - "Can't deserialize the sparse_map/set. The " - "protocol version header is invalid."); - } - - const slz_size_type bucket_count_ds = - deserialize_value(deserializer); - const slz_size_type nb_sparse_buckets = - deserialize_value(deserializer); - const slz_size_type nb_elements = - deserialize_value(deserializer); - const slz_size_type nb_deleted_buckets = - deserialize_value(deserializer); - const float max_load_factor = deserialize_value(deserializer); - - if (!hash_compatible) { - this->max_load_factor(max_load_factor); - reserve(numeric_cast(nb_elements, - "Deserialized nb_elements is too big.")); - for (slz_size_type ibucket = 0; ibucket < nb_sparse_buckets; ibucket++) { - sparse_array::deserialize_values_into_sparse_hash(deserializer, *this); - } - } else { - m_bucket_count = numeric_cast( - bucket_count_ds, "Deserialized bucket_count is too big."); - - GrowthPolicy::operator=(GrowthPolicy(m_bucket_count)); - // GrowthPolicy should not modify the bucket count we got from - // deserialization - if (m_bucket_count != bucket_count_ds) { - throw std::runtime_error( - "The GrowthPolicy is not the same even though " - "hash_compatible is true."); - } - - if (nb_sparse_buckets != - sparse_array::nb_sparse_buckets(m_bucket_count)) { - throw std::runtime_error("Deserialized nb_sparse_buckets is invalid."); - } - - m_nb_elements = numeric_cast( - nb_elements, "Deserialized nb_elements is too big."); - m_nb_deleted_buckets = numeric_cast( - nb_deleted_buckets, "Deserialized nb_deleted_buckets is too big."); - - m_sparse_buckets_data.reserve(numeric_cast( - nb_sparse_buckets, "Deserialized nb_sparse_buckets is too big.")); - for (slz_size_type ibucket = 0; ibucket < nb_sparse_buckets; ibucket++) { - m_sparse_buckets_data.emplace_back( - sparse_array::deserialize_hash_compatible( - deserializer, static_cast(*this))); - } - - if (!m_sparse_buckets_data.empty()) { - m_sparse_buckets_data.back().set_as_last(); - m_sparse_buckets = m_sparse_buckets_data.data(); - } - - this->max_load_factor(max_load_factor); - if (load_factor() > this->max_load_factor()) { - throw std::runtime_error( - "Invalid max_load_factor. Check that the serializer and " - "deserializer support " - "floats correctly as they can be converted implicitely to ints."); - } - } - } - - public: - static const size_type DEFAULT_INIT_BUCKET_COUNT = 0; - static constexpr float DEFAULT_MAX_LOAD_FACTOR = 0.5f; - - /** - * Protocol version currenlty used for serialization. - */ - static const slz_size_type SERIALIZATION_PROTOCOL_VERSION = 1; - - using sparse_array_ptr = typename std::allocator_traits::template rebind_traits::pointer; - /** - * Return an nullptr to indicate an empty bucket - */ - static sparse_array_ptr static_empty_sparse_bucket_ptr() { - return {}; - } - - private: - sparse_buckets_container m_sparse_buckets_data; - - - /** - * Points to m_sparse_buckets_data.data() if !m_sparse_buckets_data.empty() - * otherwise points to static_empty_sparse_bucket_ptr. This variable is useful - * to avoid the cost of checking if m_sparse_buckets_data is empty when trying - * to find an element. - * - * TODO Remove m_sparse_buckets_data and only use a pointer instead of a - * pointer+vector to save some space in the sparse_hash object. - */ - - sparse_array_ptr m_sparse_buckets; - - size_type m_bucket_count; - size_type m_nb_elements; - size_type m_nb_deleted_buckets; - - /** - * Maximum that m_nb_elements can reach before a rehash occurs automatically - * to grow the hash table. - */ - size_type m_load_threshold_rehash; - - /** - * Maximum that m_nb_elements + m_nb_deleted_buckets can reach before cleaning - * up the buckets marked as deleted. - */ - size_type m_load_threshold_clear_deleted; - float m_max_load_factor; -}; - -} // namespace detail_sparse_hash -} // namespace dice + static const std::size_t BUCKET_MASK = BITMAP_NB_BITS - 1; + + static_assert(is_power_of_two(BITMAP_NB_BITS), + "BITMAP_NB_BITS must be a power of two."); + static_assert(std::numeric_limits::digits >= BITMAP_NB_BITS, + "bitmap_type must be able to hold at least BITMAP_NB_BITS."); + static_assert((std::size_t(1) << BUCKET_SHIFT) == BITMAP_NB_BITS, + "(1 << BUCKET_SHIFT) must be equal to BITMAP_NB_BITS."); + static_assert(std::numeric_limits::max() >= BITMAP_NB_BITS, + "size_type must be big enough to hold BITMAP_NB_BITS."); + static_assert(std::is_unsigned::value, + "bitmap_type must be unsigned."); + static_assert((std::numeric_limits::max() & BUCKET_MASK) == + BITMAP_NB_BITS - 1, + ""); + + public: + /** + * Map an ibucket [0, bucket_count) in the hash table to a sparse_ibucket + * (a sparse_array holds multiple buckets, so there is less sparse_array than + * bucket_count). + * + * The bucket ibucket is in + * m_sparse_buckets[sparse_ibucket(ibucket)][index_in_sparse_bucket(ibucket)] + * instead of something like m_buckets[ibucket] in a classical hash table. + */ + static std::size_t sparse_ibucket(std::size_t ibucket) { + return ibucket >> BUCKET_SHIFT; + } + + /** + * Map an ibucket [0, bucket_count) in the hash table to an index in the + * sparse_array which corresponds to the bucket. + * + * The bucket ibucket is in + * m_sparse_buckets[sparse_ibucket(ibucket)][index_in_sparse_bucket(ibucket)] + * instead of something like m_buckets[ibucket] in a classical hash table. + */ + static typename sparse_array::size_type index_in_sparse_bucket( + std::size_t ibucket) { + return static_cast( + ibucket & sparse_array::BUCKET_MASK); + } + + static std::size_t nb_sparse_buckets(std::size_t bucket_count) noexcept { + if (bucket_count == 0) { + return 0; + } + + return std::max( + 1, sparse_ibucket(dice::sparse_map::detail_sparse_hash::round_up_to_power_of_two( + bucket_count))); + } + + public: + sparse_array() noexcept + : m_values(nullptr), + m_bitmap_vals(0), + m_bitmap_deleted_vals(0), + m_nb_elements(0), + m_capacity(0), + m_last_array(false) {} + + //needed for "is_constructible" with no parameters + sparse_array(std::allocator_arg_t, Allocator const &) noexcept : sparse_array() {} + + explicit sparse_array(bool last_bucket) noexcept + : m_values(nullptr), + m_bitmap_vals(0), + m_bitmap_deleted_vals(0), + m_nb_elements(0), + m_capacity(0), + m_last_array(last_bucket) {} + + //const Allocator needed for MoveInsertable requirement + sparse_array(size_type capacity, Allocator const &const_alloc) + : m_values(nullptr), + m_bitmap_vals(0), + m_bitmap_deleted_vals(0), + m_nb_elements(0), + m_capacity(capacity), + m_last_array(false) { + if (m_capacity > 0) { + auto alloc = const_cast(const_alloc); + m_values = alloc.allocate(m_capacity); + tsl_sh_assert(m_values != + nullptr);// allocate should throw if there is a failure + } + } + + //const Allocator needed for MoveInsertable requirement + sparse_array(const sparse_array &other, Allocator const &const_alloc) + : m_values(nullptr), + m_bitmap_vals(other.m_bitmap_vals), + m_bitmap_deleted_vals(other.m_bitmap_deleted_vals), + m_nb_elements(0), + m_capacity(other.m_capacity), + m_last_array(other.m_last_array) { + tsl_sh_assert(other.m_capacity >= other.m_nb_elements); + if (m_capacity == 0) { + return; + } + + auto alloc = const_cast(const_alloc); + m_values = alloc.allocate(m_capacity); + tsl_sh_assert(m_values != + nullptr);// allocate should throw if there is a failure + try { + for (size_type i = 0; i < other.m_nb_elements; i++) { + construct_value(alloc, m_values + i, other.m_values[i]); + m_nb_elements++; + } + } catch (...) { + clear(alloc); + throw; + } + } + + sparse_array(sparse_array &&other) noexcept + : m_values(other.m_values), + m_bitmap_vals(other.m_bitmap_vals), + m_bitmap_deleted_vals(other.m_bitmap_deleted_vals), + m_nb_elements(other.m_nb_elements), + m_capacity(other.m_capacity), + m_last_array(other.m_last_array) { + other.m_values = nullptr; + other.m_bitmap_vals = 0; + other.m_bitmap_deleted_vals = 0; + other.m_nb_elements = 0; + other.m_capacity = 0; + } + + //const Allocator needed for MoveInsertable requirement + sparse_array(sparse_array &&other, Allocator const &const_alloc) + : m_values(nullptr), + m_bitmap_vals(other.m_bitmap_vals), + m_bitmap_deleted_vals(other.m_bitmap_deleted_vals), + m_nb_elements(0), + m_capacity(other.m_capacity), + m_last_array(other.m_last_array) { + tsl_sh_assert(other.m_capacity >= other.m_nb_elements); + if (m_capacity == 0) { + return; + } + + auto alloc = const_cast(const_alloc); + m_values = alloc.allocate(m_capacity); + tsl_sh_assert(m_values != + nullptr);// allocate should throw if there is a failure + try { + for (size_type i = 0; i < other.m_nb_elements; i++) { + construct_value(alloc, m_values + i, std::move(other.m_values[i])); + m_nb_elements++; + } + } catch (...) { + clear(alloc); + throw; + } + } + + sparse_array &operator=(const sparse_array &) = delete; + sparse_array &operator=(sparse_array &&other) noexcept { + this->m_values = other.m_values; + this->m_bitmap_vals = other.m_bitmap_vals; + this->m_bitmap_deleted_vals = other.m_bitmap_deleted_vals; + this->m_nb_elements = other.m_nb_elements; + this->m_capacity = other.m_capacity; + other.m_values = nullptr; + other.m_bitmap_vals = 0; + other.m_bitmap_deleted_vals = 0; + other.m_nb_elements = 0; + other.m_capacity = 0; + return *this; + } + + + ~sparse_array() noexcept { + // The code that manages the sparse_array must have called clear before + // destruction. See documentation of sparse_array for more details. + tsl_sh_assert(m_capacity == 0 && m_nb_elements == 0 && m_values == nullptr); + } + + iterator begin() noexcept { return m_values; } + iterator end() noexcept { return m_values + m_nb_elements; } + const_iterator begin() const noexcept { return cbegin(); } + const_iterator end() const noexcept { return cend(); } + const_iterator cbegin() const noexcept { return m_values; } + const_iterator cend() const noexcept { return m_values + m_nb_elements; } + + bool empty() const noexcept { return m_nb_elements == 0; } + + size_type size() const noexcept { return m_nb_elements; } + + void clear(allocator_type &alloc) noexcept { + destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); + + m_values = nullptr; + m_bitmap_vals = 0; + m_bitmap_deleted_vals = 0; + m_nb_elements = 0; + m_capacity = 0; + } + + bool last() const noexcept { return m_last_array; } + + void set_as_last() noexcept { m_last_array = true; } + + bool has_value(size_type index) const noexcept { + tsl_sh_assert(index < BITMAP_NB_BITS); + return (m_bitmap_vals & (bitmap_type(1) << index)) != 0; + } + + bool has_deleted_value(size_type index) const noexcept { + tsl_sh_assert(index < BITMAP_NB_BITS); + return (m_bitmap_deleted_vals & (bitmap_type(1) << index)) != 0; + } + + iterator value(size_type index) noexcept { + tsl_sh_assert(has_value(index)); + return m_values + index_to_offset(index); + } + + const_iterator value(size_type index) const noexcept { + tsl_sh_assert(has_value(index)); + return m_values + index_to_offset(index); + } + + /** + * Return iterator to set value. + */ + template + iterator set(allocator_type &alloc, size_type index, Args &&...value_args) { + tsl_sh_assert(!has_value(index)); + + const size_type offset = index_to_offset(index); + insert_at_offset(alloc, offset, std::forward(value_args)...); + + m_bitmap_vals = (m_bitmap_vals | (bitmap_type(1) << index)); + m_bitmap_deleted_vals = + (m_bitmap_deleted_vals & ~(bitmap_type(1) << index)); + + m_nb_elements++; + + tsl_sh_assert(has_value(index)); + tsl_sh_assert(!has_deleted_value(index)); + + return m_values + offset; + } + + iterator erase(allocator_type &alloc, iterator position) { + const size_type offset = + static_cast(std::distance(begin(), position)); + return erase(alloc, position, offset_to_index(offset)); + } + + // Return the next value or end if no next value + iterator erase(allocator_type &alloc, iterator position, size_type index) { + tsl_sh_assert(has_value(index)); + tsl_sh_assert(!has_deleted_value(index)); + + const size_type offset = + static_cast(std::distance(begin(), position)); + erase_at_offset(alloc, offset); + + m_bitmap_vals = (m_bitmap_vals & ~(bitmap_type(1) << index)); + m_bitmap_deleted_vals = (m_bitmap_deleted_vals | (bitmap_type(1) << index)); + + m_nb_elements--; + + tsl_sh_assert(!has_value(index)); + tsl_sh_assert(has_deleted_value(index)); + + return m_values + offset; + } + + void swap(sparse_array &other) { + using std::swap; + + swap(m_values, other.m_values); + swap(m_bitmap_vals, other.m_bitmap_vals); + swap(m_bitmap_deleted_vals, other.m_bitmap_deleted_vals); + swap(m_nb_elements, other.m_nb_elements); + swap(m_capacity, other.m_capacity); + swap(m_last_array, other.m_last_array); + } + + static iterator mutable_iterator(const_iterator pos) { + return ::dice::sparse_map::Remove_Const::template remove(pos); + } + + template + void serialize(Serializer &serializer) const { + const slz_size_type sparse_bucket_size = m_nb_elements; + serializer(sparse_bucket_size); + + const slz_size_type bitmap_vals = m_bitmap_vals; + serializer(bitmap_vals); + + const slz_size_type bitmap_deleted_vals = m_bitmap_deleted_vals; + serializer(bitmap_deleted_vals); + + for (const value_type &value : *this) { + serializer(value); + } + } + + template + static sparse_array deserialize_hash_compatible(Deserializer &deserializer, + Allocator &alloc) { + const slz_size_type sparse_bucket_size = + deserialize_value(deserializer); + const slz_size_type bitmap_vals = + deserialize_value(deserializer); + const slz_size_type bitmap_deleted_vals = + deserialize_value(deserializer); + + if (sparse_bucket_size > BITMAP_NB_BITS) { + throw std::runtime_error( + "Deserialized sparse_bucket_size is too big for the platform. " + "Maximum should be BITMAP_NB_BITS."); + } + + sparse_array sarray; + if (sparse_bucket_size == 0) { + return sarray; + } + + sarray.m_bitmap_vals = numeric_cast( + bitmap_vals, "Deserialized bitmap_vals is too big."); + sarray.m_bitmap_deleted_vals = numeric_cast( + bitmap_deleted_vals, "Deserialized bitmap_deleted_vals is too big."); + + sarray.m_capacity = numeric_cast( + sparse_bucket_size, "Deserialized sparse_bucket_size is too big."); + sarray.m_values = alloc.allocate(sarray.m_capacity); + + try { + for (size_type ivalue = 0; ivalue < sarray.m_capacity; ivalue++) { + construct_value(alloc, sarray.m_values + ivalue, + deserialize_value(deserializer)); + sarray.m_nb_elements++; + } + } catch (...) { + sarray.clear(alloc); + throw; + } + + return sarray; + } + + /** + * Deserialize the values of the bucket and insert them all in sparse_hash + * through sparse_hash.insert(...). + */ + template + static void deserialize_values_into_sparse_hash(Deserializer &deserializer, + SparseHash &sparse_hash) { + const slz_size_type sparse_bucket_size = + deserialize_value(deserializer); + + const slz_size_type bitmap_vals = + deserialize_value(deserializer); + static_cast(bitmap_vals);// Ignore, not needed + + const slz_size_type bitmap_deleted_vals = + deserialize_value(deserializer); + static_cast(bitmap_deleted_vals);// Ignore, not needed + + for (slz_size_type ivalue = 0; ivalue < sparse_bucket_size; ivalue++) { + sparse_hash.insert(deserialize_value(deserializer)); + } + } + + private: + template + static void construct_value(allocator_type &alloc, pointer value, + Args &&...value_args) { + std::allocator_traits::construct( + alloc, detail_sparse_hash::to_address(value), std::forward(value_args)...); + } + + static void destroy_value(allocator_type &alloc, pointer value) noexcept { + std::allocator_traits::destroy(alloc, detail_sparse_hash::to_address(value)); + } + + static void destroy_and_deallocate_values( + allocator_type &alloc, pointer values, size_type nb_values, + size_type capacity_values) noexcept { + for (size_type i = 0; i < nb_values; i++) { + destroy_value(alloc, values + i); + } + + alloc.deallocate(values, capacity_values); + } + + static size_type popcount(bitmap_type val) noexcept { + if (sizeof(bitmap_type) <= sizeof(unsigned int)) { + return static_cast( + dice::sparse_map::detail_popcount::popcount(static_cast(val))); + } else { + return static_cast(dice::sparse_map::detail_popcount::popcountll(val)); + } + } + + size_type index_to_offset(size_type index) const noexcept { + tsl_sh_assert(index < BITMAP_NB_BITS); + return popcount(m_bitmap_vals & + ((bitmap_type(1) << index) - bitmap_type(1))); + } + + // TODO optimize + size_type offset_to_index(size_type offset) const noexcept { + tsl_sh_assert(offset < m_nb_elements); + + bitmap_type bitmap_vals = m_bitmap_vals; + size_type index = 0; + size_type nb_ones = 0; + + while (bitmap_vals != 0) { + if ((bitmap_vals & 0x1) == 1) { + if (nb_ones == offset) { + break; + } + + nb_ones++; + } + + index++; + bitmap_vals = bitmap_vals >> 1; + } + + return index; + } + + size_type next_capacity() const noexcept { + return static_cast(m_capacity + CAPACITY_GROWTH_STEP); + } + + /** + * Insertion + * + * Two situations: + * - Either we are in a situation where + * std::is_nothrow_move_constructible::value is true. In this + * case, on insertion we just reallocate m_values when we reach its capacity + * (i.e. m_nb_elements == m_capacity), otherwise we just put the new value at + * its appropriate place. We can easily keep the strong exception guarantee as + * moving the values around is safe. + * - Otherwise we are in a situation where + * std::is_nothrow_move_constructible::value is false. In this + * case on EACH insertion we allocate a new area of m_nb_elements + 1 where we + * copy the values of m_values into it and put the new value there. On + * success, we set m_values to this new area. Even if slower, it's the only + * way to preserve to strong exception guarantee. + */ + template::value>::type * = nullptr> + void insert_at_offset(allocator_type &alloc, size_type offset, + Args &&...value_args) { + if (m_nb_elements < m_capacity) { + insert_at_offset_no_realloc(alloc, offset, + std::forward(value_args)...); + } else { + insert_at_offset_realloc(alloc, offset, next_capacity(), + std::forward(value_args)...); + } + } + + template::value>::type * = nullptr> + void insert_at_offset(allocator_type &alloc, size_type offset, + Args &&...value_args) { + insert_at_offset_realloc(alloc, offset, m_nb_elements + 1, + std::forward(value_args)...); + } + + template::value>::type * = nullptr> + void insert_at_offset_no_realloc(allocator_type &alloc, size_type offset, + Args &&...value_args) { + tsl_sh_assert(offset <= m_nb_elements); + tsl_sh_assert(m_nb_elements < m_capacity); + + for (size_type i = m_nb_elements; i > offset; i--) { + construct_value(alloc, m_values + i, std::move(m_values[i - 1])); + destroy_value(alloc, m_values + i - 1); + } + + try { + construct_value(alloc, m_values + offset, + std::forward(value_args)...); + } catch (...) { + for (size_type i = offset; i < m_nb_elements; i++) { + construct_value(alloc, m_values + i, std::move(m_values[i + 1])); + destroy_value(alloc, m_values + i + 1); + } + throw; + } + } + + template::value>::type * = nullptr> + void insert_at_offset_realloc(allocator_type &alloc, size_type offset, + size_type new_capacity, Args &&...value_args) { + tsl_sh_assert(new_capacity > m_nb_elements); + + pointer new_values = alloc.allocate(new_capacity); + // Allocate should throw if there is a failure + tsl_sh_assert(new_values != nullptr); + + try { + construct_value(alloc, new_values + offset, + std::forward(value_args)...); + } catch (...) { + alloc.deallocate(new_values, new_capacity); + throw; + } + + // Should not throw from here + for (size_type i = 0; i < offset; i++) { + construct_value(alloc, new_values + i, std::move(m_values[i])); + } + + for (size_type i = offset; i < m_nb_elements; i++) { + construct_value(alloc, new_values + i + 1, std::move(m_values[i])); + } + + destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); + + m_values = new_values; + m_capacity = new_capacity; + } + + template::value>::type * = nullptr> + void insert_at_offset_realloc(allocator_type &alloc, size_type offset, + size_type new_capacity, Args &&...value_args) { + tsl_sh_assert(new_capacity > m_nb_elements); + + value_type *new_values = alloc.allocate(new_capacity); + // Allocate should throw if there is a failure + tsl_sh_assert(new_values != nullptr); + + size_type nb_new_values = 0; + try { + for (size_type i = 0; i < offset; i++) { + construct_value(alloc, new_values + i, m_values[i]); + nb_new_values++; + } + + construct_value(alloc, new_values + offset, + std::forward(value_args)...); + nb_new_values++; + + for (size_type i = offset; i < m_nb_elements; i++) { + construct_value(alloc, new_values + i + 1, m_values[i]); + nb_new_values++; + } + } catch (...) { + destroy_and_deallocate_values(alloc, new_values, nb_new_values, + new_capacity); + throw; + } + + tsl_sh_assert(nb_new_values == m_nb_elements + 1); + + destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); + + m_values = new_values; + m_capacity = new_capacity; + } + + /** + * Erasure + * + * Two situations: + * - Either we are in a situation where + * std::is_nothrow_move_constructible::value is true. Simply + * destroy the value and left-shift move the value on the right of offset. + * - Otherwise we are in a situation where + * std::is_nothrow_move_constructible::value is false. Copy all + * the values except the one at offset into a new heap area. On success, we + * set m_values to this new area. Even if slower, it's the only way to + * preserve to strong exception guarantee. + */ + template::value>::type * = nullptr> + void erase_at_offset(allocator_type &alloc, size_type offset) noexcept { + tsl_sh_assert(offset < m_nb_elements); + + destroy_value(alloc, m_values + offset); + + for (size_type i = offset + 1; i < m_nb_elements; i++) { + construct_value(alloc, m_values + i - 1, std::move(m_values[i])); + destroy_value(alloc, m_values + i); + } + } + + template::value>::type * = nullptr> + void erase_at_offset(allocator_type &alloc, size_type offset) { + tsl_sh_assert(offset < m_nb_elements); + + // Erasing the last element, don't need to reallocate. We keep the capacity. + if (offset + 1 == m_nb_elements) { + destroy_value(alloc, m_values + offset); + return; + } + + tsl_sh_assert(m_nb_elements > 1); + const size_type new_capacity = m_nb_elements - 1; + + value_type *new_values = alloc.allocate(new_capacity); + // Allocate should throw if there is a failure + tsl_sh_assert(new_values != nullptr); + + size_type nb_new_values = 0; + try { + for (size_type i = 0; i < m_nb_elements; i++) { + if (i != offset) { + construct_value(alloc, new_values + nb_new_values, m_values[i]); + nb_new_values++; + } + } + } catch (...) { + destroy_and_deallocate_values(alloc, new_values, nb_new_values, + new_capacity); + throw; + } + + tsl_sh_assert(nb_new_values == m_nb_elements - 1); + + destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); + + m_values = new_values; + m_capacity = new_capacity; + } + + private: + pointer m_values; + + bitmap_type m_bitmap_vals; + bitmap_type m_bitmap_deleted_vals; + + size_type m_nb_elements; + size_type m_capacity; + bool m_last_array; + }; + + /** + * Internal common class used by `sparse_map` and `sparse_set`. + * + * `ValueType` is what will be stored by `sparse_hash` (usually `std::pair` for map and `Key` for set). + * + * `KeySelect` should be a `FunctionObject` which takes a `ValueType` in + * parameter and returns a reference to the key. + * + * `ValueSelect` should be a `FunctionObject` which takes a `ValueType` in + * parameter and returns a reference to the value. `ValueSelect` should be void + * if there is no value (in a set for example). + * + * The strong exception guarantee only holds if `ExceptionSafety` is set to + * `dice::sh::exception_safety::strong`. + * + * `ValueType` must be nothrow move constructible and/or copy constructible. + * Behaviour is undefined if the destructor of `ValueType` throws. + * + * + * The class holds its buckets in a 2-dimensional fashion. Instead of having a + * linear `std::vector` for [0, bucket_count) where each bucket stores + * one value, we have a `std::vector` (m_sparse_buckets_data) + * where each `sparse_array` stores multiple values (up to + * `sparse_array::BITMAP_NB_BITS`). To convert a one dimensional `ibucket` + * position to a position in `std::vector` and a position in + * `sparse_array`, use respectively the methods + * `sparse_array::sparse_ibucket(ibucket)` and + * `sparse_array::index_in_sparse_bucket(ibucket)`. + */ + template + class sparse_hash : private Allocator, + private Hash, + private KeyEqual, + private GrowthPolicy { + private: + template + using has_mapped_type = + typename std::integral_constant::value>; + + static_assert( + noexcept(std::declval().bucket_for_hash(std::size_t(0))), + "GrowthPolicy::bucket_for_hash must be noexcept."); + static_assert(noexcept(std::declval().clear()), + "GrowthPolicy::clear must be noexcept."); + + public: + template + class sparse_iterator; + + using key_type = typename KeySelect::key_type; + using value_type = ValueType; + using hasher = Hash; + using key_equal = KeyEqual; + using allocator_type = Allocator; + using reference = value_type &; + using const_reference = const value_type &; + using size_type = typename std::allocator_traits::size_type; + using pointer = typename std::allocator_traits::pointer; + using const_pointer = typename std::allocator_traits::const_pointer; + using difference_type = typename std::allocator_traits::difference_type; + using iterator = sparse_iterator; + using const_iterator = sparse_iterator; + + private: + using sparse_array = + dice::sparse_map::detail_sparse_hash::sparse_array; + + using sparse_buckets_allocator = typename std::allocator_traits< + allocator_type>::template rebind_alloc; + using sparse_buckets_container = + boost::container::vector; + + public: + /** + * The `operator*()` and `operator->()` methods return a const reference and + * const pointer respectively to the stored value type (`Key` for a set, + * `std::pair` for a map). + * + * In case of a map, to get a mutable reference to the value `T` associated to + * a key (the `.second` in the stored pair), you have to call `value()`. + */ + template + class sparse_iterator { + friend class sparse_hash; + + private: + using sparse_bucket_iterator = typename std::conditional< + IsConst, typename sparse_buckets_container::const_iterator, + typename sparse_buckets_container::iterator>::type; + + using sparse_array_iterator = + typename std::conditional::type; + + /** + * sparse_array_it should be nullptr if sparse_bucket_it == + * m_sparse_buckets_data.end(). (TODO better way?) + */ + sparse_iterator(sparse_bucket_iterator sparse_bucket_it, + sparse_array_iterator sparse_array_it) + : m_sparse_buckets_it(sparse_bucket_it), + m_sparse_array_it(sparse_array_it) {} + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = const typename sparse_hash::value_type; + using difference_type = std::ptrdiff_t; + using reference = value_type &; + using pointer = typename sparse_hash::const_pointer; + + sparse_iterator() noexcept {} + + // Copy constructor from iterator to const_iterator. + template::type * = nullptr> + sparse_iterator(const sparse_iterator &other) noexcept + : m_sparse_buckets_it(other.m_sparse_buckets_it), + m_sparse_array_it(other.m_sparse_array_it) {} + + sparse_iterator(const sparse_iterator &other) = default; + sparse_iterator(sparse_iterator &&other) = default; + sparse_iterator &operator=(const sparse_iterator &other) = default; + sparse_iterator &operator=(sparse_iterator &&other) = default; + + const typename sparse_hash::key_type &key() const { + return KeySelect()(*m_sparse_array_it); + } + + template::value && + IsConst>::type * = nullptr> + const typename U::value_type &value() const { + return U()(*m_sparse_array_it); + } + + template::value && + !IsConst>::type * = nullptr> + typename U::value_type &value() { + return U()(*m_sparse_array_it); + } + + reference operator*() const { return *m_sparse_array_it; } + + //with fancy pointers addressof might be problematic. + pointer operator->() const { return std::addressof(*m_sparse_array_it); } + + sparse_iterator &operator++() { + tsl_sh_assert(m_sparse_array_it != nullptr); + ++m_sparse_array_it; + + //vector iterator with fancy pointers have a problem with -> + if (m_sparse_array_it == (*m_sparse_buckets_it).end()) { + do { + if ((*m_sparse_buckets_it).last()) { + ++m_sparse_buckets_it; + m_sparse_array_it = nullptr; + return *this; + } + + ++m_sparse_buckets_it; + } while ((*m_sparse_buckets_it).empty()); + + m_sparse_array_it = (*m_sparse_buckets_it).begin(); + } + + return *this; + } + + sparse_iterator operator++(int) { + sparse_iterator tmp(*this); + ++*this; + + return tmp; + } + + friend bool operator==(const sparse_iterator &lhs, + const sparse_iterator &rhs) { + return lhs.m_sparse_buckets_it == rhs.m_sparse_buckets_it && + lhs.m_sparse_array_it == rhs.m_sparse_array_it; + } + + friend bool operator!=(const sparse_iterator &lhs, + const sparse_iterator &rhs) { + return !(lhs == rhs); + } + + private: + sparse_bucket_iterator m_sparse_buckets_it; + sparse_array_iterator m_sparse_array_it; + }; + + public: + sparse_hash(size_type bucket_count, const Hash &hash, const KeyEqual &equal, + const Allocator &alloc, float max_load_factor) + : Allocator(alloc), + Hash(hash), + KeyEqual(equal), + GrowthPolicy(bucket_count), + m_sparse_buckets_data(alloc), + // m_sparse_buckets_data(std::allocator_traits::rebind_alloc(alloc)), + m_sparse_buckets(static_empty_sparse_bucket_ptr()), + m_bucket_count(bucket_count), + m_nb_elements(0), + m_nb_deleted_buckets(0) { + if (m_bucket_count > max_bucket_count()) { + throw std::length_error("The map exceeds its maximum size."); + } + + if (m_bucket_count > 0) { + /* + * We can't use the `vector(size_type count, const Allocator& alloc)` + * constructor as it's only available in C++14 and we need to support + * C++11. We thus must resize after using the `vector(const Allocator& + * alloc)` constructor. + * + * We can't use `vector(size_type count, const T& value, const Allocator& + * alloc)` as it requires the value T to be copyable. + */ + m_sparse_buckets_data.resize( + sparse_array::nb_sparse_buckets(bucket_count)); + m_sparse_buckets = m_sparse_buckets_data.data(); + + tsl_sh_assert(!m_sparse_buckets_data.empty()); + m_sparse_buckets_data.back().set_as_last(); + } + + this->max_load_factor(max_load_factor); + + // Check in the constructor instead of outside of a function to avoid + // compilation issues when value_type is not complete. + static_assert(std::is_nothrow_move_constructible::value || + std::is_copy_constructible::value, + "Key, and T if present, must be nothrow move constructible " + "and/or copy constructible."); + } + + ~sparse_hash() { clear(); } + + sparse_hash(const sparse_hash &other) + : Allocator(std::allocator_traits< + Allocator>::select_on_container_copy_construction(other)), + Hash(other), + KeyEqual(other), + GrowthPolicy(other), + m_sparse_buckets_data( + std::allocator_traits< + Allocator>::select_on_container_copy_construction(other)), + m_bucket_count(other.m_bucket_count), + m_nb_elements(other.m_nb_elements), + m_nb_deleted_buckets(other.m_nb_deleted_buckets), + m_load_threshold_rehash(other.m_load_threshold_rehash), + m_load_threshold_clear_deleted(other.m_load_threshold_clear_deleted), + m_max_load_factor(other.m_max_load_factor) { + copy_buckets_from(other), + m_sparse_buckets = m_sparse_buckets_data.empty() + ? static_empty_sparse_bucket_ptr() + : m_sparse_buckets_data.data(); + } + + sparse_hash(sparse_hash &&other) noexcept( + std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value) + : Allocator(std::move(other)), + Hash(std::move(other)), + KeyEqual(std::move(other)), + GrowthPolicy(std::move(other)), + m_sparse_buckets_data(std::move(other.m_sparse_buckets_data)), + m_sparse_buckets(m_sparse_buckets_data.empty() + ? static_empty_sparse_bucket_ptr() + : m_sparse_buckets_data.data()), + m_bucket_count(other.m_bucket_count), + m_nb_elements(other.m_nb_elements), + m_nb_deleted_buckets(other.m_nb_deleted_buckets), + m_load_threshold_rehash(other.m_load_threshold_rehash), + m_load_threshold_clear_deleted(other.m_load_threshold_clear_deleted), + m_max_load_factor(other.m_max_load_factor) { + other.GrowthPolicy::clear(); + other.m_sparse_buckets_data.clear(); + other.m_sparse_buckets = static_empty_sparse_bucket_ptr(); + other.m_bucket_count = 0; + other.m_nb_elements = 0; + other.m_nb_deleted_buckets = 0; + other.m_load_threshold_rehash = 0; + other.m_load_threshold_clear_deleted = 0; + } + + sparse_hash &operator=(const sparse_hash &other) { + if (this != &other) { + clear(); + + if (std::allocator_traits< + Allocator>::propagate_on_container_copy_assignment::value) { + Allocator::operator=(other); + } + + Hash::operator=(other); + KeyEqual::operator=(other); + GrowthPolicy::operator=(other); + + if (std::allocator_traits< + Allocator>::propagate_on_container_copy_assignment::value) { + m_sparse_buckets_data = + sparse_buckets_container(static_cast(other)); + } else { + if (m_sparse_buckets_data.size() != + other.m_sparse_buckets_data.size()) { + m_sparse_buckets_data = + sparse_buckets_container(static_cast(*this)); + } else { + m_sparse_buckets_data.clear(); + } + } + + copy_buckets_from(other); + m_sparse_buckets = m_sparse_buckets_data.empty() + ? static_empty_sparse_bucket_ptr() + : m_sparse_buckets_data.data(); + + m_bucket_count = other.m_bucket_count; + m_nb_elements = other.m_nb_elements; + m_nb_deleted_buckets = other.m_nb_deleted_buckets; + m_load_threshold_rehash = other.m_load_threshold_rehash; + m_load_threshold_clear_deleted = other.m_load_threshold_clear_deleted; + m_max_load_factor = other.m_max_load_factor; + } + + return *this; + } + + sparse_hash &operator=(sparse_hash &&other) noexcept { + clear(); + + if (not std::allocator_traits< + Allocator>::propagate_on_container_move_assignment::value and + (static_cast(*this) != static_cast(other))) { + move_buckets_from(std::move(other)); + } else { + static_cast(*this) = std::move(static_cast(other)); + m_sparse_buckets_data = std::move(other.m_sparse_buckets_data); + } + + m_sparse_buckets = m_sparse_buckets_data.empty() + ? static_empty_sparse_bucket_ptr() + : m_sparse_buckets_data.data(); + + static_cast(*this) = std::move(static_cast(other)); + static_cast(*this) = std::move(static_cast(other)); + static_cast(*this) = + std::move(static_cast(other)); + m_bucket_count = other.m_bucket_count; + m_nb_elements = other.m_nb_elements; + m_nb_deleted_buckets = other.m_nb_deleted_buckets; + m_load_threshold_rehash = other.m_load_threshold_rehash; + m_load_threshold_clear_deleted = other.m_load_threshold_clear_deleted; + m_max_load_factor = other.m_max_load_factor; + + other.GrowthPolicy::clear(); + other.m_sparse_buckets_data.clear(); + other.m_sparse_buckets = static_empty_sparse_bucket_ptr(); + other.m_bucket_count = 0; + other.m_nb_elements = 0; + other.m_nb_deleted_buckets = 0; + other.m_load_threshold_rehash = 0; + other.m_load_threshold_clear_deleted = 0; + + return *this; + } + + allocator_type get_allocator() const { + return static_cast(*this); + } + + iterator begin() noexcept { + auto begin = m_sparse_buckets_data.begin(); + //vector iterator with fancy pointers have a problem with -> + while (begin != m_sparse_buckets_data.end() && (*begin).empty()) { + ++begin; + } + + //vector iterator with fancy pointers have a problem with -> + return iterator(begin, (begin != m_sparse_buckets_data.end()) + ? (*begin).begin() + : nullptr); + } + + const_iterator begin() const noexcept { return cbegin(); } + + const_iterator cbegin() const noexcept { + auto begin = m_sparse_buckets_data.cbegin(); + //vector iterator with fancy pointers have a problem with -> + while (begin != m_sparse_buckets_data.cend() && (*begin).empty()) { + ++begin; + } + + return const_iterator(begin, (begin != m_sparse_buckets_data.cend()) + ? (*begin).cbegin() + : nullptr); + } + + iterator end() noexcept { + return iterator(m_sparse_buckets_data.end(), nullptr); + } + + const_iterator end() const noexcept { return cend(); } + + const_iterator cend() const noexcept { + return const_iterator(m_sparse_buckets_data.cend(), nullptr); + } + + bool empty() const noexcept { return m_nb_elements == 0; } + + size_type size() const noexcept { return m_nb_elements; } + + size_type max_size() const noexcept { + return std::min(std::allocator_traits::max_size(), + m_sparse_buckets_data.max_size()); + } + + void clear() noexcept { + for (auto &bucket : m_sparse_buckets_data) { + bucket.clear(*this); + } + + m_nb_elements = 0; + m_nb_deleted_buckets = 0; + } + + template + std::pair insert(P &&value) { + return insert_impl(KeySelect()(value), std::forward

(value)); + } + + template + iterator insert_hint(const_iterator hint, P &&value) { + if (hint != cend() && + compare_keys(KeySelect()(*hint), KeySelect()(value))) { + return mutable_iterator(hint); + } + + return insert(std::forward

(value)).first; + } + + template + void insert(InputIt first, InputIt last) { + if (std::is_base_of< + std::forward_iterator_tag, + typename std::iterator_traits::iterator_category>::value) { + const auto nb_elements_insert = std::distance(first, last); + const size_type nb_free_buckets = m_load_threshold_rehash - size(); + tsl_sh_assert(m_load_threshold_rehash >= size()); + + if (nb_elements_insert > 0 && + nb_free_buckets < size_type(nb_elements_insert)) { + reserve(size() + size_type(nb_elements_insert)); + } + } + + for (; first != last; ++first) { + insert(*first); + } + } + + template + std::pair insert_or_assign(K &&key, M &&obj) { + auto it = try_emplace(std::forward(key), std::forward(obj)); + if (!it.second) { + it.first.value() = std::forward(obj); + } + + return it; + } + + template + iterator insert_or_assign(const_iterator hint, K &&key, M &&obj) { + if (hint != cend() && compare_keys(KeySelect()(*hint), key)) { + auto it = mutable_iterator(hint); + it.value() = std::forward(obj); + + return it; + } + + return insert_or_assign(std::forward(key), std::forward(obj)).first; + } + + template + std::pair emplace(Args &&...args) { + return insert(value_type(std::forward(args)...)); + } + + template + iterator emplace_hint(const_iterator hint, Args &&...args) { + return insert_hint(hint, value_type(std::forward(args)...)); + } + + template + std::pair try_emplace(K &&key, Args &&...args) { + return insert_impl(key, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(args)...)); + } + + template + iterator try_emplace_hint(const_iterator hint, K &&key, Args &&...args) { + if (hint != cend() && compare_keys(KeySelect()(*hint), key)) { + return mutable_iterator(hint); + } + + return try_emplace(std::forward(key), std::forward(args)...).first; + } + + /** + * Here to avoid `template size_type erase(const K& key)` being used + * when we use an iterator instead of a const_iterator. + */ + iterator erase(iterator pos) { + tsl_sh_assert(pos != end() && m_nb_elements > 0); + //vector iterator with fancy pointers have a problem with -> + auto it_sparse_array_next = + (*pos.m_sparse_buckets_it).erase(*this, pos.m_sparse_array_it); + m_nb_elements--; + m_nb_deleted_buckets++; + + if (it_sparse_array_next == (*pos.m_sparse_buckets_it).end()) { + auto it_sparse_buckets_next = pos.m_sparse_buckets_it; + do { + ++it_sparse_buckets_next; + } while (it_sparse_buckets_next != m_sparse_buckets_data.end() && + (*it_sparse_buckets_next).empty()); + + if (it_sparse_buckets_next == m_sparse_buckets_data.end()) { + return end(); + } else { + return iterator(it_sparse_buckets_next, + (*it_sparse_buckets_next).begin()); + } + } else { + return iterator(pos.m_sparse_buckets_it, it_sparse_array_next); + } + } + + iterator erase(const_iterator pos) { return erase(mutable_iterator(pos)); } + + iterator erase(const_iterator first, const_iterator last) { + if (first == last) { + return mutable_iterator(first); + } + + // TODO Optimize, could avoid the call to std::distance. + const size_type nb_elements_to_erase = + static_cast(std::distance(first, last)); + auto to_delete = mutable_iterator(first); + for (size_type i = 0; i < nb_elements_to_erase; i++) { + to_delete = erase(to_delete); + } + + return to_delete; + } + + template + size_type erase(const K &key) { + return erase(key, hash_key(key)); + } + + template + size_type erase(const K &key, std::size_t hash) { + return erase_impl(key, hash); + } + + void swap(sparse_hash &other) { + using std::swap; + + if (std::allocator_traits::propagate_on_container_swap::value) { + swap(static_cast(*this), static_cast(other)); + } else { + tsl_sh_assert(static_cast(*this) == + static_cast(other)); + } + + swap(static_cast(*this), static_cast(other)); + swap(static_cast(*this), static_cast(other)); + swap(static_cast(*this), + static_cast(other)); + swap(m_sparse_buckets_data, other.m_sparse_buckets_data); + swap(m_sparse_buckets, other.m_sparse_buckets); + swap(m_bucket_count, other.m_bucket_count); + swap(m_nb_elements, other.m_nb_elements); + swap(m_nb_deleted_buckets, other.m_nb_deleted_buckets); + swap(m_load_threshold_rehash, other.m_load_threshold_rehash); + swap(m_load_threshold_clear_deleted, other.m_load_threshold_clear_deleted); + swap(m_max_load_factor, other.m_max_load_factor); + } + + + template< + class K, class U = ValueSelect, + typename std::enable_if::value>::type * = nullptr> + typename U::value_type &at(const K &key) { + return at(key, hash_key(key)); + } + + template< + class K, class U = ValueSelect, + typename std::enable_if::value>::type * = nullptr> + typename U::value_type &at(const K &key, std::size_t hash) { + return const_cast( + static_cast(this)->at(key, hash)); + } + + template< + class K, class U = ValueSelect, + typename std::enable_if::value>::type * = nullptr> + const typename U::value_type &at(const K &key) const { + return at(key, hash_key(key)); + } + + template< + class K, class U = ValueSelect, + typename std::enable_if::value>::type * = nullptr> + const typename U::value_type &at(const K &key, std::size_t hash) const { + auto it = find(key, hash); + if (it != cend()) { + return it.value(); + } else { + throw std::out_of_range("Couldn't find key."); + } + } + + template< + class K, class U = ValueSelect, + typename std::enable_if::value>::type * = nullptr> + typename U::value_type &operator[](K &&key) { + return try_emplace(std::forward(key)).first.value(); + } + + template + bool contains(const K &key) const { + return contains(key, hash_key(key)); + } + + template + bool contains(const K &key, std::size_t hash) const { + return count(key, hash) != 0; + } + + template + size_type count(const K &key) const { + return count(key, hash_key(key)); + } + + template + size_type count(const K &key, std::size_t hash) const { + if (find(key, hash) != cend()) { + return 1; + } else { + return 0; + } + } + + template + iterator find(const K &key) { + return find_impl(key, hash_key(key)); + } + + template + iterator find(const K &key, std::size_t hash) { + return find_impl(key, hash); + } + + template + const_iterator find(const K &key) const { + return find_impl(key, hash_key(key)); + } + + template + const_iterator find(const K &key, std::size_t hash) const { + return find_impl(key, hash); + } + + template + std::pair equal_range(const K &key) { + return equal_range(key, hash_key(key)); + } + + template + std::pair equal_range(const K &key, std::size_t hash) { + iterator it = find(key, hash); + return std::make_pair(it, (it == end()) ? it : std::next(it)); + } + + template + std::pair equal_range(const K &key) const { + return equal_range(key, hash_key(key)); + } + + template + std::pair equal_range( + const K &key, std::size_t hash) const { + const_iterator it = find(key, hash); + return std::make_pair(it, (it == cend()) ? it : std::next(it)); + } + + size_type bucket_count() const { return m_bucket_count; } + + size_type max_bucket_count() const { + return m_sparse_buckets_data.max_size(); + } + + float load_factor() const { + if (bucket_count() == 0) { + return 0; + } + + return float(m_nb_elements) / float(bucket_count()); + } + + float max_load_factor() const { return m_max_load_factor; } + + void max_load_factor(float ml) { + m_max_load_factor = std::max(0.1f, std::min(ml, 0.8f)); + m_load_threshold_rehash = + size_type(float(bucket_count()) * m_max_load_factor); + + const float max_load_factor_with_deleted_buckets = + m_max_load_factor + 0.5f * (1.0f - m_max_load_factor); + tsl_sh_assert(max_load_factor_with_deleted_buckets > 0.0f && + max_load_factor_with_deleted_buckets <= 1.0f); + m_load_threshold_clear_deleted = + size_type(float(bucket_count()) * max_load_factor_with_deleted_buckets); + } + + void rehash(size_type count) { + count = std::max(count, + size_type(std::ceil(float(size()) / max_load_factor()))); + rehash_impl(count); + } + + void reserve(size_type count) { + rehash(size_type(std::ceil(float(count) / max_load_factor()))); + } + + hasher hash_function() const { return static_cast(*this); } + + key_equal key_eq() const { return static_cast(*this); } + + iterator mutable_iterator(const_iterator pos) { + auto it_sparse_buckets = + m_sparse_buckets_data.begin() + + std::distance(m_sparse_buckets_data.cbegin(), pos.m_sparse_buckets_it); + + return iterator(it_sparse_buckets, + sparse_array::mutable_iterator(pos.m_sparse_array_it)); + } + + template + void serialize(Serializer &serializer) const { + serialize_impl(serializer); + } + + template + void deserialize(Deserializer &deserializer, bool hash_compatible) { + deserialize_impl(deserializer, hash_compatible); + } + + private: + template + std::size_t hash_key(const K &key) const { + return Hash::operator()(key); + } + + template + bool compare_keys(const K1 &key1, const K2 &key2) const { + return KeyEqual::operator()(key1, key2); + } + + size_type bucket_for_hash(std::size_t hash) const { + const std::size_t bucket = GrowthPolicy::bucket_for_hash(hash); + tsl_sh_assert(sparse_array::sparse_ibucket(bucket) < + m_sparse_buckets_data.size() || + (bucket == 0 && m_sparse_buckets_data.empty())); + + return bucket; + } + + template::value>::type * = + nullptr> + size_type next_bucket(size_type ibucket, size_type iprobe) const { + (void) iprobe; + if (Probing == dice::sparse_map::sh::probing::linear) { + return (ibucket + 1) & this->m_mask; + } else { + tsl_sh_assert(Probing == dice::sparse_map::sh::probing::quadratic); + return (ibucket + iprobe) & this->m_mask; + } + } + + template::value>::type * = + nullptr> + size_type next_bucket(size_type ibucket, size_type iprobe) const { + (void) iprobe; + if (Probing == dice::sparse_map::sh::probing::linear) { + ibucket++; + return (ibucket != bucket_count()) ? ibucket : 0; + } else { + tsl_sh_assert(Probing == dice::sparse_map::sh::probing::quadratic); + ibucket += iprobe; + return (ibucket < bucket_count()) ? ibucket : ibucket % bucket_count(); + } + } + + // TODO encapsulate m_sparse_buckets_data to avoid the managing the allocator + void copy_buckets_from(const sparse_hash &other) { + m_sparse_buckets_data.reserve(other.m_sparse_buckets_data.size()); + + try { + for (const auto &bucket : other.m_sparse_buckets_data) { + m_sparse_buckets_data.emplace_back(bucket, + static_cast(*this)); + } + } catch (...) { + clear(); + throw; + } + + tsl_sh_assert(m_sparse_buckets_data.empty() || + m_sparse_buckets_data.back().last()); + } + + void move_buckets_from(sparse_hash &&other) { + m_sparse_buckets_data.reserve(other.m_sparse_buckets_data.size()); + + try { + for (auto &&bucket : other.m_sparse_buckets_data) { + m_sparse_buckets_data.emplace_back(std::move(bucket), + static_cast(*this)); + } + } catch (...) { + clear(); + throw; + } + + tsl_sh_assert(m_sparse_buckets_data.empty() || + m_sparse_buckets_data.back().last()); + } + + template + std::pair insert_impl(const K &key, + Args &&...value_type_args) { + if (size() >= m_load_threshold_rehash) { + rehash_impl(GrowthPolicy::next_bucket_count()); + } else if (size() + m_nb_deleted_buckets >= + m_load_threshold_clear_deleted) { + clear_deleted_buckets(); + } + tsl_sh_assert(!m_sparse_buckets_data.empty()); + + /** + * We must insert the value in the first empty or deleted bucket we find. If + * we first find a deleted bucket, we still have to continue the search + * until we find an empty bucket or until we have searched all the buckets + * to be sure that the value is not in the hash table. We thus remember the + * position, if any, of the first deleted bucket we have encountered so we + * can insert it there if needed. + */ + bool found_first_deleted_bucket = false; + std::size_t sparse_ibucket_first_deleted = 0; + typename sparse_array::size_type index_in_sparse_bucket_first_deleted = 0; + + const std::size_t hash = hash_key(key); + std::size_t ibucket = bucket_for_hash(hash); + + std::size_t probe = 0; + while (true) { + std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); + auto index_in_sparse_bucket = + sparse_array::index_in_sparse_bucket(ibucket); + + if (m_sparse_buckets != static_empty_sparse_bucket_ptr()) { + if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { + auto value_it = + m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); + if (compare_keys(key, KeySelect()(*value_it))) { + return std::make_pair( + iterator(m_sparse_buckets_data.begin() + sparse_ibucket, + value_it), + false); + } + } else if (m_sparse_buckets[sparse_ibucket].has_deleted_value( + index_in_sparse_bucket) && + probe < m_bucket_count) { + if (!found_first_deleted_bucket) { + found_first_deleted_bucket = true; + sparse_ibucket_first_deleted = sparse_ibucket; + index_in_sparse_bucket_first_deleted = index_in_sparse_bucket; + } + } else if (found_first_deleted_bucket) { + auto it = insert_in_bucket(sparse_ibucket_first_deleted, + index_in_sparse_bucket_first_deleted, + std::forward(value_type_args)...); + m_nb_deleted_buckets--; + + return it; + } else { + return insert_in_bucket(sparse_ibucket, index_in_sparse_bucket, + std::forward(value_type_args)...); + } + } else { + return insert_in_bucket(sparse_ibucket, index_in_sparse_bucket, + std::forward(value_type_args)...); + } + + probe++; + ibucket = next_bucket(ibucket, probe); + } + } + + template + std::pair insert_in_bucket( + std::size_t sparse_ibucket, + typename sparse_array::size_type index_in_sparse_bucket, + Args &&...value_type_args) { + // is not called when empty + auto value_it = m_sparse_buckets[sparse_ibucket].set( + *this, index_in_sparse_bucket, std::forward(value_type_args)...); + m_nb_elements++; + + return std::make_pair( + iterator(m_sparse_buckets_data.begin() + sparse_ibucket, value_it), + true); + } + + template + size_type erase_impl(const K &key, std::size_t hash) { + std::size_t ibucket = bucket_for_hash(hash); + + std::size_t probe = 0; + + if (m_sparse_buckets == static_empty_sparse_bucket_ptr()) + return 0; + while (true) { + const std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); + const auto index_in_sparse_bucket = + sparse_array::index_in_sparse_bucket(ibucket); + + if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { + auto value_it = + m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); + if (compare_keys(key, KeySelect()(*value_it))) { + m_sparse_buckets[sparse_ibucket].erase(*this, value_it, + index_in_sparse_bucket); + m_nb_elements--; + m_nb_deleted_buckets++; + + return 1; + } + } else if (!m_sparse_buckets[sparse_ibucket].has_deleted_value( + index_in_sparse_bucket) || + probe >= m_bucket_count) { + return 0; + } + + probe++; + ibucket = next_bucket(ibucket, probe); + } + } + + template + iterator find_impl(const K &key, std::size_t hash) { + return mutable_iterator( + static_cast(this)->find(key, hash)); + } + + template + const_iterator find_impl(const K &key, std::size_t hash) const { + std::size_t ibucket = bucket_for_hash(hash); + + std::size_t probe = 0; + while (true) { + const std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); + const auto index_in_sparse_bucket = + sparse_array::index_in_sparse_bucket(ibucket); + + if (m_sparse_buckets == static_empty_sparse_bucket_ptr()) { + return cend(); + } + if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { + auto value_it = + m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); + if (compare_keys(key, KeySelect()(*value_it))) { + return const_iterator(m_sparse_buckets_data.cbegin() + sparse_ibucket, + value_it); + } + } else if (!m_sparse_buckets[sparse_ibucket].has_deleted_value( + index_in_sparse_bucket) || + probe >= m_bucket_count) { + return cend(); + } + + probe++; + ibucket = next_bucket(ibucket, probe); + } + } + + void clear_deleted_buckets() { + // TODO could be optimized, we could do it in-place instead of allocating a + // new bucket array. + rehash_impl(m_bucket_count); + tsl_sh_assert(m_nb_deleted_buckets == 0); + } + + template::type + * = nullptr> + void rehash_impl(size_type count) { + sparse_hash new_table(count, static_cast(*this), + static_cast(*this), + static_cast(*this), m_max_load_factor); + + for (auto &bucket : m_sparse_buckets_data) { + for (auto &val : bucket) { + new_table.insert_on_rehash(std::move(val)); + } + + // TODO try to reuse some of the memory + bucket.clear(*this); + } + + new_table.swap(*this); + } + + /** + * TODO: For now we copy each element into the new map. We could move + * them if they are nothrow_move_constructible without triggering + * any exception if we reserve enough space in the sparse arrays beforehand. + */ + template::type * = nullptr> + void rehash_impl(size_type count) { + sparse_hash new_table(count, static_cast(*this), + static_cast(*this), + static_cast(*this), m_max_load_factor); + + for (const auto &bucket : m_sparse_buckets_data) { + for (const auto &val : bucket) { + new_table.insert_on_rehash(val); + } + } + + new_table.swap(*this); + } + + template + void insert_on_rehash(K &&key_value) { + const key_type &key = KeySelect()(key_value); + + const std::size_t hash = hash_key(key); + std::size_t ibucket = bucket_for_hash(hash); + + std::size_t probe = 0; + while (true) { + std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); + auto index_in_sparse_bucket = + sparse_array::index_in_sparse_bucket(ibucket); + + if (!m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { + m_sparse_buckets[sparse_ibucket].set(*this, index_in_sparse_bucket, + std::forward(key_value)); + m_nb_elements++; + + return; + } else { + tsl_sh_assert(!compare_keys( + key, KeySelect()(*m_sparse_buckets[sparse_ibucket].value( + index_in_sparse_bucket)))); + } + + probe++; + ibucket = next_bucket(ibucket, probe); + } + } + + template + void serialize_impl(Serializer &serializer) const { + const slz_size_type version = SERIALIZATION_PROTOCOL_VERSION; + serializer(version); + + const slz_size_type bucket_count = m_bucket_count; + serializer(bucket_count); + + const slz_size_type nb_sparse_buckets = m_sparse_buckets_data.size(); + serializer(nb_sparse_buckets); + + const slz_size_type nb_elements = m_nb_elements; + serializer(nb_elements); + + const slz_size_type nb_deleted_buckets = m_nb_deleted_buckets; + serializer(nb_deleted_buckets); + + const float max_load_factor = m_max_load_factor; + serializer(max_load_factor); + + for (const auto &bucket : m_sparse_buckets_data) { + bucket.serialize(serializer); + } + } + + template + void deserialize_impl(Deserializer &deserializer, bool hash_compatible) { + tsl_sh_assert( + m_bucket_count == 0 && + m_sparse_buckets_data.empty());// Current hash table must be empty + + const slz_size_type version = + deserialize_value(deserializer); + // For now we only have one version of the serialization protocol. + // If it doesn't match there is a problem with the file. + if (version != SERIALIZATION_PROTOCOL_VERSION) { + throw std::runtime_error( + "Can't deserialize the sparse_map/set. The " + "protocol version header is invalid."); + } + + const slz_size_type bucket_count_ds = + deserialize_value(deserializer); + const slz_size_type nb_sparse_buckets = + deserialize_value(deserializer); + const slz_size_type nb_elements = + deserialize_value(deserializer); + const slz_size_type nb_deleted_buckets = + deserialize_value(deserializer); + const float max_load_factor = deserialize_value(deserializer); + + if (!hash_compatible) { + this->max_load_factor(max_load_factor); + reserve(numeric_cast(nb_elements, + "Deserialized nb_elements is too big.")); + for (slz_size_type ibucket = 0; ibucket < nb_sparse_buckets; ibucket++) { + sparse_array::deserialize_values_into_sparse_hash(deserializer, *this); + } + } else { + m_bucket_count = numeric_cast( + bucket_count_ds, "Deserialized bucket_count is too big."); + + GrowthPolicy::operator=(GrowthPolicy(m_bucket_count)); + // GrowthPolicy should not modify the bucket count we got from + // deserialization + if (m_bucket_count != bucket_count_ds) { + throw std::runtime_error( + "The GrowthPolicy is not the same even though " + "hash_compatible is true."); + } + + if (nb_sparse_buckets != + sparse_array::nb_sparse_buckets(m_bucket_count)) { + throw std::runtime_error("Deserialized nb_sparse_buckets is invalid."); + } + + m_nb_elements = numeric_cast( + nb_elements, "Deserialized nb_elements is too big."); + m_nb_deleted_buckets = numeric_cast( + nb_deleted_buckets, "Deserialized nb_deleted_buckets is too big."); + + m_sparse_buckets_data.reserve(numeric_cast( + nb_sparse_buckets, "Deserialized nb_sparse_buckets is too big.")); + for (slz_size_type ibucket = 0; ibucket < nb_sparse_buckets; ibucket++) { + m_sparse_buckets_data.emplace_back( + sparse_array::deserialize_hash_compatible( + deserializer, static_cast(*this))); + } + + if (!m_sparse_buckets_data.empty()) { + m_sparse_buckets_data.back().set_as_last(); + m_sparse_buckets = m_sparse_buckets_data.data(); + } + + this->max_load_factor(max_load_factor); + if (load_factor() > this->max_load_factor()) { + throw std::runtime_error( + "Invalid max_load_factor. Check that the serializer and " + "deserializer support " + "floats correctly as they can be converted implicitely to ints."); + } + } + } + + public: + static const size_type DEFAULT_INIT_BUCKET_COUNT = 0; + static constexpr float DEFAULT_MAX_LOAD_FACTOR = 0.5f; + + /** + * Protocol version currenlty used for serialization. + */ + static const slz_size_type SERIALIZATION_PROTOCOL_VERSION = 1; + + using sparse_array_ptr = typename std::allocator_traits::template rebind_traits::pointer; + /** + * Return an nullptr to indicate an empty bucket + */ + static sparse_array_ptr static_empty_sparse_bucket_ptr() { + return {}; + } + + private: + sparse_buckets_container m_sparse_buckets_data; + + + /** + * Points to m_sparse_buckets_data.data() if !m_sparse_buckets_data.empty() + * otherwise points to static_empty_sparse_bucket_ptr. This variable is useful + * to avoid the cost of checking if m_sparse_buckets_data is empty when trying + * to find an element. + * + * TODO Remove m_sparse_buckets_data and only use a pointer instead of a + * pointer+vector to save some space in the sparse_hash object. + */ + sparse_array_ptr m_sparse_buckets; + + size_type m_bucket_count; + size_type m_nb_elements; + size_type m_nb_deleted_buckets; + + /** + * Maximum that m_nb_elements can reach before a rehash occurs automatically + * to grow the hash table. + */ + size_type m_load_threshold_rehash; + + /** + * Maximum that m_nb_elements + m_nb_deleted_buckets can reach before cleaning + * up the buckets marked as deleted. + */ + size_type m_load_threshold_clear_deleted; + float m_max_load_factor; + }; + + }// namespace detail_sparse_hash +}// namespace dice::sparse_map #endif diff --git a/include/dice/sparse-map/sparse_map.hpp b/include/dice/sparse-map/sparse_map.hpp index 8dc234c..4230990 100644 --- a/include/dice/sparse-map/sparse_map.hpp +++ b/include/dice/sparse-map/sparse_map.hpp @@ -31,771 +31,744 @@ #include #include -#include "dice/sparse-map/sparse_hash.hpp" #include "dice/sparse-map/boost_offset_pointer.hpp" +#include "dice/sparse-map/sparse_hash.hpp" namespace dice::sparse_map { -/** - * Implementation of a sparse hash map using open-addressing with quadratic - * probing. The goal on the hash map is to be the most memory efficient - * possible, even at low load factor, while keeping reasonable performances. - * - * `GrowthPolicy` defines how the map grows and consequently how a hash value is - * mapped to a bucket. By default the map uses - * `dice::sh::power_of_two_growth_policy`. This policy keeps the number of - * buckets to a power of two and uses a mask to map the hash to a bucket instead - * of the slow modulo. Other growth policies are available and you may define - * your own growth policy, check `dice::sh::power_of_two_growth_policy` for the - * interface. - * - * `ExceptionSafety` defines the exception guarantee provided by the class. By - * default only the basic exception safety is guaranteed which mean that all - * resources used by the hash map will be freed (no memory leaks) but the hash - * map may end-up in an undefined state if an exception is thrown (undefined - * here means that some elements may be missing). This can ONLY happen on rehash - * (either on insert or if `rehash` is called explicitly) and will occur if the - * Allocator can't allocate memory (`std::bad_alloc`) or if the copy constructor - * (when a nothrow move constructor is not available) throws an exception. This - * can be avoided by calling `reserve` beforehand. This basic guarantee is - * similar to the one of `google::sparse_hash_map` and `spp::sparse_hash_map`. - * It is possible to ask for the strong exception guarantee with - * `dice::sh::exception_safety::strong`, the drawback is that the map will be - * slower on rehashes and will also need more memory on rehashes. - * - * `Sparsity` defines how much the hash set will compromise between insertion - * speed and memory usage. A high sparsity means less memory usage but longer - * insertion times, and vice-versa for low sparsity. The default - * `dice::sh::sparsity::medium` sparsity offers a good compromise. It doesn't - * change the lookup speed. - * - * `Key` and `T` must be nothrow move constructible and/or copy constructible. - * - * If the destructor of `Key` or `T` throws an exception, the behaviour of the - * class is undefined. - * - * Iterators invalidation: - * - clear, operator=, reserve, rehash: always invalidate the iterators. - * - insert, emplace, emplace_hint, operator[]: if there is an effective - * insert, invalidate the iterators. - * - erase: always invalidate the iterators. - */ -template , - class KeyEqual = std::equal_to, - class Allocator = std::allocator>, - class GrowthPolicy = dice::sparse_map::sh::power_of_two_growth_policy<2>, - dice::sparse_map::sh::exception_safety ExceptionSafety = - dice::sparse_map::sh::exception_safety::basic, - dice::sparse_map::sh::sparsity Sparsity = dice::sparse_map::sh::sparsity::medium> -class sparse_map { - private: - template - using has_is_transparent = dice::sparse_map::detail_sparse_hash::has_is_transparent; - - class KeySelect { - public: - using key_type = Key; - - const key_type &operator()( - const std::pair &key_value) const noexcept { - return key_value.first; - } - - key_type &operator()(std::pair &key_value) noexcept { - return key_value.first; - } - }; - - class ValueSelect { - public: - using value_type = T; - - const value_type &operator()( - const std::pair &key_value) const noexcept { - return key_value.second; - } - - value_type &operator()(std::pair &key_value) noexcept { - return key_value.second; - } - }; - - using ht = detail_sparse_hash::sparse_hash< - std::pair, KeySelect, ValueSelect, Hash, KeyEqual, Allocator, - GrowthPolicy, ExceptionSafety, Sparsity, dice::sparse_map::sh::probing::quadratic>; - - public: - using key_type = typename ht::key_type; - using mapped_type = T; - using value_type = typename ht::value_type; - using size_type = typename ht::size_type; - using difference_type = typename ht::difference_type; - using hasher = typename ht::hasher; - using key_equal = typename ht::key_equal; - using allocator_type = typename ht::allocator_type; - using reference = typename ht::reference; - using const_reference = typename ht::const_reference; - using pointer = typename ht::pointer; - using const_pointer = typename ht::const_pointer; - using iterator = typename ht::iterator; - using const_iterator = typename ht::const_iterator; - - public: - /* - * Constructors - */ - sparse_map() : sparse_map(ht::DEFAULT_INIT_BUCKET_COUNT) {} - - explicit sparse_map(size_type bucket_count, const Hash &hash = Hash(), - const KeyEqual &equal = KeyEqual(), - const Allocator &alloc = Allocator()) - : m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) {} - - sparse_map(size_type bucket_count, const Allocator &alloc) - : sparse_map(bucket_count, Hash(), KeyEqual(), alloc) {} - - sparse_map(size_type bucket_count, const Hash &hash, const Allocator &alloc) - : sparse_map(bucket_count, hash, KeyEqual(), alloc) {} - - explicit sparse_map(const Allocator &alloc) - : sparse_map(ht::DEFAULT_INIT_BUCKET_COUNT, alloc) {} - - template - sparse_map(InputIt first, InputIt last, - size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, - const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), - const Allocator &alloc = Allocator()) - : sparse_map(bucket_count, hash, equal, alloc) { - insert(first, last); - } - - template - sparse_map(InputIt first, InputIt last, size_type bucket_count, - const Allocator &alloc) - : sparse_map(first, last, bucket_count, Hash(), KeyEqual(), alloc) {} - - template - sparse_map(InputIt first, InputIt last, size_type bucket_count, - const Hash &hash, const Allocator &alloc) - : sparse_map(first, last, bucket_count, hash, KeyEqual(), alloc) {} - - sparse_map(std::initializer_list init, - size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, - const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), - const Allocator &alloc = Allocator()) - : sparse_map(init.begin(), init.end(), bucket_count, hash, equal, alloc) { - } - - sparse_map(std::initializer_list init, size_type bucket_count, - const Allocator &alloc) - : sparse_map(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), - alloc) {} - - sparse_map(std::initializer_list init, size_type bucket_count, - const Hash &hash, const Allocator &alloc) - : sparse_map(init.begin(), init.end(), bucket_count, hash, KeyEqual(), - alloc) {} - - sparse_map &operator=(std::initializer_list ilist) { - m_ht.clear(); - - m_ht.reserve(ilist.size()); - m_ht.insert(ilist.begin(), ilist.end()); - - return *this; - } - - allocator_type get_allocator() const { return m_ht.get_allocator(); } - - /* - * Iterators - */ - iterator begin() noexcept { return m_ht.begin(); } - const_iterator begin() const noexcept { return m_ht.begin(); } - const_iterator cbegin() const noexcept { return m_ht.cbegin(); } - - iterator end() noexcept { return m_ht.end(); } - const_iterator end() const noexcept { return m_ht.end(); } - const_iterator cend() const noexcept { return m_ht.cend(); } - - /* - * Capacity - */ - bool empty() const noexcept { return m_ht.empty(); } - size_type size() const noexcept { return m_ht.size(); } - size_type max_size() const noexcept { return m_ht.max_size(); } - - /* - * Modifiers - */ - void clear() noexcept { m_ht.clear(); } - - std::pair insert(const value_type &value) { - return m_ht.insert(value); - } - - template ::value>::type * = nullptr> - std::pair insert(P &&value) { - return m_ht.emplace(std::forward

(value)); - } - - std::pair insert(value_type &&value) { - return m_ht.insert(std::move(value)); - } - - iterator insert(const_iterator hint, const value_type &value) { - return m_ht.insert_hint(hint, value); - } - - template ::value>::type * = nullptr> - iterator insert(const_iterator hint, P &&value) { - return m_ht.emplace_hint(hint, std::forward

(value)); - } - - iterator insert(const_iterator hint, value_type &&value) { - return m_ht.insert_hint(hint, std::move(value)); - } - - template - void insert(InputIt first, InputIt last) { - m_ht.insert(first, last); - } - - void insert(std::initializer_list ilist) { - m_ht.insert(ilist.begin(), ilist.end()); - } - - template - std::pair insert_or_assign(const key_type &k, M &&obj) { - return m_ht.insert_or_assign(k, std::forward(obj)); - } - - template - std::pair insert_or_assign(key_type &&k, M &&obj) { - return m_ht.insert_or_assign(std::move(k), std::forward(obj)); - } - - template - iterator insert_or_assign(const_iterator hint, const key_type &k, M &&obj) { - return m_ht.insert_or_assign(hint, k, std::forward(obj)); - } - - template - iterator insert_or_assign(const_iterator hint, key_type &&k, M &&obj) { - return m_ht.insert_or_assign(hint, std::move(k), std::forward(obj)); - } - - /** - * Due to the way elements are stored, emplace will need to move or copy the - * key-value once. The method is equivalent to - * `insert(value_type(std::forward(args)...));`. - * - * Mainly here for compatibility with the `std::unordered_map` interface. - */ - template - std::pair emplace(Args &&...args) { - return m_ht.emplace(std::forward(args)...); - } - - /** - * Due to the way elements are stored, emplace_hint will need to move or copy - * the key-value once. The method is equivalent to `insert(hint, - * value_type(std::forward(args)...));`. - * - * Mainly here for compatibility with the `std::unordered_map` interface. - */ - template - iterator emplace_hint(const_iterator hint, Args &&...args) { - return m_ht.emplace_hint(hint, std::forward(args)...); - } - - template - std::pair try_emplace(const key_type &k, Args &&...args) { - return m_ht.try_emplace(k, std::forward(args)...); - } - - template - std::pair try_emplace(key_type &&k, Args &&...args) { - return m_ht.try_emplace(std::move(k), std::forward(args)...); - } - - template - iterator try_emplace(const_iterator hint, const key_type &k, Args &&...args) { - return m_ht.try_emplace_hint(hint, k, std::forward(args)...); - } - - template - iterator try_emplace(const_iterator hint, key_type &&k, Args &&...args) { - return m_ht.try_emplace_hint(hint, std::move(k), - std::forward(args)...); - } - - iterator erase(iterator pos) { return m_ht.erase(pos); } - iterator erase(const_iterator pos) { return m_ht.erase(pos); } - iterator erase(const_iterator first, const_iterator last) { - return m_ht.erase(first, last); - } - size_type erase(const key_type &key) { return m_ht.erase(key); } - - /** - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - size_type erase(const key_type &key, std::size_t precalculated_hash) { - return m_ht.erase(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - * comparable to `Key`. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type erase(const K &key) { - return m_ht.erase(key); - } - - /** - * @copydoc erase(const K& key) - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type erase(const K &key, std::size_t precalculated_hash) { - return m_ht.erase(key, precalculated_hash); - } - - void swap(sparse_map &other) { other.m_ht.swap(m_ht); } - - /* - * Lookup - */ - T &at(const Key &key) { return m_ht.at(key); } - - /** - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - T &at(const Key &key, std::size_t precalculated_hash) { - return m_ht.at(key, precalculated_hash); - } - - const T &at(const Key &key) const { return m_ht.at(key); } - - /** - * @copydoc at(const Key& key, std::size_t precalculated_hash) - */ - const T &at(const Key &key, std::size_t precalculated_hash) const { - return m_ht.at(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - * comparable to `Key`. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - T &at(const K &key) { - return m_ht.at(key); - } - - /** - * @copydoc at(const K& key) - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - T &at(const K &key, std::size_t precalculated_hash) { - return m_ht.at(key, precalculated_hash); - } - - /** - * @copydoc at(const K& key) - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - const T &at(const K &key) const { - return m_ht.at(key); - } - - /** - * @copydoc at(const K& key, std::size_t precalculated_hash) - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - const T &at(const K &key, std::size_t precalculated_hash) const { - return m_ht.at(key, precalculated_hash); - } - - T &operator[](const Key &key) { return m_ht[key]; } - T &operator[](Key &&key) { return m_ht[std::move(key)]; } - - size_type count(const Key &key) const { return m_ht.count(key); } - - /** - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - size_type count(const Key &key, std::size_t precalculated_hash) const { - return m_ht.count(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - * comparable to `Key`. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type count(const K &key) const { - return m_ht.count(key); - } - - /** - * @copydoc count(const K& key) const - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type count(const K &key, std::size_t precalculated_hash) const { - return m_ht.count(key, precalculated_hash); - } - - iterator find(const Key &key) { return m_ht.find(key); } - - /** - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - iterator find(const Key &key, std::size_t precalculated_hash) { - return m_ht.find(key, precalculated_hash); - } - - const_iterator find(const Key &key) const { return m_ht.find(key); } - - /** - * @copydoc find(const Key& key, std::size_t precalculated_hash) - */ - const_iterator find(const Key &key, std::size_t precalculated_hash) const { - return m_ht.find(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - * comparable to `Key`. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - iterator find(const K &key) { - return m_ht.find(key); - } - - /** - * @copydoc find(const K& key) - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - iterator find(const K &key, std::size_t precalculated_hash) { - return m_ht.find(key, precalculated_hash); - } - - /** - * @copydoc find(const K& key) - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - const_iterator find(const K &key) const { - return m_ht.find(key); - } - - /** - * @copydoc find(const K& key) - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - const_iterator find(const K &key, std::size_t precalculated_hash) const { - return m_ht.find(key, precalculated_hash); - } - - bool contains(const Key &key) const { return m_ht.contains(key); } - - /** - * Use the hash value 'precalculated_hash' instead of hashing the key. The - * hash value should be the same as hash_function()(key). Useful to speed-up - * the lookup if you already have the hash. - */ - bool contains(const Key &key, std::size_t precalculated_hash) const { - return m_ht.contains(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * KeyEqual::is_transparent exists. If so, K must be hashable and comparable - * to Key. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - bool contains(const K &key) const { - return m_ht.contains(key); - } - - /** - * @copydoc contains(const K& key) const - * - * Use the hash value 'precalculated_hash' instead of hashing the key. The - * hash value should be the same as hash_function()(key). Useful to speed-up - * the lookup if you already have the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - bool contains(const K &key, std::size_t precalculated_hash) const { - return m_ht.contains(key, precalculated_hash); - } - - std::pair equal_range(const Key &key) { - return m_ht.equal_range(key); - } - - /** - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - std::pair equal_range(const Key &key, - std::size_t precalculated_hash) { - return m_ht.equal_range(key, precalculated_hash); - } - - std::pair equal_range(const Key &key) const { - return m_ht.equal_range(key); - } - - /** - * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) - */ - std::pair equal_range( - const Key &key, std::size_t precalculated_hash) const { - return m_ht.equal_range(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - * comparable to `Key`. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range(const K &key) { - return m_ht.equal_range(key); - } - - /** - * @copydoc equal_range(const K& key) - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range(const K &key, - std::size_t precalculated_hash) { - return m_ht.equal_range(key, precalculated_hash); - } - - /** - * @copydoc equal_range(const K& key) - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range(const K &key) const { - return m_ht.equal_range(key); - } - - /** - * @copydoc equal_range(const K& key, std::size_t precalculated_hash) - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range( - const K &key, std::size_t precalculated_hash) const { - return m_ht.equal_range(key, precalculated_hash); - } - - /* - * Bucket interface - */ - size_type bucket_count() const { return m_ht.bucket_count(); } - size_type max_bucket_count() const { return m_ht.max_bucket_count(); } - - /* - * Hash policy - */ - float load_factor() const { return m_ht.load_factor(); } - float max_load_factor() const { return m_ht.max_load_factor(); } - void max_load_factor(float ml) { m_ht.max_load_factor(ml); } - - void rehash(size_type count) { m_ht.rehash(count); } - void reserve(size_type count) { m_ht.reserve(count); } - - /* - * Observers - */ - hasher hash_function() const { return m_ht.hash_function(); } - key_equal key_eq() const { return m_ht.key_eq(); } - - /* - * Other - */ - - /** - * Convert a `const_iterator` to an `iterator`. - */ - iterator mutable_iterator(const_iterator pos) { - return m_ht.mutable_iterator(pos); - } - - /** - * Serialize the map through the `serializer` parameter. - * - * The `serializer` parameter must be a function object that supports the - * following call: - * - `template void operator()(const U& value);` where the types - * `std::uint64_t`, `float` and `std::pair` must be supported for U. - * - * The implementation leaves binary compatibility (endianness, IEEE 754 for - * floats, ...) of the types it serializes in the hands of the `Serializer` - * function object if compatibility is required. - */ - template - void serialize(Serializer &serializer) const { - m_ht.serialize(serializer); - } - - /** - * Deserialize a previously serialized map through the `deserializer` - * parameter. - * - * The `deserializer` parameter must be a function object that supports the - * following calls: - * - `template U operator()();` where the types `std::uint64_t`, - * `float` and `std::pair` must be supported for U. - * - * If the deserialized hash map type is hash compatible with the serialized - * map, the deserialization process can be sped up by setting - * `hash_compatible` to true. To be hash compatible, the Hash, KeyEqual and - * GrowthPolicy must behave the same way than the ones used on the serialized - * map. The `std::size_t` must also be of the same size as the one on the - * platform used to serialize the map. If these criteria are not met, the - * behaviour is undefined with `hash_compatible` sets to true. - * - * The behaviour is undefined if the type `Key` and `T` of the `sparse_map` - * are not the same as the types used during serialization. - * - * The implementation leaves binary compatibility (endianness, IEEE 754 for - * floats, size of int, ...) of the types it deserializes in the hands of the - * `Deserializer` function object if compatibility is required. - */ - template - static sparse_map deserialize(Deserializer &deserializer, - bool hash_compatible = false) { - sparse_map map(0); - map.m_ht.deserialize(deserializer, hash_compatible); - - return map; - } - - friend bool operator==(const sparse_map &lhs, const sparse_map &rhs) { - if (lhs.size() != rhs.size()) { - return false; - } - - for (const auto &element_lhs : lhs) { - const auto it_element_rhs = rhs.find(element_lhs.first); - if (it_element_rhs == rhs.cend() || - element_lhs.second != it_element_rhs->second) { - return false; - } - } - - return true; - } - - friend bool operator!=(const sparse_map &lhs, const sparse_map &rhs) { - return !operator==(lhs, rhs); - } - - friend void swap(sparse_map &lhs, sparse_map &rhs) { lhs.swap(rhs); } - - private: - ht m_ht; -}; - -/** - * Same as `dice::sparse_map`. - */ -template , - class KeyEqual = std::equal_to, - class Allocator = std::allocator>> -using sparse_pg_map = - sparse_map; - -} // end namespace dice + /** + * Implementation of a sparse hash map using open-addressing with quadratic + * probing. The goal on the hash map is to be the most memory efficient + * possible, even at low load factor, while keeping reasonable performances. + * + * `GrowthPolicy` defines how the map grows and consequently how a hash value is + * mapped to a bucket. By default the map uses + * `dice::sh::power_of_two_growth_policy`. This policy keeps the number of + * buckets to a power of two and uses a mask to map the hash to a bucket instead + * of the slow modulo. Other growth policies are available and you may define + * your own growth policy, check `dice::sh::power_of_two_growth_policy` for the + * interface. + * + * `ExceptionSafety` defines the exception guarantee provided by the class. By + * default only the basic exception safety is guaranteed which mean that all + * resources used by the hash map will be freed (no memory leaks) but the hash + * map may end-up in an undefined state if an exception is thrown (undefined + * here means that some elements may be missing). This can ONLY happen on rehash + * (either on insert or if `rehash` is called explicitly) and will occur if the + * Allocator can't allocate memory (`std::bad_alloc`) or if the copy constructor + * (when a nothrow move constructor is not available) throws an exception. This + * can be avoided by calling `reserve` beforehand. This basic guarantee is + * similar to the one of `google::sparse_hash_map` and `spp::sparse_hash_map`. + * It is possible to ask for the strong exception guarantee with + * `dice::sh::exception_safety::strong`, the drawback is that the map will be + * slower on rehashes and will also need more memory on rehashes. + * + * `Sparsity` defines how much the hash set will compromise between insertion + * speed and memory usage. A high sparsity means less memory usage but longer + * insertion times, and vice-versa for low sparsity. The default + * `dice::sh::sparsity::medium` sparsity offers a good compromise. It doesn't + * change the lookup speed. + * + * `Key` and `T` must be nothrow move constructible and/or copy constructible. + * + * If the destructor of `Key` or `T` throws an exception, the behaviour of the + * class is undefined. + * + * Iterators invalidation: + * - clear, operator=, reserve, rehash: always invalidate the iterators. + * - insert, emplace, emplace_hint, operator[]: if there is an effective + * insert, invalidate the iterators. + * - erase: always invalidate the iterators. + */ + template, + class KeyEqual = std::equal_to, + class Allocator = std::allocator>, + class GrowthPolicy = dice::sparse_map::sh::power_of_two_growth_policy<2>, + dice::sparse_map::sh::exception_safety ExceptionSafety = + dice::sparse_map::sh::exception_safety::basic, + dice::sparse_map::sh::sparsity Sparsity = dice::sparse_map::sh::sparsity::medium> + class sparse_map { + private: + template + using has_is_transparent = dice::sparse_map::detail_sparse_hash::has_is_transparent; + + class KeySelect { + public: + using key_type = Key; + + const key_type &operator()( + const std::pair &key_value) const noexcept { + return key_value.first; + } + + key_type &operator()(std::pair &key_value) noexcept { + return key_value.first; + } + }; + + class ValueSelect { + public: + using value_type = T; + + const value_type &operator()( + const std::pair &key_value) const noexcept { + return key_value.second; + } + + value_type &operator()(std::pair &key_value) noexcept { + return key_value.second; + } + }; + + using ht = detail_sparse_hash::sparse_hash< + std::pair, KeySelect, ValueSelect, Hash, KeyEqual, Allocator, + GrowthPolicy, ExceptionSafety, Sparsity, dice::sparse_map::sh::probing::quadratic>; + + public: + using key_type = typename ht::key_type; + using mapped_type = T; + using value_type = typename ht::value_type; + using size_type = typename ht::size_type; + using difference_type = typename ht::difference_type; + using hasher = typename ht::hasher; + using key_equal = typename ht::key_equal; + using allocator_type = typename ht::allocator_type; + using reference = typename ht::reference; + using const_reference = typename ht::const_reference; + using pointer = typename ht::pointer; + using const_pointer = typename ht::const_pointer; + using iterator = typename ht::iterator; + using const_iterator = typename ht::const_iterator; + + public: + sparse_map() : sparse_map(ht::DEFAULT_INIT_BUCKET_COUNT) {} + + explicit sparse_map(size_type bucket_count, const Hash &hash = Hash(), + const KeyEqual &equal = KeyEqual(), + const Allocator &alloc = Allocator()) + : m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) {} + + sparse_map(size_type bucket_count, const Allocator &alloc) + : sparse_map(bucket_count, Hash(), KeyEqual(), alloc) {} + + sparse_map(size_type bucket_count, const Hash &hash, const Allocator &alloc) + : sparse_map(bucket_count, hash, KeyEqual(), alloc) {} + + explicit sparse_map(const Allocator &alloc) + : sparse_map(ht::DEFAULT_INIT_BUCKET_COUNT, alloc) {} + + template + sparse_map(InputIt first, InputIt last, + size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, + const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), + const Allocator &alloc = Allocator()) + : sparse_map(bucket_count, hash, equal, alloc) { + insert(first, last); + } + + template + sparse_map(InputIt first, InputIt last, size_type bucket_count, + const Allocator &alloc) + : sparse_map(first, last, bucket_count, Hash(), KeyEqual(), alloc) {} + + template + sparse_map(InputIt first, InputIt last, size_type bucket_count, + const Hash &hash, const Allocator &alloc) + : sparse_map(first, last, bucket_count, hash, KeyEqual(), alloc) {} + + sparse_map(std::initializer_list init, + size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, + const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), + const Allocator &alloc = Allocator()) + : sparse_map(init.begin(), init.end(), bucket_count, hash, equal, alloc) { + } + + sparse_map(std::initializer_list init, size_type bucket_count, + const Allocator &alloc) + : sparse_map(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), + alloc) {} + + sparse_map(std::initializer_list init, size_type bucket_count, + const Hash &hash, const Allocator &alloc) + : sparse_map(init.begin(), init.end(), bucket_count, hash, KeyEqual(), + alloc) {} + + sparse_map &operator=(std::initializer_list ilist) { + m_ht.clear(); + + m_ht.reserve(ilist.size()); + m_ht.insert(ilist.begin(), ilist.end()); + + return *this; + } + + allocator_type get_allocator() const { return m_ht.get_allocator(); } + + iterator begin() noexcept { return m_ht.begin(); } + const_iterator begin() const noexcept { return m_ht.begin(); } + const_iterator cbegin() const noexcept { return m_ht.cbegin(); } + + iterator end() noexcept { return m_ht.end(); } + const_iterator end() const noexcept { return m_ht.end(); } + const_iterator cend() const noexcept { return m_ht.cend(); } + + bool empty() const noexcept { return m_ht.empty(); } + size_type size() const noexcept { return m_ht.size(); } + size_type max_size() const noexcept { return m_ht.max_size(); } + + void clear() noexcept { m_ht.clear(); } + + std::pair insert(const value_type &value) { + return m_ht.insert(value); + } + + template::value>::type * = nullptr> + std::pair insert(P &&value) { + return m_ht.emplace(std::forward

(value)); + } + + std::pair insert(value_type &&value) { + return m_ht.insert(std::move(value)); + } + + iterator insert(const_iterator hint, const value_type &value) { + return m_ht.insert_hint(hint, value); + } + + template::value>::type * = nullptr> + iterator insert(const_iterator hint, P &&value) { + return m_ht.emplace_hint(hint, std::forward

(value)); + } + + iterator insert(const_iterator hint, value_type &&value) { + return m_ht.insert_hint(hint, std::move(value)); + } + + template + void insert(InputIt first, InputIt last) { + m_ht.insert(first, last); + } + + void insert(std::initializer_list ilist) { + m_ht.insert(ilist.begin(), ilist.end()); + } + + template + std::pair insert_or_assign(const key_type &k, M &&obj) { + return m_ht.insert_or_assign(k, std::forward(obj)); + } + + template + std::pair insert_or_assign(key_type &&k, M &&obj) { + return m_ht.insert_or_assign(std::move(k), std::forward(obj)); + } + + template + iterator insert_or_assign(const_iterator hint, const key_type &k, M &&obj) { + return m_ht.insert_or_assign(hint, k, std::forward(obj)); + } + + template + iterator insert_or_assign(const_iterator hint, key_type &&k, M &&obj) { + return m_ht.insert_or_assign(hint, std::move(k), std::forward(obj)); + } + + /** + * Due to the way elements are stored, emplace will need to move or copy the + * key-value once. The method is equivalent to + * `insert(value_type(std::forward(args)...));`. + * + * Mainly here for compatibility with the `std::unordered_map` interface. + */ + template + std::pair emplace(Args &&...args) { + return m_ht.emplace(std::forward(args)...); + } + + /** + * Due to the way elements are stored, emplace_hint will need to move or copy + * the key-value once. The method is equivalent to `insert(hint, + * value_type(std::forward(args)...));`. + * + * Mainly here for compatibility with the `std::unordered_map` interface. + */ + template + iterator emplace_hint(const_iterator hint, Args &&...args) { + return m_ht.emplace_hint(hint, std::forward(args)...); + } + + template + std::pair try_emplace(const key_type &k, Args &&...args) { + return m_ht.try_emplace(k, std::forward(args)...); + } + + template + std::pair try_emplace(key_type &&k, Args &&...args) { + return m_ht.try_emplace(std::move(k), std::forward(args)...); + } + + template + iterator try_emplace(const_iterator hint, const key_type &k, Args &&...args) { + return m_ht.try_emplace_hint(hint, k, std::forward(args)...); + } + + template + iterator try_emplace(const_iterator hint, key_type &&k, Args &&...args) { + return m_ht.try_emplace_hint(hint, std::move(k), + std::forward(args)...); + } + + iterator erase(iterator pos) { return m_ht.erase(pos); } + iterator erase(const_iterator pos) { return m_ht.erase(pos); } + iterator erase(const_iterator first, const_iterator last) { + return m_ht.erase(first, last); + } + size_type erase(const key_type &key) { return m_ht.erase(key); } + + /** + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + size_type erase(const key_type &key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + * comparable to `Key`. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + size_type erase(const K &key) { + return m_ht.erase(key); + } + + /** + * @copydoc erase(const K& key) + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + size_type erase(const K &key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + void swap(sparse_map &other) { other.m_ht.swap(m_ht); } + + T &at(const Key &key) { return m_ht.at(key); } + + /** + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + T &at(const Key &key, std::size_t precalculated_hash) { + return m_ht.at(key, precalculated_hash); + } + + const T &at(const Key &key) const { return m_ht.at(key); } + + /** + * @copydoc at(const Key& key, std::size_t precalculated_hash) + */ + const T &at(const Key &key, std::size_t precalculated_hash) const { + return m_ht.at(key, precalculated_hash); + } + + /** + s* This overload only participates in the overload resolution if the typedef + s* `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + s* comparable to `Key`. + s*/ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + T &at(const K &key) { + return m_ht.at(key); + } + + /** + * @copydoc at(const K& key) + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + T &at(const K &key, std::size_t precalculated_hash) { + return m_ht.at(key, precalculated_hash); + } + + /** + * @copydoc at(const K& key) + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + const T &at(const K &key) const { + return m_ht.at(key); + } + + /** + * @copydoc at(const K& key, std::size_t precalculated_hash) + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + const T &at(const K &key, std::size_t precalculated_hash) const { + return m_ht.at(key, precalculated_hash); + } + + T &operator[](const Key &key) { return m_ht[key]; } + T &operator[](Key &&key) { return m_ht[std::move(key)]; } + + size_type count(const Key &key) const { return m_ht.count(key); } + + /** + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + size_type count(const Key &key, std::size_t precalculated_hash) const { + return m_ht.count(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + * comparable to `Key`. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + size_type count(const K &key) const { + return m_ht.count(key); + } + + /** + * @copydoc count(const K& key) const + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + size_type count(const K &key, std::size_t precalculated_hash) const { + return m_ht.count(key, precalculated_hash); + } + + iterator find(const Key &key) { return m_ht.find(key); } + + /** + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + iterator find(const Key &key, std::size_t precalculated_hash) { + return m_ht.find(key, precalculated_hash); + } + + const_iterator find(const Key &key) const { return m_ht.find(key); } + + /** + * @copydoc find(const Key& key, std::size_t precalculated_hash) + */ + const_iterator find(const Key &key, std::size_t precalculated_hash) const { + return m_ht.find(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + * comparable to `Key`. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + iterator find(const K &key) { + return m_ht.find(key); + } + + /** + * @copydoc find(const K& key) + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + iterator find(const K &key, std::size_t precalculated_hash) { + return m_ht.find(key, precalculated_hash); + } + + /** + * @copydoc find(const K& key) + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + const_iterator find(const K &key) const { + return m_ht.find(key); + } + + /** + * @copydoc find(const K& key) + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + const_iterator find(const K &key, std::size_t precalculated_hash) const { + return m_ht.find(key, precalculated_hash); + } + + bool contains(const Key &key) const { return m_ht.contains(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The + * hash value should be the same as hash_function()(key). Useful to speed-up + * the lookup if you already have the hash. + */ + bool contains(const Key &key, std::size_t precalculated_hash) const { + return m_ht.contains(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * KeyEqual::is_transparent exists. If so, K must be hashable and comparable + * to Key. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + bool contains(const K &key) const { + return m_ht.contains(key); + } + + /** + * @copydoc contains(const K& key) const + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The + * hash value should be the same as hash_function()(key). Useful to speed-up + * the lookup if you already have the hash. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + bool contains(const K &key, std::size_t precalculated_hash) const { + return m_ht.contains(key, precalculated_hash); + } + + std::pair equal_range(const Key &key) { + return m_ht.equal_range(key); + } + + /** + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + std::pair equal_range(const Key &key, + std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + std::pair equal_range(const Key &key) const { + return m_ht.equal_range(key); + } + + /** + * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) + */ + std::pair equal_range( + const Key &key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + * comparable to `Key`. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + std::pair equal_range(const K &key) { + return m_ht.equal_range(key); + } + + /** + * @copydoc equal_range(const K& key) + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + std::pair equal_range(const K &key, + std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * @copydoc equal_range(const K& key) + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + std::pair equal_range(const K &key) const { + return m_ht.equal_range(key); + } + + /** + * @copydoc equal_range(const K& key, std::size_t precalculated_hash) + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + std::pair equal_range( + const K &key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + size_type bucket_count() const { return m_ht.bucket_count(); } + size_type max_bucket_count() const { return m_ht.max_bucket_count(); } + + float load_factor() const { return m_ht.load_factor(); } + float max_load_factor() const { return m_ht.max_load_factor(); } + void max_load_factor(float ml) { m_ht.max_load_factor(ml); } + + void rehash(size_type count) { m_ht.rehash(count); } + void reserve(size_type count) { m_ht.reserve(count); } + + hasher hash_function() const { return m_ht.hash_function(); } + key_equal key_eq() const { return m_ht.key_eq(); } + + + /** + * Convert a `const_iterator` to an `iterator`. + */ + iterator mutable_iterator(const_iterator pos) { + return m_ht.mutable_iterator(pos); + } + + /** + * Serialize the map through the `serializer` parameter. + * + * The `serializer` parameter must be a function object that supports the + * following call: + * - `template void operator()(const U& value);` where the types + * `std::uint64_t`, `float` and `std::pair` must be supported for U. + * + * The implementation leaves binary compatibility (endianness, IEEE 754 for + * floats, ...) of the types it serializes in the hands of the `Serializer` + * function object if compatibility is required. + */ + template + void serialize(Serializer &serializer) const { + m_ht.serialize(serializer); + } + + /** + * Deserialize a previously serialized map through the `deserializer` + * parameter. + * + * The `deserializer` parameter must be a function object that supports the + * following calls: + * - `template U operator()();` where the types `std::uint64_t`, + * `float` and `std::pair` must be supported for U. + * + * If the deserialized hash map type is hash compatible with the serialized + * map, the deserialization process can be sped up by setting + * `hash_compatible` to true. To be hash compatible, the Hash, KeyEqual and + * GrowthPolicy must behave the same way than the ones used on the serialized + * map. The `std::size_t` must also be of the same size as the one on the + * platform used to serialize the map. If these criteria are not met, the + * behaviour is undefined with `hash_compatible` sets to true. + * + * The behaviour is undefined if the type `Key` and `T` of the `sparse_map` + * are not the same as the types used during serialization. + * + * The implementation leaves binary compatibility (endianness, IEEE 754 for + * floats, size of int, ...) of the types it deserializes in the hands of the + * `Deserializer` function object if compatibility is required. + */ + template + static sparse_map deserialize(Deserializer &deserializer, + bool hash_compatible = false) { + sparse_map map(0); + map.m_ht.deserialize(deserializer, hash_compatible); + + return map; + } + + friend bool operator==(const sparse_map &lhs, const sparse_map &rhs) { + if (lhs.size() != rhs.size()) { + return false; + } + + for (const auto &element_lhs : lhs) { + const auto it_element_rhs = rhs.find(element_lhs.first); + if (it_element_rhs == rhs.cend() || + element_lhs.second != it_element_rhs->second) { + return false; + } + } + + return true; + } + + friend bool operator!=(const sparse_map &lhs, const sparse_map &rhs) { + return !operator==(lhs, rhs); + } + + friend void swap(sparse_map &lhs, sparse_map &rhs) { lhs.swap(rhs); } + + private: + ht m_ht; + }; + + /** + * Same as `dice::sparse_map`. + */ + template, + class KeyEqual = std::equal_to, + class Allocator = std::allocator>> + using sparse_pg_map = + sparse_map; + +}// namespace dice::sparse_map #endif diff --git a/include/dice/sparse-map/sparse_set.hpp b/include/dice/sparse-map/sparse_set.hpp index d0e0f0b..d0a8af0 100644 --- a/include/dice/sparse-map/sparse_set.hpp +++ b/include/dice/sparse-map/sparse_set.hpp @@ -31,626 +31,599 @@ #include #include -#include "dice/sparse-map/sparse_hash.hpp" #include "dice/sparse-map/boost_offset_pointer.hpp" +#include "dice/sparse-map/sparse_hash.hpp" namespace dice::sparse_map { -/** - * Implementation of a sparse hash set using open-addressing with quadratic - * probing. The goal on the hash set is to be the most memory efficient - * possible, even at low load factor, while keeping reasonable performances. - * - * `GrowthPolicy` defines how the set grows and consequently how a hash value is - * mapped to a bucket. By default the set uses - * `dice::sh::power_of_two_growth_policy`. This policy keeps the number of - * buckets to a power of two and uses a mask to map the hash to a bucket instead - * of the slow modulo. Other growth policies are available and you may define - * your own growth policy, check `dice::sh::power_of_two_growth_policy` for the - * interface. - * - * `ExceptionSafety` defines the exception guarantee provided by the class. By - * default only the basic exception safety is guaranteed which mean that all - * resources used by the hash set will be freed (no memory leaks) but the hash - * set may end-up in an undefined state if an exception is thrown (undefined - * here means that some elements may be missing). This can ONLY happen on rehash - * (either on insert or if `rehash` is called explicitly) and will occur if the - * Allocator can't allocate memory (`std::bad_alloc`) or if the copy constructor - * (when a nothrow move constructor is not available) throws an exception. This - * can be avoided by calling `reserve` beforehand. This basic guarantee is - * similar to the one of `google::sparse_hash_map` and `spp::sparse_hash_map`. - * It is possible to ask for the strong exception guarantee with - * `dice::sh::exception_safety::strong`, the drawback is that the set will be - * slower on rehashes and will also need more memory on rehashes. - * - * `Sparsity` defines how much the hash set will compromise between insertion - * speed and memory usage. A high sparsity means less memory usage but longer - * insertion times, and vice-versa for low sparsity. The default - * `dice::sh::sparsity::medium` sparsity offers a good compromise. It doesn't - * change the lookup speed. - * - * `Key` must be nothrow move constructible and/or copy constructible. - * - * If the destructor of `Key` throws an exception, the behaviour of the class is - * undefined. - * - * Iterators invalidation: - * - clear, operator=, reserve, rehash: always invalidate the iterators. - * - insert, emplace, emplace_hint: if there is an effective insert, invalidate - * the iterators. - * - erase: always invalidate the iterators. - */ -template , - class KeyEqual = std::equal_to, - class Allocator = std::allocator, - class GrowthPolicy = dice::sparse_map::sh::power_of_two_growth_policy<2>, - dice::sparse_map::sh::exception_safety ExceptionSafety = - dice::sparse_map::sh::exception_safety::basic, - dice::sparse_map::sh::sparsity Sparsity = dice::sparse_map::sh::sparsity::medium> -class sparse_set { - private: - template - using has_is_transparent = dice::sparse_map::detail_sparse_hash::has_is_transparent; - - class KeySelect { - public: - using key_type = Key; - - const key_type &operator()(const Key &key) const noexcept { return key; } - - key_type &operator()(Key &key) noexcept { return key; } - }; - - using ht = - detail_sparse_hash::sparse_hash; - - public: - using key_type = typename ht::key_type; - using value_type = typename ht::value_type; - using size_type = typename ht::size_type; - using difference_type = typename ht::difference_type; - using hasher = typename ht::hasher; - using key_equal = typename ht::key_equal; - using allocator_type = typename ht::allocator_type; - using reference = typename ht::reference; - using const_reference = typename ht::const_reference; - using pointer = typename ht::pointer; - using const_pointer = typename ht::const_pointer; - using iterator = typename ht::iterator; - using const_iterator = typename ht::const_iterator; - - /* - * Constructors - */ - sparse_set() : sparse_set(ht::DEFAULT_INIT_BUCKET_COUNT) {} - - explicit sparse_set(size_type bucket_count, const Hash &hash = Hash(), - const KeyEqual &equal = KeyEqual(), - const Allocator &alloc = Allocator()) - : m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) {} - - sparse_set(size_type bucket_count, const Allocator &alloc) - : sparse_set(bucket_count, Hash(), KeyEqual(), alloc) {} - - sparse_set(size_type bucket_count, const Hash &hash, const Allocator &alloc) - : sparse_set(bucket_count, hash, KeyEqual(), alloc) {} - - explicit sparse_set(const Allocator &alloc) - : sparse_set(ht::DEFAULT_INIT_BUCKET_COUNT, alloc) {} - - template - sparse_set(InputIt first, InputIt last, - size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, - const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), - const Allocator &alloc = Allocator()) - : sparse_set(bucket_count, hash, equal, alloc) { - insert(first, last); - } - - template - sparse_set(InputIt first, InputIt last, size_type bucket_count, - const Allocator &alloc) - : sparse_set(first, last, bucket_count, Hash(), KeyEqual(), alloc) {} - - template - sparse_set(InputIt first, InputIt last, size_type bucket_count, - const Hash &hash, const Allocator &alloc) - : sparse_set(first, last, bucket_count, hash, KeyEqual(), alloc) {} - - sparse_set(std::initializer_list init, - size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, - const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), - const Allocator &alloc = Allocator()) - : sparse_set(init.begin(), init.end(), bucket_count, hash, equal, alloc) { - } - - sparse_set(std::initializer_list init, size_type bucket_count, - const Allocator &alloc) - : sparse_set(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), - alloc) {} - - sparse_set(std::initializer_list init, size_type bucket_count, - const Hash &hash, const Allocator &alloc) - : sparse_set(init.begin(), init.end(), bucket_count, hash, KeyEqual(), - alloc) {} - - sparse_set &operator=(std::initializer_list ilist) { - m_ht.clear(); - - m_ht.reserve(ilist.size()); - m_ht.insert(ilist.begin(), ilist.end()); - - return *this; - } - - allocator_type get_allocator() const { return m_ht.get_allocator(); } - - /* - * Iterators - */ - iterator begin() noexcept { return m_ht.begin(); } - const_iterator begin() const noexcept { return m_ht.begin(); } - const_iterator cbegin() const noexcept { return m_ht.cbegin(); } - - iterator end() noexcept { return m_ht.end(); } - const_iterator end() const noexcept { return m_ht.end(); } - const_iterator cend() const noexcept { return m_ht.cend(); } - - /* - * Capacity - */ - bool empty() const noexcept { return m_ht.empty(); } - size_type size() const noexcept { return m_ht.size(); } - size_type max_size() const noexcept { return m_ht.max_size(); } - - /* - * Modifiers - */ - void clear() noexcept { m_ht.clear(); } - - std::pair insert(const value_type &value) { - return m_ht.insert(value); - } - - std::pair insert(value_type &&value) { - return m_ht.insert(std::move(value)); - } - - iterator insert(const_iterator hint, const value_type &value) { - return m_ht.insert_hint(hint, value); - } - - iterator insert(const_iterator hint, value_type &&value) { - return m_ht.insert_hint(hint, std::move(value)); - } - - template - void insert(InputIt first, InputIt last) { - m_ht.insert(first, last); - } - - void insert(std::initializer_list ilist) { - m_ht.insert(ilist.begin(), ilist.end()); - } - - /** - * Due to the way elements are stored, emplace will need to move or copy the - * key-value once. The method is equivalent to - * `insert(value_type(std::forward(args)...));`. - * - * Mainly here for compatibility with the `std::unordered_map` interface. - */ - template - std::pair emplace(Args &&...args) { - return m_ht.emplace(std::forward(args)...); - } - - /** - * Due to the way elements are stored, emplace_hint will need to move or copy - * the key-value once. The method is equivalent to `insert(hint, - * value_type(std::forward(args)...));`. - * - * Mainly here for compatibility with the `std::unordered_map` interface. - */ - template - iterator emplace_hint(const_iterator hint, Args &&...args) { - return m_ht.emplace_hint(hint, std::forward(args)...); - } - - iterator erase(iterator pos) { return m_ht.erase(pos); } - iterator erase(const_iterator pos) { return m_ht.erase(pos); } - iterator erase(const_iterator first, const_iterator last) { - return m_ht.erase(first, last); - } - size_type erase(const key_type &key) { return m_ht.erase(key); } - - /** - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - size_type erase(const key_type &key, std::size_t precalculated_hash) { - return m_ht.erase(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - * comparable to `Key`. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type erase(const K &key) { - return m_ht.erase(key); - } - - /** - * @copydoc erase(const K& key) - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type erase(const K &key, std::size_t precalculated_hash) { - return m_ht.erase(key, precalculated_hash); - } - - void swap(sparse_set &other) { other.m_ht.swap(m_ht); } - - /* - * Lookup - */ - size_type count(const Key &key) const { return m_ht.count(key); } - - /** - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - size_type count(const Key &key, std::size_t precalculated_hash) const { - return m_ht.count(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - * comparable to `Key`. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type count(const K &key) const { - return m_ht.count(key); - } - - /** - * @copydoc count(const K& key) const - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type count(const K &key, std::size_t precalculated_hash) const { - return m_ht.count(key, precalculated_hash); - } - - iterator find(const Key &key) { return m_ht.find(key); } - - /** - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - iterator find(const Key &key, std::size_t precalculated_hash) { - return m_ht.find(key, precalculated_hash); - } - - const_iterator find(const Key &key) const { return m_ht.find(key); } - - /** - * @copydoc find(const Key& key, std::size_t precalculated_hash) - */ - const_iterator find(const Key &key, std::size_t precalculated_hash) const { - return m_ht.find(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - * comparable to `Key`. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - iterator find(const K &key) { - return m_ht.find(key); - } - - /** - * @copydoc find(const K& key) - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - iterator find(const K &key, std::size_t precalculated_hash) { - return m_ht.find(key, precalculated_hash); - } - - /** - * @copydoc find(const K& key) - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - const_iterator find(const K &key) const { - return m_ht.find(key); - } - - /** - * @copydoc find(const K& key) - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - const_iterator find(const K &key, std::size_t precalculated_hash) const { - return m_ht.find(key, precalculated_hash); - } - - bool contains(const Key &key) const { return m_ht.contains(key); } - - /** - * Use the hash value 'precalculated_hash' instead of hashing the key. The - * hash value should be the same as hash_function()(key). Useful to speed-up - * the lookup if you already have the hash. - */ - bool contains(const Key &key, std::size_t precalculated_hash) const { - return m_ht.contains(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * KeyEqual::is_transparent exists. If so, K must be hashable and comparable - * to Key. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - bool contains(const K &key) const { - return m_ht.contains(key); - } - - /** - * @copydoc contains(const K& key) const - * - * Use the hash value 'precalculated_hash' instead of hashing the key. The - * hash value should be the same as hash_function()(key). Useful to speed-up - * the lookup if you already have the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - bool contains(const K &key, std::size_t precalculated_hash) const { - return m_ht.contains(key, precalculated_hash); - } - - std::pair equal_range(const Key &key) { - return m_ht.equal_range(key); - } - - /** - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - std::pair equal_range(const Key &key, - std::size_t precalculated_hash) { - return m_ht.equal_range(key, precalculated_hash); - } - - std::pair equal_range(const Key &key) const { - return m_ht.equal_range(key); - } - - /** - * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) - */ - std::pair equal_range( - const Key &key, std::size_t precalculated_hash) const { - return m_ht.equal_range(key, precalculated_hash); - } - - /** - * This overload only participates in the overload resolution if the typedef - * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - * comparable to `Key`. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range(const K &key) { - return m_ht.equal_range(key); - } - - /** - * @copydoc equal_range(const K& key) - * - * Use the hash value `precalculated_hash` instead of hashing the key. The - * hash value should be the same as `hash_function()(key)`, otherwise the - * behaviour is undefined. Useful to speed-up the lookup if you already have - * the hash. - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range(const K &key, - std::size_t precalculated_hash) { - return m_ht.equal_range(key, precalculated_hash); - } - - /** - * @copydoc equal_range(const K& key) - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range(const K &key) const { - return m_ht.equal_range(key); - } - - /** - * @copydoc equal_range(const K& key, std::size_t precalculated_hash) - */ - template < - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range( - const K &key, std::size_t precalculated_hash) const { - return m_ht.equal_range(key, precalculated_hash); - } - - /* - * Bucket interface - */ - size_type bucket_count() const { return m_ht.bucket_count(); } - size_type max_bucket_count() const { return m_ht.max_bucket_count(); } - - /* - * Hash policy - */ - float load_factor() const { return m_ht.load_factor(); } - float max_load_factor() const { return m_ht.max_load_factor(); } - void max_load_factor(float ml) { m_ht.max_load_factor(ml); } - - void rehash(size_type count) { m_ht.rehash(count); } - void reserve(size_type count) { m_ht.reserve(count); } - - /* - * Observers - */ - hasher hash_function() const { return m_ht.hash_function(); } - key_equal key_eq() const { return m_ht.key_eq(); } - - /* - * Other - */ - - /** - * Convert a `const_iterator` to an `iterator`. - */ - iterator mutable_iterator(const_iterator pos) { - return m_ht.mutable_iterator(pos); - } - - /** - * Serialize the set through the `serializer` parameter. - * - * The `serializer` parameter must be a function object that supports the - * following call: - * - `void operator()(const U& value);` where the types `std::uint64_t`, - * `float` and `Key` must be supported for U. - * - * The implementation leaves binary compatibility (endianness, IEEE 754 for - * floats, ...) of the types it serializes in the hands of the `Serializer` - * function object if compatibility is required. - */ - template - void serialize(Serializer &serializer) const { - m_ht.serialize(serializer); - } - - /** - * Deserialize a previously serialized set through the `deserializer` - * parameter. - * - * The `deserializer` parameter must be a function object that supports the - * following calls: - * - `template U operator()();` where the types `std::uint64_t`, - * `float` and `Key` must be supported for U. - * - * If the deserialized hash set type is hash compatible with the serialized - * set, the deserialization process can be sped up by setting - * `hash_compatible` to true. To be hash compatible, the Hash, KeyEqual and - * GrowthPolicy must behave the same way than the ones used on the serialized - * set. The `std::size_t` must also be of the same size as the one on the - * platform used to serialize the set. If these criteria are not met, the - * behaviour is undefined with `hash_compatible` sets to true. - * - * The behaviour is undefined if the type `Key` of the `sparse_set` is not the - * same as the type used during serialization. - * - * The implementation leaves binary compatibility (endianness, IEEE 754 for - * floats, size of int, ...) of the types it deserializes in the hands of the - * `Deserializer` function object if compatibility is required. - */ - template - static sparse_set deserialize(Deserializer &deserializer, - bool hash_compatible = false) { - sparse_set set(0); - set.m_ht.deserialize(deserializer, hash_compatible); - - return set; - } - - friend bool operator==(const sparse_set &lhs, const sparse_set &rhs) { - if (lhs.size() != rhs.size()) { - return false; - } - - for (const auto &element_lhs : lhs) { - const auto it_element_rhs = rhs.find(element_lhs); - if (it_element_rhs == rhs.cend()) { - return false; - } - } - - return true; - } - - friend bool operator!=(const sparse_set &lhs, const sparse_set &rhs) { - return !operator==(lhs, rhs); - } - - friend void swap(sparse_set &lhs, sparse_set &rhs) { lhs.swap(rhs); } - - private: - ht m_ht; -}; - -/** - * Same as `dice::sparse_set`. - */ -template , - class KeyEqual = std::equal_to, - class Allocator = std::allocator> -using sparse_pg_set = - sparse_set; - -} // end namespace dice + /** + * Implementation of a sparse hash set using open-addressing with quadratic + * probing. The goal on the hash set is to be the most memory efficient + * possible, even at low load factor, while keeping reasonable performances. + * + * `GrowthPolicy` defines how the set grows and consequently how a hash value is + * mapped to a bucket. By default the set uses + * `dice::sh::power_of_two_growth_policy`. This policy keeps the number of + * buckets to a power of two and uses a mask to map the hash to a bucket instead + * of the slow modulo. Other growth policies are available and you may define + * your own growth policy, check `dice::sh::power_of_two_growth_policy` for the + * interface. + * + * `ExceptionSafety` defines the exception guarantee provided by the class. By + * default only the basic exception safety is guaranteed which mean that all + * resources used by the hash set will be freed (no memory leaks) but the hash + * set may end-up in an undefined state if an exception is thrown (undefined + * here means that some elements may be missing). This can ONLY happen on rehash + * (either on insert or if `rehash` is called explicitly) and will occur if the + * Allocator can't allocate memory (`std::bad_alloc`) or if the copy constructor + * (when a nothrow move constructor is not available) throws an exception. This + * can be avoided by calling `reserve` beforehand. This basic guarantee is + * similar to the one of `google::sparse_hash_map` and `spp::sparse_hash_map`. + * It is possible to ask for the strong exception guarantee with + * `dice::sh::exception_safety::strong`, the drawback is that the set will be + * slower on rehashes and will also need more memory on rehashes. + * + * `Sparsity` defines how much the hash set will compromise between insertion + * speed and memory usage. A high sparsity means less memory usage but longer + * insertion times, and vice-versa for low sparsity. The default + * `dice::sh::sparsity::medium` sparsity offers a good compromise. It doesn't + * change the lookup speed. + * + * `Key` must be nothrow move constructible and/or copy constructible. + * + * If the destructor of `Key` throws an exception, the behaviour of the class is + * undefined. + * + * Iterators invalidation: + * - clear, operator=, reserve, rehash: always invalidate the iterators. + * - insert, emplace, emplace_hint: if there is an effective insert, invalidate + * the iterators. + * - erase: always invalidate the iterators. + */ + template, + class KeyEqual = std::equal_to, + class Allocator = std::allocator, + class GrowthPolicy = dice::sparse_map::sh::power_of_two_growth_policy<2>, + dice::sparse_map::sh::exception_safety ExceptionSafety = + dice::sparse_map::sh::exception_safety::basic, + dice::sparse_map::sh::sparsity Sparsity = dice::sparse_map::sh::sparsity::medium> + class sparse_set { + private: + template + using has_is_transparent = dice::sparse_map::detail_sparse_hash::has_is_transparent; + + class KeySelect { + public: + using key_type = Key; + + const key_type &operator()(const Key &key) const noexcept { return key; } + + key_type &operator()(Key &key) noexcept { return key; } + }; + + using ht = + detail_sparse_hash::sparse_hash; + + public: + using key_type = typename ht::key_type; + using value_type = typename ht::value_type; + using size_type = typename ht::size_type; + using difference_type = typename ht::difference_type; + using hasher = typename ht::hasher; + using key_equal = typename ht::key_equal; + using allocator_type = typename ht::allocator_type; + using reference = typename ht::reference; + using const_reference = typename ht::const_reference; + using pointer = typename ht::pointer; + using const_pointer = typename ht::const_pointer; + using iterator = typename ht::iterator; + using const_iterator = typename ht::const_iterator; + + sparse_set() : sparse_set(ht::DEFAULT_INIT_BUCKET_COUNT) {} + + explicit sparse_set(size_type bucket_count, const Hash &hash = Hash(), + const KeyEqual &equal = KeyEqual(), + const Allocator &alloc = Allocator()) + : m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) {} + + sparse_set(size_type bucket_count, const Allocator &alloc) + : sparse_set(bucket_count, Hash(), KeyEqual(), alloc) {} + + sparse_set(size_type bucket_count, const Hash &hash, const Allocator &alloc) + : sparse_set(bucket_count, hash, KeyEqual(), alloc) {} + + explicit sparse_set(const Allocator &alloc) + : sparse_set(ht::DEFAULT_INIT_BUCKET_COUNT, alloc) {} + + template + sparse_set(InputIt first, InputIt last, + size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, + const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), + const Allocator &alloc = Allocator()) + : sparse_set(bucket_count, hash, equal, alloc) { + insert(first, last); + } + + template + sparse_set(InputIt first, InputIt last, size_type bucket_count, + const Allocator &alloc) + : sparse_set(first, last, bucket_count, Hash(), KeyEqual(), alloc) {} + + template + sparse_set(InputIt first, InputIt last, size_type bucket_count, + const Hash &hash, const Allocator &alloc) + : sparse_set(first, last, bucket_count, hash, KeyEqual(), alloc) {} + + sparse_set(std::initializer_list init, + size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, + const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), + const Allocator &alloc = Allocator()) + : sparse_set(init.begin(), init.end(), bucket_count, hash, equal, alloc) { + } + + sparse_set(std::initializer_list init, size_type bucket_count, + const Allocator &alloc) + : sparse_set(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), + alloc) {} + + sparse_set(std::initializer_list init, size_type bucket_count, + const Hash &hash, const Allocator &alloc) + : sparse_set(init.begin(), init.end(), bucket_count, hash, KeyEqual(), + alloc) {} + + sparse_set &operator=(std::initializer_list ilist) { + m_ht.clear(); + + m_ht.reserve(ilist.size()); + m_ht.insert(ilist.begin(), ilist.end()); + + return *this; + } + + allocator_type get_allocator() const { return m_ht.get_allocator(); } + + iterator begin() noexcept { return m_ht.begin(); } + const_iterator begin() const noexcept { return m_ht.begin(); } + const_iterator cbegin() const noexcept { return m_ht.cbegin(); } + + iterator end() noexcept { return m_ht.end(); } + const_iterator end() const noexcept { return m_ht.end(); } + const_iterator cend() const noexcept { return m_ht.cend(); } + + bool empty() const noexcept { return m_ht.empty(); } + size_type size() const noexcept { return m_ht.size(); } + size_type max_size() const noexcept { return m_ht.max_size(); } + + void clear() noexcept { m_ht.clear(); } + + std::pair insert(const value_type &value) { + return m_ht.insert(value); + } + + std::pair insert(value_type &&value) { + return m_ht.insert(std::move(value)); + } + + iterator insert(const_iterator hint, const value_type &value) { + return m_ht.insert_hint(hint, value); + } + + iterator insert(const_iterator hint, value_type &&value) { + return m_ht.insert_hint(hint, std::move(value)); + } + + template + void insert(InputIt first, InputIt last) { + m_ht.insert(first, last); + } + + void insert(std::initializer_list ilist) { + m_ht.insert(ilist.begin(), ilist.end()); + } + + /** + * Due to the way elements are stored, emplace will need to move or copy the + * key-value once. The method is equivalent to + * `insert(value_type(std::forward(args)...));`. + * + * Mainly here for compatibility with the `std::unordered_map` interface. + */ + template + std::pair emplace(Args &&...args) { + return m_ht.emplace(std::forward(args)...); + } + + /** + * Due to the way elements are stored, emplace_hint will need to move or copy + * the key-value once. The method is equivalent to `insert(hint, + * value_type(std::forward(args)...));`. + * + * Mainly here for compatibility with the `std::unordered_map` interface. + */ + template + iterator emplace_hint(const_iterator hint, Args &&...args) { + return m_ht.emplace_hint(hint, std::forward(args)...); + } + + iterator erase(iterator pos) { return m_ht.erase(pos); } + iterator erase(const_iterator pos) { return m_ht.erase(pos); } + iterator erase(const_iterator first, const_iterator last) { + return m_ht.erase(first, last); + } + size_type erase(const key_type &key) { return m_ht.erase(key); } + + /** + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + size_type erase(const key_type &key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + * comparable to `Key`. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + size_type erase(const K &key) { + return m_ht.erase(key); + } + + /** + * @copydoc erase(const K& key) + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + size_type erase(const K &key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + void swap(sparse_set &other) { other.m_ht.swap(m_ht); } + + size_type count(const Key &key) const { return m_ht.count(key); } + + /** + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + size_type count(const Key &key, std::size_t precalculated_hash) const { + return m_ht.count(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + * comparable to `Key`. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + size_type count(const K &key) const { + return m_ht.count(key); + } + + /** + * @copydoc count(const K& key) const + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + size_type count(const K &key, std::size_t precalculated_hash) const { + return m_ht.count(key, precalculated_hash); + } + + iterator find(const Key &key) { return m_ht.find(key); } + + /** + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + iterator find(const Key &key, std::size_t precalculated_hash) { + return m_ht.find(key, precalculated_hash); + } + + const_iterator find(const Key &key) const { return m_ht.find(key); } + + /** + * @copydoc find(const Key& key, std::size_t precalculated_hash) + */ + const_iterator find(const Key &key, std::size_t precalculated_hash) const { + return m_ht.find(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + * comparable to `Key`. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + iterator find(const K &key) { + return m_ht.find(key); + } + + /** + * @copydoc find(const K& key) + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + iterator find(const K &key, std::size_t precalculated_hash) { + return m_ht.find(key, precalculated_hash); + } + + /** + * @copydoc find(const K& key) + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + const_iterator find(const K &key) const { + return m_ht.find(key); + } + + /** + * @copydoc find(const K& key) + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + const_iterator find(const K &key, std::size_t precalculated_hash) const { + return m_ht.find(key, precalculated_hash); + } + + bool contains(const Key &key) const { return m_ht.contains(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The + * hash value should be the same as hash_function()(key). Useful to speed-up + * the lookup if you already have the hash. + */ + bool contains(const Key &key, std::size_t precalculated_hash) const { + return m_ht.contains(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * KeyEqual::is_transparent exists. If so, K must be hashable and comparable + * to Key. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + bool contains(const K &key) const { + return m_ht.contains(key); + } + + /** + * @copydoc contains(const K& key) const + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The + * hash value should be the same as hash_function()(key). Useful to speed-up + * the lookup if you already have the hash. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + bool contains(const K &key, std::size_t precalculated_hash) const { + return m_ht.contains(key, precalculated_hash); + } + + std::pair equal_range(const Key &key) { + return m_ht.equal_range(key); + } + + /** + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + std::pair equal_range(const Key &key, + std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + std::pair equal_range(const Key &key) const { + return m_ht.equal_range(key); + } + + /** + * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) + */ + std::pair equal_range( + const Key &key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef + * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + * comparable to `Key`. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + std::pair equal_range(const K &key) { + return m_ht.equal_range(key); + } + + /** + * @copydoc equal_range(const K& key) + * + * Use the hash value `precalculated_hash` instead of hashing the key. The + * hash value should be the same as `hash_function()(key)`, otherwise the + * behaviour is undefined. Useful to speed-up the lookup if you already have + * the hash. + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + std::pair equal_range(const K &key, + std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * @copydoc equal_range(const K& key) + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + std::pair equal_range(const K &key) const { + return m_ht.equal_range(key); + } + + /** + * @copydoc equal_range(const K& key, std::size_t precalculated_hash) + */ + template< + class K, class KE = KeyEqual, + typename std::enable_if::value>::type * = nullptr> + std::pair equal_range( + const K &key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + size_type bucket_count() const { return m_ht.bucket_count(); } + size_type max_bucket_count() const { return m_ht.max_bucket_count(); } + + float load_factor() const { return m_ht.load_factor(); } + float max_load_factor() const { return m_ht.max_load_factor(); } + void max_load_factor(float ml) { m_ht.max_load_factor(ml); } + + void rehash(size_type count) { m_ht.rehash(count); } + void reserve(size_type count) { m_ht.reserve(count); } + + hasher hash_function() const { return m_ht.hash_function(); } + key_equal key_eq() const { return m_ht.key_eq(); } + + + /** + * Convert a `const_iterator` to an `iterator`. + */ + iterator mutable_iterator(const_iterator pos) { + return m_ht.mutable_iterator(pos); + } + + /** + * Serialize the set through the `serializer` parameter. + * + * The `serializer` parameter must be a function object that supports the + * following call: + * - `void operator()(const U& value);` where the types `std::uint64_t`, + * `float` and `Key` must be supported for U. + * + * The implementation leaves binary compatibility (endianness, IEEE 754 for + * floats, ...) of the types it serializes in the hands of the `Serializer` + * function object if compatibility is required. + */ + template + void serialize(Serializer &serializer) const { + m_ht.serialize(serializer); + } + + /** + * Deserialize a previously serialized set through the `deserializer` + * parameter. + * + * The `deserializer` parameter must be a function object that supports the + * following calls: + * - `template U operator()();` where the types `std::uint64_t`, + * `float` and `Key` must be supported for U. + * + * If the deserialized hash set type is hash compatible with the serialized + * set, the deserialization process can be sped up by setting + * `hash_compatible` to true. To be hash compatible, the Hash, KeyEqual and + * GrowthPolicy must behave the same way than the ones used on the serialized + * set. The `std::size_t` must also be of the same size as the one on the + * platform used to serialize the set. If these criteria are not met, the + * behaviour is undefined with `hash_compatible` sets to true. + * + * The behaviour is undefined if the type `Key` of the `sparse_set` is not the + * same as the type used during serialization. + * + * The implementation leaves binary compatibility (endianness, IEEE 754 for + * floats, size of int, ...) of the types it deserializes in the hands of the + * `Deserializer` function object if compatibility is required. + */ + template + static sparse_set deserialize(Deserializer &deserializer, + bool hash_compatible = false) { + sparse_set set(0); + set.m_ht.deserialize(deserializer, hash_compatible); + + return set; + } + + friend bool operator==(const sparse_set &lhs, const sparse_set &rhs) { + if (lhs.size() != rhs.size()) { + return false; + } + + for (const auto &element_lhs : lhs) { + const auto it_element_rhs = rhs.find(element_lhs); + if (it_element_rhs == rhs.cend()) { + return false; + } + } + + return true; + } + + friend bool operator!=(const sparse_set &lhs, const sparse_set &rhs) { + return !operator==(lhs, rhs); + } + + friend void swap(sparse_set &lhs, sparse_set &rhs) { lhs.swap(rhs); } + + private: + ht m_ht; + }; + + /** + * Same as `dice::sparse_set`. + */ + template, + class KeyEqual = std::equal_to, + class Allocator = std::allocator> + using sparse_pg_set = + sparse_set; + +}// namespace dice::sparse_map #endif From 5b3890feaadcd81b41bc5bb63f3c10e9afaceee8 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Tue, 1 Aug 2023 13:20:17 +0200 Subject: [PATCH 02/41] update to c++20 --- .../dice/sparse-map/sparse_growth_policy.hpp | 17 + include/dice/sparse-map/sparse_hash.hpp | 508 ++++++------------ include/dice/sparse-map/sparse_map.hpp | 222 ++++---- include/dice/sparse-map/sparse_set.hpp | 170 +++--- tests/CMakeLists.txt | 1 - tests/popcount_tests.cpp | 106 ---- tests/sparse_map_tests.cpp | 16 +- 7 files changed, 342 insertions(+), 698 deletions(-) delete mode 100644 tests/popcount_tests.cpp diff --git a/include/dice/sparse-map/sparse_growth_policy.hpp b/include/dice/sparse-map/sparse_growth_policy.hpp index 0642ef0..dfcce35 100644 --- a/include/dice/sparse-map/sparse_growth_policy.hpp +++ b/include/dice/sparse-map/sparse_growth_policy.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,18 @@ namespace dice::sparse_map::sh { + template + concept growth_policy = requires (G const cgpol, G gpol, std::size_t &min_bucket_count_in_out, std::size_t hash) { + G{min_bucket_count_in_out}; + { cgpol.bucket_for_hash(hash) } -> std::convertible_to; + { cgpol.next_bucket_count() } -> std::convertible_to; + { cgpol.max_bucket_count() } -> std::convertible_to; + gpol.clear(); + + noexcept(cgpol.bucket_for_hash(hash)); + noexcept(gpol.clear()); + }; + /** * Grow the hash table by a factor of GrowthFactor keeping the bucket count to a * power of two. It allows the table to use a mask operation instead of a modulo @@ -102,6 +115,10 @@ namespace dice::sparse_map::sh { */ void clear() noexcept { m_mask = 0; } + std::size_t mask() const noexcept { + return m_mask; + } + private: static std::size_t round_up_to_power_of_two(std::size_t value) { if (is_power_of_two(value)) { diff --git a/include/dice/sparse-map/sparse_hash.hpp b/include/dice/sparse-map/sparse_hash.hpp index eea3c3a..afde4c6 100644 --- a/include/dice/sparse-map/sparse_hash.hpp +++ b/include/dice/sparse-map/sparse_hash.hpp @@ -25,6 +25,7 @@ #define DICE_SPARSE_MAP_SPARSE_HASH_HPP #include +#include #include #include #include @@ -42,14 +43,6 @@ #include "boost/container/vector.hpp" #include "dice/sparse-map/sparse_growth_policy.hpp" -#ifdef __INTEL_COMPILER -#include // For _popcnt32 and _popcnt64 -#endif - -#ifdef _MSC_VER -#include // For __cpuid, __popcnt and __popcnt64 -#endif - #ifdef TSL_DEBUG #define tsl_sh_assert(expr) assert(expr) #else @@ -59,129 +52,23 @@ namespace dice::sparse_map { namespace sh { - enum class probing { linear, - quadratic }; + enum class probing { + linear, + quadratic + }; - enum class exception_safety { basic, - strong }; + enum class exception_safety { + basic, + strong + }; - enum class sparsity { high, - medium, - low }; + enum class sparsity { + high, + medium, + low + }; }// namespace sh - namespace detail_popcount { - /** - * Define the popcount(ll) methods and pick-up the best depending on the - * compiler. - */ - - // From Wikipedia: https://en.wikipedia.org/wiki/Hamming_weight - inline int fallback_popcountll(unsigned long long int x) { - static_assert( - sizeof(unsigned long long int) == sizeof(std::uint64_t), - "sizeof(unsigned long long int) must be equal to sizeof(std::uint64_t). " - "Open a feature request if you need support for a platform where it " - "isn't the case."); - - const std::uint64_t m1 = 0x5555555555555555ull; - const std::uint64_t m2 = 0x3333333333333333ull; - const std::uint64_t m4 = 0x0f0f0f0f0f0f0f0full; - const std::uint64_t h01 = 0x0101010101010101ull; - - x -= (x >> 1ull) & m1; - x = (x & m2) + ((x >> 2ull) & m2); - x = (x + (x >> 4ull)) & m4; - return static_cast((x * h01) >> (64ull - 8ull)); - } - - inline int fallback_popcount(unsigned int x) { - static_assert(sizeof(unsigned int) == sizeof(std::uint32_t) || - sizeof(unsigned int) == sizeof(std::uint64_t), - "sizeof(unsigned int) must be equal to sizeof(std::uint32_t) " - "or sizeof(std::uint64_t). " - "Open a feature request if you need support for a platform " - "where it isn't the case."); - - if (sizeof(unsigned int) == sizeof(std::uint32_t)) { - const std::uint32_t m1 = 0x55555555; - const std::uint32_t m2 = 0x33333333; - const std::uint32_t m4 = 0x0f0f0f0f; - const std::uint32_t h01 = 0x01010101; - - x -= (x >> 1) & m1; - x = (x & m2) + ((x >> 2) & m2); - x = (x + (x >> 4)) & m4; - return static_cast((x * h01) >> (32 - 8)); - } else { - return fallback_popcountll(x); - } - } - -#if defined(__clang__) || defined(__GNUC__) - inline int popcountll(unsigned long long int value) { - return __builtin_popcountll(value); - } - - inline int popcount(unsigned int value) { return __builtin_popcount(value); } - -#elif defined(_MSC_VER) - /** - * We need to check for popcount support at runtime on Windows with __cpuid - * See https://msdn.microsoft.com/en-us/library/bb385231.aspx - */ - inline bool has_popcount_support() { - int cpu_infos[4]; - __cpuid(cpu_infos, 1); - return (cpu_infos[2] & (1 << 23)) != 0; - } - - inline int popcountll(unsigned long long int value) { -#ifdef _WIN64 - static_assert( - sizeof(unsigned long long int) == sizeof(std::int64_t), - "sizeof(unsigned long long int) must be equal to sizeof(std::int64_t). "); - - static const bool has_popcount = has_popcount_support(); - return has_popcount - ? static_cast(__popcnt64(static_cast(value))) - : fallback_popcountll(value); -#else - return fallback_popcountll(value); -#endif - } - - inline int popcount(unsigned int value) { - static_assert(sizeof(unsigned int) == sizeof(std::int32_t), - "sizeof(unsigned int) must be equal to sizeof(std::int32_t). "); - - static const bool has_popcount = has_popcount_support(); - return has_popcount - ? static_cast(__popcnt(static_cast(value))) - : fallback_popcount(value); - } - -#elif defined(__INTEL_COMPILER) - inline int popcountll(unsigned long long int value) { - static_assert(sizeof(unsigned long long int) == sizeof(__int64), ""); - return _popcnt64(static_cast<__int64>(value)); - } - - inline int popcount(unsigned int value) { - return _popcnt32(static_cast(value)); - } - -#else - inline int popcountll(unsigned long long int x) { - return fallback_popcountll(x); - } - - inline int popcount(unsigned int x) { return fallback_popcount(x); } - -#endif - }// namespace detail_popcount - - /* Replacement for const_cast in sparse_array. * Can be overloaded for specific fancy pointers * (see: include/dice/boost_offset_pointer.h). @@ -197,57 +84,11 @@ namespace dice::sparse_map { }; namespace detail_sparse_hash { - /* to_address can convert any raw or fancy pointer into a raw pointer. - * It is needed for the allocator construct and destroy calls. - * This specific implementation is based on boost 1.71.0. - */ -#if __cplusplus >= 201400L// with 14-features - template - T *to_address(T *v) noexcept { return v; } - - namespace fancy_ptr_detail { - template - inline T *ptr_address(T *v, int) noexcept { return v; } - - template - inline auto ptr_address(const T &v, int) noexcept - -> decltype(std::pointer_traits::to_address(v)) { - return std::pointer_traits::to_address(v); - } - template - inline auto ptr_address(const T &v, long) noexcept { - return fancy_ptr_detail::ptr_address(v.operator->(), 0); - } - }// namespace fancy_ptr_detail - - template - inline auto to_address(const T &v) noexcept { - return fancy_ptr_detail::ptr_address(v, 0); - } -#else// without 14-features - template - inline T *to_address(T *v) noexcept { return v; } - - template - inline typename std::pointer_traits::element_type *to_address(const T &v) noexcept { - return detail_sparse_hash::to_address(v.operator->()); - } -#endif - - template struct make_void { using type = void; }; - template - struct has_is_transparent : std::false_type {}; - - template - struct has_is_transparent::type> - : std::true_type {}; - template struct is_power_of_two_policy : std::false_type {}; @@ -757,11 +598,11 @@ namespace dice::sparse_map { static void construct_value(allocator_type &alloc, pointer value, Args &&...value_args) { std::allocator_traits::construct( - alloc, detail_sparse_hash::to_address(value), std::forward(value_args)...); + alloc, std::to_address(value), std::forward(value_args)...); } static void destroy_value(allocator_type &alloc, pointer value) noexcept { - std::allocator_traits::destroy(alloc, detail_sparse_hash::to_address(value)); + std::allocator_traits::destroy(alloc, std::to_address(value)); } static void destroy_and_deallocate_values( @@ -775,12 +616,7 @@ namespace dice::sparse_map { } static size_type popcount(bitmap_type val) noexcept { - if (sizeof(bitmap_type) <= sizeof(unsigned int)) { - return static_cast( - dice::sparse_map::detail_popcount::popcount(static_cast(val))); - } else { - return static_cast(dice::sparse_map::detail_popcount::popcountll(val)); - } + return std::popcount(val); } size_type index_to_offset(size_type index) const noexcept { @@ -1066,33 +902,38 @@ namespace dice::sparse_map { * `sparse_array::index_in_sparse_bucket(ibucket)`. */ template - class sparse_hash : private Allocator, - private Hash, - private KeyEqual, - private GrowthPolicy { + class sparse_hash { private: - template - using has_mapped_type = - typename std::integral_constant::value>; + template + struct GetMappedType { + using type = typename VSel::value_type; + using const_reference = const type &; + using reference = type &; + }; - static_assert( - noexcept(std::declval().bucket_for_hash(std::size_t(0))), - "GrowthPolicy::bucket_for_hash must be noexcept."); - static_assert(noexcept(std::declval().clear()), - "GrowthPolicy::clear must be noexcept."); + template<> + struct GetMappedType { + using type = void; + using const_reference = void; + using reference = void; + }; public: template class sparse_iterator; using key_type = typename KeySelect::key_type; + using mapped_type = typename GetMappedType::type; + using mapped_const_reference = typename GetMappedType::const_reference; + using mapped_reference = typename GetMappedType::reference; using value_type = ValueType; using hasher = Hash; using key_equal = KeyEqual; using allocator_type = Allocator; + using growth_policy = GrowthPolicy; using reference = value_type &; using const_reference = const value_type &; using size_type = typename std::allocator_traits::size_type; @@ -1103,6 +944,8 @@ namespace dice::sparse_map { using const_iterator = sparse_iterator; private: + static constexpr bool has_mapped_type = !std::is_same_v; + using sparse_array = dice::sparse_map::detail_sparse_hash::sparse_array; @@ -1125,14 +968,13 @@ namespace dice::sparse_map { friend class sparse_hash; private: - using sparse_bucket_iterator = typename std::conditional< - IsConst, typename sparse_buckets_container::const_iterator, - typename sparse_buckets_container::iterator>::type; + using sparse_bucket_iterator = std::conditional_t; - using sparse_array_iterator = - typename std::conditional::type; + using sparse_array_iterator = std::conditional_t; /** * sparse_array_it should be nullptr if sparse_bucket_it == @@ -1153,9 +995,7 @@ namespace dice::sparse_map { sparse_iterator() noexcept {} // Copy constructor from iterator to const_iterator. - template::type * = nullptr> - sparse_iterator(const sparse_iterator &other) noexcept + sparse_iterator(const sparse_iterator &other) noexcept requires (IsConst) : m_sparse_buckets_it(other.m_sparse_buckets_it), m_sparse_array_it(other.m_sparse_array_it) {} @@ -1168,18 +1008,12 @@ namespace dice::sparse_map { return KeySelect()(*m_sparse_array_it); } - template::value && - IsConst>::type * = nullptr> - const typename U::value_type &value() const { - return U()(*m_sparse_array_it); + mapped_const_reference value() const requires (has_mapped_type && IsConst) { + return ValueSelect()(*m_sparse_array_it); } - template::value && - !IsConst>::type * = nullptr> - typename U::value_type &value() { - return U()(*m_sparse_array_it); + mapped_reference value() requires (has_mapped_type && !IsConst) { + return ValueSelect()(*m_sparse_array_it); } reference operator*() const { return *m_sparse_array_it; } @@ -1216,15 +1050,14 @@ namespace dice::sparse_map { return tmp; } - friend bool operator==(const sparse_iterator &lhs, - const sparse_iterator &rhs) { - return lhs.m_sparse_buckets_it == rhs.m_sparse_buckets_it && - lhs.m_sparse_array_it == rhs.m_sparse_array_it; + template + bool operator==(const sparse_iterator &other) const noexcept { + return m_sparse_buckets_it == other.m_sparse_buckets_it && m_sparse_array_it == other.m_sparse_array_it; } - friend bool operator!=(const sparse_iterator &lhs, - const sparse_iterator &rhs) { - return !(lhs == rhs); + template + bool operator!=(const sparse_iterator &other) const noexcept { + return m_sparse_buckets_it != other.m_sparse_buckets_it || m_sparse_array_it != other.m_sparse_array_it; } private: @@ -1235,29 +1068,30 @@ namespace dice::sparse_map { public: sparse_hash(size_type bucket_count, const Hash &hash, const KeyEqual &equal, const Allocator &alloc, float max_load_factor) - : Allocator(alloc), - Hash(hash), - KeyEqual(equal), - GrowthPolicy(bucket_count), - m_sparse_buckets_data(alloc), - // m_sparse_buckets_data(std::allocator_traits::rebind_alloc(alloc)), + : m_sparse_buckets_data(alloc), + // m_sparse_buckets_data(std::allocator_traits::rebind_alloc(m_alloc)), m_sparse_buckets(static_empty_sparse_bucket_ptr()), m_bucket_count(bucket_count), m_nb_elements(0), - m_nb_deleted_buckets(0) { + m_nb_deleted_buckets(0), + m_alloc{alloc}, + m_h{hash}, + m_keq{equal}, + m_gpol{bucket_count} { + if (m_bucket_count > max_bucket_count()) { throw std::length_error("The map exceeds its maximum size."); } if (m_bucket_count > 0) { /* - * We can't use the `vector(size_type count, const Allocator& alloc)` + * We can't use the `vector(size_type count, const Allocator& m_alloc)` * constructor as it's only available in C++14 and we need to support * C++11. We thus must resize after using the `vector(const Allocator& - * alloc)` constructor. + * m_alloc)` constructor. * * We can't use `vector(size_type count, const T& value, const Allocator& - * alloc)` as it requires the value T to be copyable. + * m_alloc)` as it requires the value T to be copyable. */ m_sparse_buckets_data.resize( sparse_array::nb_sparse_buckets(bucket_count)); @@ -1280,20 +1114,17 @@ namespace dice::sparse_map { ~sparse_hash() { clear(); } sparse_hash(const sparse_hash &other) - : Allocator(std::allocator_traits< - Allocator>::select_on_container_copy_construction(other)), - Hash(other), - KeyEqual(other), - GrowthPolicy(other), - m_sparse_buckets_data( - std::allocator_traits< - Allocator>::select_on_container_copy_construction(other)), + : m_sparse_buckets_data(std::allocator_traits::select_on_container_copy_construction(other.m_alloc)), m_bucket_count(other.m_bucket_count), m_nb_elements(other.m_nb_elements), m_nb_deleted_buckets(other.m_nb_deleted_buckets), m_load_threshold_rehash(other.m_load_threshold_rehash), m_load_threshold_clear_deleted(other.m_load_threshold_clear_deleted), - m_max_load_factor(other.m_max_load_factor) { + m_max_load_factor(other.m_max_load_factor), + m_alloc{std::allocator_traits::select_on_container_copy_construction(other.m_alloc)}, + m_h{other.m_h}, + m_keq{other.m_keq}, + m_gpol{other.m_gpol} { copy_buckets_from(other), m_sparse_buckets = m_sparse_buckets_data.empty() ? static_empty_sparse_bucket_ptr() @@ -1302,11 +1133,7 @@ namespace dice::sparse_map { sparse_hash(sparse_hash &&other) noexcept( std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value) - : Allocator(std::move(other)), - Hash(std::move(other)), - KeyEqual(std::move(other)), - GrowthPolicy(std::move(other)), - m_sparse_buckets_data(std::move(other.m_sparse_buckets_data)), + : m_sparse_buckets_data(std::move(other.m_sparse_buckets_data)), m_sparse_buckets(m_sparse_buckets_data.empty() ? static_empty_sparse_bucket_ptr() : m_sparse_buckets_data.data()), @@ -1315,8 +1142,12 @@ namespace dice::sparse_map { m_nb_deleted_buckets(other.m_nb_deleted_buckets), m_load_threshold_rehash(other.m_load_threshold_rehash), m_load_threshold_clear_deleted(other.m_load_threshold_clear_deleted), - m_max_load_factor(other.m_max_load_factor) { - other.GrowthPolicy::clear(); + m_max_load_factor(other.m_max_load_factor), + m_alloc{std::move(other.m_alloc)}, + m_h{std::move(other.m_h)}, + m_keq{std::move(other.m_keq)}, + m_gpol{std::move(other.m_gpol)} { + other.m_gpol.clear(); other.m_sparse_buckets_data.clear(); other.m_sparse_buckets = static_empty_sparse_bucket_ptr(); other.m_bucket_count = 0; @@ -1330,24 +1161,20 @@ namespace dice::sparse_map { if (this != &other) { clear(); - if (std::allocator_traits< - Allocator>::propagate_on_container_copy_assignment::value) { - Allocator::operator=(other); + if (std::allocator_traits::propagate_on_container_copy_assignment::value) { + m_alloc = other.m_alloc; } - Hash::operator=(other); - KeyEqual::operator=(other); - GrowthPolicy::operator=(other); + m_h = other.m_h; + m_keq = other.m_keq; + m_gpol = other.m_gpol; - if (std::allocator_traits< - Allocator>::propagate_on_container_copy_assignment::value) { - m_sparse_buckets_data = - sparse_buckets_container(static_cast(other)); + if (std::allocator_traits::propagate_on_container_copy_assignment::value) { + m_sparse_buckets_data = sparse_buckets_container(other.m_alloc); } else { if (m_sparse_buckets_data.size() != other.m_sparse_buckets_data.size()) { - m_sparse_buckets_data = - sparse_buckets_container(static_cast(*this)); + m_sparse_buckets_data = sparse_buckets_container(m_alloc); } else { m_sparse_buckets_data.clear(); } @@ -1372,12 +1199,10 @@ namespace dice::sparse_map { sparse_hash &operator=(sparse_hash &&other) noexcept { clear(); - if (not std::allocator_traits< - Allocator>::propagate_on_container_move_assignment::value and - (static_cast(*this) != static_cast(other))) { + if (!std::allocator_traits::propagate_on_container_move_assignment::value && m_alloc != other.m_alloc) { move_buckets_from(std::move(other)); } else { - static_cast(*this) = std::move(static_cast(other)); + m_alloc = std::move(other.m_alloc); m_sparse_buckets_data = std::move(other.m_sparse_buckets_data); } @@ -1385,10 +1210,9 @@ namespace dice::sparse_map { ? static_empty_sparse_bucket_ptr() : m_sparse_buckets_data.data(); - static_cast(*this) = std::move(static_cast(other)); - static_cast(*this) = std::move(static_cast(other)); - static_cast(*this) = - std::move(static_cast(other)); + m_h = std::move(other.m_h); + m_keq = std::move(other.m_keq); + m_gpol = std::move(other.m_gpol); m_bucket_count = other.m_bucket_count; m_nb_elements = other.m_nb_elements; m_nb_deleted_buckets = other.m_nb_deleted_buckets; @@ -1396,7 +1220,7 @@ namespace dice::sparse_map { m_load_threshold_clear_deleted = other.m_load_threshold_clear_deleted; m_max_load_factor = other.m_max_load_factor; - other.GrowthPolicy::clear(); + other.m_gpol.clear(); other.m_sparse_buckets_data.clear(); other.m_sparse_buckets = static_empty_sparse_bucket_ptr(); other.m_bucket_count = 0; @@ -1460,7 +1284,7 @@ namespace dice::sparse_map { void clear() noexcept { for (auto &bucket : m_sparse_buckets_data) { - bucket.clear(*this); + bucket.clear(m_alloc); } m_nb_elements = 0; @@ -1475,7 +1299,7 @@ namespace dice::sparse_map { template iterator insert_hint(const_iterator hint, P &&value) { if (hint != cend() && - compare_keys(KeySelect()(*hint), KeySelect()(value))) { + m_keq(KeySelect()(*hint), KeySelect()(value))) { return mutable_iterator(hint); } @@ -1514,7 +1338,7 @@ namespace dice::sparse_map { template iterator insert_or_assign(const_iterator hint, K &&key, M &&obj) { - if (hint != cend() && compare_keys(KeySelect()(*hint), key)) { + if (hint != cend() && m_keq(KeySelect()(*hint), key)) { auto it = mutable_iterator(hint); it.value() = std::forward(obj); @@ -1543,7 +1367,7 @@ namespace dice::sparse_map { template iterator try_emplace_hint(const_iterator hint, K &&key, Args &&...args) { - if (hint != cend() && compare_keys(KeySelect()(*hint), key)) { + if (hint != cend() && m_keq(KeySelect()(*hint), key)) { return mutable_iterator(hint); } @@ -1558,7 +1382,7 @@ namespace dice::sparse_map { tsl_sh_assert(pos != end() && m_nb_elements > 0); //vector iterator with fancy pointers have a problem with -> auto it_sparse_array_next = - (*pos.m_sparse_buckets_it).erase(*this, pos.m_sparse_array_it); + (*pos.m_sparse_buckets_it).erase(m_alloc, pos.m_sparse_array_it); m_nb_elements--; m_nb_deleted_buckets++; @@ -1600,7 +1424,7 @@ namespace dice::sparse_map { template size_type erase(const K &key) { - return erase(key, hash_key(key)); + return erase(key, m_h(key)); } template @@ -1612,16 +1436,14 @@ namespace dice::sparse_map { using std::swap; if (std::allocator_traits::propagate_on_container_swap::value) { - swap(static_cast(*this), static_cast(other)); + swap(m_alloc, other.m_alloc); } else { - tsl_sh_assert(static_cast(*this) == - static_cast(other)); + tsl_sh_assert(m_alloc == other.m_alloc); } - swap(static_cast(*this), static_cast(other)); - swap(static_cast(*this), static_cast(other)); - swap(static_cast(*this), - static_cast(other)); + swap(m_h, other.m_h); + swap(m_keq, other.m_keq); + swap(m_gpol, other.m_gpol); swap(m_sparse_buckets_data, other.m_sparse_buckets_data); swap(m_sparse_buckets, other.m_sparse_buckets); swap(m_bucket_count, other.m_bucket_count); @@ -1632,33 +1454,24 @@ namespace dice::sparse_map { swap(m_max_load_factor, other.m_max_load_factor); } - - template< - class K, class U = ValueSelect, - typename std::enable_if::value>::type * = nullptr> - typename U::value_type &at(const K &key) { - return at(key, hash_key(key)); + template requires (has_mapped_type) + mapped_reference at(const K &key) { + return at(key, m_h(key)); } - template< - class K, class U = ValueSelect, - typename std::enable_if::value>::type * = nullptr> - typename U::value_type &at(const K &key, std::size_t hash) { - return const_cast( + template requires (has_mapped_type) + mapped_reference at(const K &key, std::size_t hash) { + return const_cast( static_cast(this)->at(key, hash)); } - template< - class K, class U = ValueSelect, - typename std::enable_if::value>::type * = nullptr> - const typename U::value_type &at(const K &key) const { - return at(key, hash_key(key)); + template requires (has_mapped_type) + mapped_const_reference at(const K &key) const { + return at(key, m_h(key)); } - template< - class K, class U = ValueSelect, - typename std::enable_if::value>::type * = nullptr> - const typename U::value_type &at(const K &key, std::size_t hash) const { + template requires (has_mapped_type) + mapped_const_reference at(const K &key, std::size_t hash) const { auto it = find(key, hash); if (it != cend()) { return it.value(); @@ -1667,16 +1480,14 @@ namespace dice::sparse_map { } } - template< - class K, class U = ValueSelect, - typename std::enable_if::value>::type * = nullptr> - typename U::value_type &operator[](K &&key) { + template requires (has_mapped_type) + mapped_reference operator[](K &&key) { return try_emplace(std::forward(key)).first.value(); } template bool contains(const K &key) const { - return contains(key, hash_key(key)); + return contains(key, m_h(key)); } template @@ -1686,7 +1497,7 @@ namespace dice::sparse_map { template size_type count(const K &key) const { - return count(key, hash_key(key)); + return count(key, m_h(key)); } template @@ -1700,7 +1511,7 @@ namespace dice::sparse_map { template iterator find(const K &key) { - return find_impl(key, hash_key(key)); + return find_impl(key, m_h(key)); } template @@ -1710,7 +1521,7 @@ namespace dice::sparse_map { template const_iterator find(const K &key) const { - return find_impl(key, hash_key(key)); + return find_impl(key, m_h(key)); } template @@ -1720,7 +1531,7 @@ namespace dice::sparse_map { template std::pair equal_range(const K &key) { - return equal_range(key, hash_key(key)); + return equal_range(key, m_h(key)); } template @@ -1731,7 +1542,7 @@ namespace dice::sparse_map { template std::pair equal_range(const K &key) const { - return equal_range(key, hash_key(key)); + return equal_range(key, m_h(key)); } template @@ -1780,9 +1591,9 @@ namespace dice::sparse_map { rehash(size_type(std::ceil(float(count) / max_load_factor()))); } - hasher hash_function() const { return static_cast(*this); } + hasher hash_function() const { return m_h; } - key_equal key_eq() const { return static_cast(*this); } + key_equal key_eq() const { return m_keq; } iterator mutable_iterator(const_iterator pos) { auto it_sparse_buckets = @@ -1804,18 +1615,8 @@ namespace dice::sparse_map { } private: - template - std::size_t hash_key(const K &key) const { - return Hash::operator()(key); - } - - template - bool compare_keys(const K1 &key1, const K2 &key2) const { - return KeyEqual::operator()(key1, key2); - } - size_type bucket_for_hash(std::size_t hash) const { - const std::size_t bucket = GrowthPolicy::bucket_for_hash(hash); + const std::size_t bucket = m_gpol.bucket_for_hash(hash); tsl_sh_assert(sparse_array::sparse_ibucket(bucket) < m_sparse_buckets_data.size() || (bucket == 0 && m_sparse_buckets_data.empty())); @@ -1823,23 +1624,17 @@ namespace dice::sparse_map { return bucket; } - template::value>::type * = - nullptr> - size_type next_bucket(size_type ibucket, size_type iprobe) const { + size_type next_bucket(size_type ibucket, size_type iprobe) const requires (is_power_of_two_policy::value) { (void) iprobe; if (Probing == dice::sparse_map::sh::probing::linear) { - return (ibucket + 1) & this->m_mask; + return (ibucket + 1) & m_gpol.mask(); } else { tsl_sh_assert(Probing == dice::sparse_map::sh::probing::quadratic); - return (ibucket + iprobe) & this->m_mask; + return (ibucket + iprobe) & m_gpol.mask(); } } - template::value>::type * = - nullptr> - size_type next_bucket(size_type ibucket, size_type iprobe) const { + size_type next_bucket(size_type ibucket, size_type iprobe) const requires (!is_power_of_two_policy::value) { (void) iprobe; if (Probing == dice::sparse_map::sh::probing::linear) { ibucket++; @@ -1857,8 +1652,7 @@ namespace dice::sparse_map { try { for (const auto &bucket : other.m_sparse_buckets_data) { - m_sparse_buckets_data.emplace_back(bucket, - static_cast(*this)); + m_sparse_buckets_data.emplace_back(bucket, m_alloc); } } catch (...) { clear(); @@ -1874,8 +1668,7 @@ namespace dice::sparse_map { try { for (auto &&bucket : other.m_sparse_buckets_data) { - m_sparse_buckets_data.emplace_back(std::move(bucket), - static_cast(*this)); + m_sparse_buckets_data.emplace_back(std::move(bucket), m_alloc); } } catch (...) { clear(); @@ -1890,7 +1683,7 @@ namespace dice::sparse_map { std::pair insert_impl(const K &key, Args &&...value_type_args) { if (size() >= m_load_threshold_rehash) { - rehash_impl(GrowthPolicy::next_bucket_count()); + rehash_impl(m_gpol.next_bucket_count()); } else if (size() + m_nb_deleted_buckets >= m_load_threshold_clear_deleted) { clear_deleted_buckets(); @@ -1909,7 +1702,7 @@ namespace dice::sparse_map { std::size_t sparse_ibucket_first_deleted = 0; typename sparse_array::size_type index_in_sparse_bucket_first_deleted = 0; - const std::size_t hash = hash_key(key); + const std::size_t hash = m_h(key); std::size_t ibucket = bucket_for_hash(hash); std::size_t probe = 0; @@ -1922,7 +1715,7 @@ namespace dice::sparse_map { if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { auto value_it = m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); - if (compare_keys(key, KeySelect()(*value_it))) { + if (m_keq(key, KeySelect()(*value_it))) { return std::make_pair( iterator(m_sparse_buckets_data.begin() + sparse_ibucket, value_it), @@ -1964,7 +1757,7 @@ namespace dice::sparse_map { Args &&...value_type_args) { // is not called when empty auto value_it = m_sparse_buckets[sparse_ibucket].set( - *this, index_in_sparse_bucket, std::forward(value_type_args)...); + m_alloc, index_in_sparse_bucket, std::forward(value_type_args)...); m_nb_elements++; return std::make_pair( @@ -1988,8 +1781,8 @@ namespace dice::sparse_map { if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { auto value_it = m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); - if (compare_keys(key, KeySelect()(*value_it))) { - m_sparse_buckets[sparse_ibucket].erase(*this, value_it, + if (m_keq(key, KeySelect()(*value_it))) { + m_sparse_buckets[sparse_ibucket].erase(m_alloc, value_it, index_in_sparse_bucket); m_nb_elements--; m_nb_deleted_buckets++; @@ -2029,7 +1822,7 @@ namespace dice::sparse_map { if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { auto value_it = m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); - if (compare_keys(key, KeySelect()(*value_it))) { + if (m_keq(key, KeySelect()(*value_it))) { return const_iterator(m_sparse_buckets_data.cbegin() + sparse_ibucket, value_it); } @@ -2055,9 +1848,7 @@ namespace dice::sparse_map { typename std::enable_if::type * = nullptr> void rehash_impl(size_type count) { - sparse_hash new_table(count, static_cast(*this), - static_cast(*this), - static_cast(*this), m_max_load_factor); + sparse_hash new_table(count, m_h, m_keq, m_alloc, m_max_load_factor); for (auto &bucket : m_sparse_buckets_data) { for (auto &val : bucket) { @@ -2065,7 +1856,7 @@ namespace dice::sparse_map { } // TODO try to reuse some of the memory - bucket.clear(*this); + bucket.clear(m_alloc); } new_table.swap(*this); @@ -2080,9 +1871,7 @@ namespace dice::sparse_map { typename std::enable_if< U == dice::sparse_map::sh::exception_safety::strong>::type * = nullptr> void rehash_impl(size_type count) { - sparse_hash new_table(count, static_cast(*this), - static_cast(*this), - static_cast(*this), m_max_load_factor); + sparse_hash new_table(count, m_h, m_keq, m_alloc, m_max_load_factor); for (const auto &bucket : m_sparse_buckets_data) { for (const auto &val : bucket) { @@ -2097,7 +1886,7 @@ namespace dice::sparse_map { void insert_on_rehash(K &&key_value) { const key_type &key = KeySelect()(key_value); - const std::size_t hash = hash_key(key); + const std::size_t hash = m_h(key); std::size_t ibucket = bucket_for_hash(hash); std::size_t probe = 0; @@ -2107,13 +1896,13 @@ namespace dice::sparse_map { sparse_array::index_in_sparse_bucket(ibucket); if (!m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { - m_sparse_buckets[sparse_ibucket].set(*this, index_in_sparse_bucket, + m_sparse_buckets[sparse_ibucket].set(m_alloc, index_in_sparse_bucket, std::forward(key_value)); m_nb_elements++; return; } else { - tsl_sh_assert(!compare_keys( + tsl_sh_assert(!m_keq( key, KeySelect()(*m_sparse_buckets[sparse_ibucket].value( index_in_sparse_bucket)))); } @@ -2185,7 +1974,7 @@ namespace dice::sparse_map { m_bucket_count = numeric_cast( bucket_count_ds, "Deserialized bucket_count is too big."); - GrowthPolicy::operator=(GrowthPolicy(m_bucket_count)); + m_gpol = GrowthPolicy(m_bucket_count); // GrowthPolicy should not modify the bucket count we got from // deserialization if (m_bucket_count != bucket_count_ds) { @@ -2209,7 +1998,7 @@ namespace dice::sparse_map { for (slz_size_type ibucket = 0; ibucket < nb_sparse_buckets; ibucket++) { m_sparse_buckets_data.emplace_back( sparse_array::deserialize_hash_compatible( - deserializer, static_cast(*this))); + deserializer, m_alloc)); } if (!m_sparse_buckets_data.empty()) { @@ -2275,6 +2064,11 @@ namespace dice::sparse_map { */ size_type m_load_threshold_clear_deleted; float m_max_load_factor; + + [[no_unique_address]] allocator_type m_alloc; + [[no_unique_address]] hasher m_h; + [[no_unique_address]] key_equal m_keq; + [[no_unique_address]] growth_policy m_gpol; }; }// namespace detail_sparse_hash diff --git a/include/dice/sparse-map/sparse_map.hpp b/include/dice/sparse-map/sparse_map.hpp index 4230990..6a27b78 100644 --- a/include/dice/sparse-map/sparse_map.hpp +++ b/include/dice/sparse-map/sparse_map.hpp @@ -80,7 +80,8 @@ namespace dice::sparse_map { * insert, invalidate the iterators. * - erase: always invalidate the iterators. */ - template, + template, class KeyEqual = std::equal_to, class Allocator = std::allocator>, class GrowthPolicy = dice::sparse_map::sh::power_of_two_growth_policy<2>, @@ -88,16 +89,14 @@ namespace dice::sparse_map { dice::sparse_map::sh::exception_safety::basic, dice::sparse_map::sh::sparsity Sparsity = dice::sparse_map::sh::sparsity::medium> class sparse_map { - private: - template - using has_is_transparent = dice::sparse_map::detail_sparse_hash::has_is_transparent; + static constexpr bool key_equal_is_transparent = requires { + typename KeyEqual::is_transparent; + }; - class KeySelect { - public: + struct KeySelect { using key_type = Key; - const key_type &operator()( - const std::pair &key_value) const noexcept { + const key_type &operator()(const std::pair &key_value) const noexcept { return key_value.first; } @@ -106,12 +105,10 @@ namespace dice::sparse_map { } }; - class ValueSelect { - public: + struct ValueSelect { using value_type = T; - const value_type &operator()( - const std::pair &key_value) const noexcept { + const value_type &operator()(const std::pair &key_value) const noexcept { return key_value.second; } @@ -120,9 +117,8 @@ namespace dice::sparse_map { } }; - using ht = detail_sparse_hash::sparse_hash< - std::pair, KeySelect, ValueSelect, Hash, KeyEqual, Allocator, - GrowthPolicy, ExceptionSafety, Sparsity, dice::sparse_map::sh::probing::quadratic>; + using ht = detail_sparse_hash::sparse_hash, KeySelect, ValueSelect, Hash, KeyEqual, Allocator, + GrowthPolicy, ExceptionSafety, Sparsity, dice::sparse_map::sh::probing::quadratic>; public: using key_type = typename ht::key_type; @@ -202,19 +198,19 @@ namespace dice::sparse_map { return *this; } - allocator_type get_allocator() const { return m_ht.get_allocator(); } + [[nodiscard]] allocator_type get_allocator() const { return m_ht.get_allocator(); } - iterator begin() noexcept { return m_ht.begin(); } - const_iterator begin() const noexcept { return m_ht.begin(); } - const_iterator cbegin() const noexcept { return m_ht.cbegin(); } + [[nodiscard]] iterator begin() noexcept { return m_ht.begin(); } + [[nodiscard]] const_iterator begin() const noexcept { return m_ht.begin(); } + [[nodiscard]] const_iterator cbegin() const noexcept { return m_ht.cbegin(); } - iterator end() noexcept { return m_ht.end(); } - const_iterator end() const noexcept { return m_ht.end(); } - const_iterator cend() const noexcept { return m_ht.cend(); } + [[nodiscard]] iterator end() noexcept { return m_ht.end(); } + [[nodiscard]] const_iterator end() const noexcept { return m_ht.end(); } + [[nodiscard]] const_iterator cend() const noexcept { return m_ht.cend(); } - bool empty() const noexcept { return m_ht.empty(); } - size_type size() const noexcept { return m_ht.size(); } - size_type max_size() const noexcept { return m_ht.max_size(); } + [[nodiscard]] bool empty() const noexcept { return m_ht.empty(); } + [[nodiscard]] size_type size() const noexcept { return m_ht.size(); } + [[nodiscard]] size_type max_size() const noexcept { return m_ht.max_size(); } void clear() noexcept { m_ht.clear(); } @@ -222,8 +218,7 @@ namespace dice::sparse_map { return m_ht.insert(value); } - template::value>::type * = nullptr> + template requires (std::is_constructible_v) std::pair insert(P &&value) { return m_ht.emplace(std::forward

(value)); } @@ -236,8 +231,7 @@ namespace dice::sparse_map { return m_ht.insert_hint(hint, value); } - template::value>::type * = nullptr> + template requires (std::is_constructible_v) iterator insert(const_iterator hint, P &&value) { return m_ht.emplace_hint(hint, std::forward

(value)); } @@ -342,9 +336,7 @@ namespace dice::sparse_map { * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and * comparable to `Key`. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> + template requires (key_equal_is_transparent) size_type erase(const K &key) { return m_ht.erase(key); } @@ -357,16 +349,14 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> + template requires (key_equal_is_transparent) size_type erase(const K &key, std::size_t precalculated_hash) { return m_ht.erase(key, precalculated_hash); } void swap(sparse_map &other) { other.m_ht.swap(m_ht); } - T &at(const Key &key) { return m_ht.at(key); } + [[nodiscard]] T &at(const Key &key) { return m_ht.at(key); } /** * Use the hash value `precalculated_hash` instead of hashing the key. The @@ -374,28 +364,26 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - T &at(const Key &key, std::size_t precalculated_hash) { + [[nodiscard]] T &at(const Key &key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); } - const T &at(const Key &key) const { return m_ht.at(key); } + [[nodiscard]] const T &at(const Key &key) const { return m_ht.at(key); } /** * @copydoc at(const Key& key, std::size_t precalculated_hash) */ - const T &at(const Key &key, std::size_t precalculated_hash) const { + [[nodiscard]] const T &at(const Key &key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); } /** - s* This overload only participates in the overload resolution if the typedef - s* `KeyEqual::is_transparent` exists. If so, `K` must be hashable and - s* comparable to `Key`. - s*/ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - T &at(const K &key) { + * This overload only participates in the overload resolution if the typedef + * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and + * comparable to `Key`. + */ + template requires (key_equal_is_transparent) + [[nodiscard]] T &at(const K &key) { return m_ht.at(key); } @@ -407,37 +395,31 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - T &at(const K &key, std::size_t precalculated_hash) { + template requires (key_equal_is_transparent) + [[nodiscard]] T &at(const K &key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); } /** * @copydoc at(const K& key) */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - const T &at(const K &key) const { + template requires (key_equal_is_transparent) + [[nodiscard]] const T &at(const K &key) const { return m_ht.at(key); } /** * @copydoc at(const K& key, std::size_t precalculated_hash) */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - const T &at(const K &key, std::size_t precalculated_hash) const { + template requires (key_equal_is_transparent) + [[nodiscard]] const T &at(const K &key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); } - T &operator[](const Key &key) { return m_ht[key]; } - T &operator[](Key &&key) { return m_ht[std::move(key)]; } + [[nodiscard]] T &operator[](const Key &key) { return m_ht[key]; } + [[nodiscard]] T &operator[](Key &&key) { return m_ht[std::move(key)]; } - size_type count(const Key &key) const { return m_ht.count(key); } + [[nodiscard]] size_type count(const Key &key) const { return m_ht.count(key); } /** * Use the hash value `precalculated_hash` instead of hashing the key. The @@ -445,7 +427,7 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - size_type count(const Key &key, std::size_t precalculated_hash) const { + [[nodiscard]] size_type count(const Key &key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } @@ -454,10 +436,8 @@ namespace dice::sparse_map { * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and * comparable to `Key`. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type count(const K &key) const { + template requires (key_equal_is_transparent) + [[nodiscard]] size_type count(const K &key) const { return m_ht.count(key); } @@ -469,14 +449,12 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type count(const K &key, std::size_t precalculated_hash) const { + template requires (key_equal_is_transparent) + [[nodiscard]] size_type count(const K &key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } - iterator find(const Key &key) { return m_ht.find(key); } + [[nodiscard]] iterator find(const Key &key) { return m_ht.find(key); } /** * Use the hash value `precalculated_hash` instead of hashing the key. The @@ -484,16 +462,16 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - iterator find(const Key &key, std::size_t precalculated_hash) { + [[nodiscard]] iterator find(const Key &key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } - const_iterator find(const Key &key) const { return m_ht.find(key); } + [[nodiscard]] const_iterator find(const Key &key) const { return m_ht.find(key); } /** * @copydoc find(const Key& key, std::size_t precalculated_hash) */ - const_iterator find(const Key &key, std::size_t precalculated_hash) const { + [[nodiscard]] const_iterator find(const Key &key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } @@ -502,10 +480,8 @@ namespace dice::sparse_map { * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and * comparable to `Key`. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - iterator find(const K &key) { + template requires (key_equal_is_transparent) + [[nodiscard]] iterator find(const K &key) { return m_ht.find(key); } @@ -517,20 +493,16 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - iterator find(const K &key, std::size_t precalculated_hash) { + template requires (key_equal_is_transparent) + [[nodiscard]] iterator find(const K &key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } /** * @copydoc find(const K& key) */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - const_iterator find(const K &key) const { + template requires (key_equal_is_transparent) + [[nodiscard]] const_iterator find(const K &key) const { return m_ht.find(key); } @@ -542,21 +514,19 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - const_iterator find(const K &key, std::size_t precalculated_hash) const { + template requires (key_equal_is_transparent) + [[nodiscard]] const_iterator find(const K &key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } - bool contains(const Key &key) const { return m_ht.contains(key); } + [[nodiscard]] bool contains(const Key &key) const { return m_ht.contains(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The * hash value should be the same as hash_function()(key). Useful to speed-up * the lookup if you already have the hash. */ - bool contains(const Key &key, std::size_t precalculated_hash) const { + [[nodiscard]] bool contains(const Key &key, std::size_t precalculated_hash) const { return m_ht.contains(key, precalculated_hash); } @@ -565,10 +535,8 @@ namespace dice::sparse_map { * KeyEqual::is_transparent exists. If so, K must be hashable and comparable * to Key. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - bool contains(const K &key) const { + template requires (key_equal_is_transparent) + [[nodiscard]] bool contains(const K &key) const { return m_ht.contains(key); } @@ -579,14 +547,12 @@ namespace dice::sparse_map { * hash value should be the same as hash_function()(key). Useful to speed-up * the lookup if you already have the hash. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - bool contains(const K &key, std::size_t precalculated_hash) const { + template requires (key_equal_is_transparent) + [[nodiscard]] bool contains(const K &key, std::size_t precalculated_hash) const { return m_ht.contains(key, precalculated_hash); } - std::pair equal_range(const Key &key) { + [[nodiscard]] std::pair equal_range(const Key &key) { return m_ht.equal_range(key); } @@ -596,20 +562,20 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - std::pair equal_range(const Key &key, - std::size_t precalculated_hash) { + [[nodiscard]] std::pair equal_range(const Key &key, + std::size_t precalculated_hash) { return m_ht.equal_range(key, precalculated_hash); } - std::pair equal_range(const Key &key) const { + [[nodiscard]] std::pair equal_range(const Key &key) const { return m_ht.equal_range(key); } /** * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) */ - std::pair equal_range( - const Key &key, std::size_t precalculated_hash) const { + [[nodiscard]] std::pair equal_range(const Key &key, + std::size_t precalculated_hash) const { return m_ht.equal_range(key, precalculated_hash); } @@ -618,10 +584,8 @@ namespace dice::sparse_map { * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and * comparable to `Key`. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range(const K &key) { + template requires (key_equal_is_transparent) + [[nodiscard]] std::pair equal_range(const K &key) { return m_ht.equal_range(key); } @@ -633,53 +597,47 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range(const K &key, - std::size_t precalculated_hash) { + template requires (key_equal_is_transparent) + [[nodiscard]] std::pair equal_range(const K &key, + std::size_t precalculated_hash) { return m_ht.equal_range(key, precalculated_hash); } /** * @copydoc equal_range(const K& key) */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range(const K &key) const { + template requires (key_equal_is_transparent) + [[nodiscard]] std::pair equal_range(const K &key) const { return m_ht.equal_range(key); } /** * @copydoc equal_range(const K& key, std::size_t precalculated_hash) */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range( - const K &key, std::size_t precalculated_hash) const { + template requires (key_equal_is_transparent) + [[nodiscard]] std::pair equal_range(const K &key, + std::size_t precalculated_hash) const { return m_ht.equal_range(key, precalculated_hash); } - size_type bucket_count() const { return m_ht.bucket_count(); } - size_type max_bucket_count() const { return m_ht.max_bucket_count(); } + [[nodiscard]] size_type bucket_count() const { return m_ht.bucket_count(); } + [[nodiscard]] size_type max_bucket_count() const { return m_ht.max_bucket_count(); } - float load_factor() const { return m_ht.load_factor(); } - float max_load_factor() const { return m_ht.max_load_factor(); } + [[nodiscard]] float load_factor() const { return m_ht.load_factor(); } + [[nodiscard]] float max_load_factor() const { return m_ht.max_load_factor(); } void max_load_factor(float ml) { m_ht.max_load_factor(ml); } void rehash(size_type count) { m_ht.rehash(count); } void reserve(size_type count) { m_ht.reserve(count); } - hasher hash_function() const { return m_ht.hash_function(); } - key_equal key_eq() const { return m_ht.key_eq(); } + [[nodiscard]] hasher hash_function() const { return m_ht.hash_function(); } + [[nodiscard]] key_equal key_eq() const { return m_ht.key_eq(); } /** * Convert a `const_iterator` to an `iterator`. */ - iterator mutable_iterator(const_iterator pos) { + [[nodiscard]] iterator mutable_iterator(const_iterator pos) { return m_ht.mutable_iterator(pos); } diff --git a/include/dice/sparse-map/sparse_set.hpp b/include/dice/sparse-map/sparse_set.hpp index d0a8af0..f963765 100644 --- a/include/dice/sparse-map/sparse_set.hpp +++ b/include/dice/sparse-map/sparse_set.hpp @@ -88,23 +88,25 @@ namespace dice::sparse_map { dice::sparse_map::sh::exception_safety::basic, dice::sparse_map::sh::sparsity Sparsity = dice::sparse_map::sh::sparsity::medium> class sparse_set { - private: - template - using has_is_transparent = dice::sparse_map::detail_sparse_hash::has_is_transparent; + static constexpr bool key_equal_is_transparent = requires { + typename KeyEqual::is_transparent; + }; - class KeySelect { - public: + struct KeySelect { using key_type = Key; - const key_type &operator()(const Key &key) const noexcept { return key; } + const key_type &operator()(const Key &key) const noexcept { + return key; + } - key_type &operator()(Key &key) noexcept { return key; } + key_type &operator()(Key &key) noexcept { + return key; + } }; - using ht = - detail_sparse_hash::sparse_hash; + using ht = detail_sparse_hash::sparse_hash; public: using key_type = typename ht::key_type; @@ -182,19 +184,19 @@ namespace dice::sparse_map { return *this; } - allocator_type get_allocator() const { return m_ht.get_allocator(); } + [[nodiscard]] allocator_type get_allocator() const { return m_ht.get_allocator(); } - iterator begin() noexcept { return m_ht.begin(); } - const_iterator begin() const noexcept { return m_ht.begin(); } - const_iterator cbegin() const noexcept { return m_ht.cbegin(); } + [[nodiscard]] iterator begin() noexcept { return m_ht.begin(); } + [[nodiscard]] const_iterator begin() const noexcept { return m_ht.begin(); } + [[nodiscard]] const_iterator cbegin() const noexcept { return m_ht.cbegin(); } - iterator end() noexcept { return m_ht.end(); } - const_iterator end() const noexcept { return m_ht.end(); } - const_iterator cend() const noexcept { return m_ht.cend(); } + [[nodiscard]] iterator end() noexcept { return m_ht.end(); } + [[nodiscard]] const_iterator end() const noexcept { return m_ht.end(); } + [[nodiscard]] const_iterator cend() const noexcept { return m_ht.cend(); } - bool empty() const noexcept { return m_ht.empty(); } - size_type size() const noexcept { return m_ht.size(); } - size_type max_size() const noexcept { return m_ht.max_size(); } + [[nodiscard]] bool empty() const noexcept { return m_ht.empty(); } + [[nodiscard]] size_type size() const noexcept { return m_ht.size(); } + [[nodiscard]] size_type max_size() const noexcept { return m_ht.max_size(); } void clear() noexcept { m_ht.clear(); } @@ -269,9 +271,7 @@ namespace dice::sparse_map { * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and * comparable to `Key`. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> + template requires (key_equal_is_transparent) size_type erase(const K &key) { return m_ht.erase(key); } @@ -284,16 +284,14 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> + template requires (key_equal_is_transparent) size_type erase(const K &key, std::size_t precalculated_hash) { return m_ht.erase(key, precalculated_hash); } void swap(sparse_set &other) { other.m_ht.swap(m_ht); } - size_type count(const Key &key) const { return m_ht.count(key); } + [[nodiscard]] size_type count(const Key &key) const { return m_ht.count(key); } /** * Use the hash value `precalculated_hash` instead of hashing the key. The @@ -301,7 +299,7 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - size_type count(const Key &key, std::size_t precalculated_hash) const { + [[nodiscard]] size_type count(const Key &key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } @@ -310,10 +308,8 @@ namespace dice::sparse_map { * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and * comparable to `Key`. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type count(const K &key) const { + template requires (key_equal_is_transparent) + [[nodiscard]] size_type count(const K &key) const { return m_ht.count(key); } @@ -325,14 +321,12 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - size_type count(const K &key, std::size_t precalculated_hash) const { + template requires (key_equal_is_transparent) + [[nodiscard]] size_type count(const K &key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } - iterator find(const Key &key) { return m_ht.find(key); } + [[nodiscard]] iterator find(const Key &key) { return m_ht.find(key); } /** * Use the hash value `precalculated_hash` instead of hashing the key. The @@ -340,16 +334,16 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - iterator find(const Key &key, std::size_t precalculated_hash) { + [[nodiscard]] iterator find(const Key &key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } - const_iterator find(const Key &key) const { return m_ht.find(key); } + [[nodiscard]] const_iterator find(const Key &key) const { return m_ht.find(key); } /** * @copydoc find(const Key& key, std::size_t precalculated_hash) */ - const_iterator find(const Key &key, std::size_t precalculated_hash) const { + [[nodiscard]] const_iterator find(const Key &key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } @@ -358,10 +352,8 @@ namespace dice::sparse_map { * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and * comparable to `Key`. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - iterator find(const K &key) { + template requires (key_equal_is_transparent) + [[nodiscard]] iterator find(const K &key) { return m_ht.find(key); } @@ -373,20 +365,16 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - iterator find(const K &key, std::size_t precalculated_hash) { + template requires (key_equal_is_transparent) + [[nodiscard]] iterator find(const K &key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } /** * @copydoc find(const K& key) */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - const_iterator find(const K &key) const { + template requires (key_equal_is_transparent) + [[nodiscard]] const_iterator find(const K &key) const { return m_ht.find(key); } @@ -398,21 +386,19 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - const_iterator find(const K &key, std::size_t precalculated_hash) const { + template requires (key_equal_is_transparent) + [[nodiscard]] const_iterator find(const K &key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } - bool contains(const Key &key) const { return m_ht.contains(key); } + [[nodiscard]] bool contains(const Key &key) const { return m_ht.contains(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The * hash value should be the same as hash_function()(key). Useful to speed-up * the lookup if you already have the hash. */ - bool contains(const Key &key, std::size_t precalculated_hash) const { + [[nodiscard]] bool contains(const Key &key, std::size_t precalculated_hash) const { return m_ht.contains(key, precalculated_hash); } @@ -421,10 +407,8 @@ namespace dice::sparse_map { * KeyEqual::is_transparent exists. If so, K must be hashable and comparable * to Key. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - bool contains(const K &key) const { + template requires (key_equal_is_transparent) + [[nodiscard]] bool contains(const K &key) const { return m_ht.contains(key); } @@ -435,14 +419,12 @@ namespace dice::sparse_map { * hash value should be the same as hash_function()(key). Useful to speed-up * the lookup if you already have the hash. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - bool contains(const K &key, std::size_t precalculated_hash) const { + template requires (key_equal_is_transparent) + [[nodiscard]] bool contains(const K &key, std::size_t precalculated_hash) const { return m_ht.contains(key, precalculated_hash); } - std::pair equal_range(const Key &key) { + [[nodiscard]] std::pair equal_range(const Key &key) { return m_ht.equal_range(key); } @@ -452,20 +434,20 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - std::pair equal_range(const Key &key, - std::size_t precalculated_hash) { + [[nodiscard]] std::pair equal_range(const Key &key, + std::size_t precalculated_hash) { return m_ht.equal_range(key, precalculated_hash); } - std::pair equal_range(const Key &key) const { + [[nodiscard]] std::pair equal_range(const Key &key) const { return m_ht.equal_range(key); } /** * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) */ - std::pair equal_range( - const Key &key, std::size_t precalculated_hash) const { + [[nodiscard]] std::pair equal_range(const Key &key, + std::size_t precalculated_hash) const { return m_ht.equal_range(key, precalculated_hash); } @@ -474,10 +456,8 @@ namespace dice::sparse_map { * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and * comparable to `Key`. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range(const K &key) { + template requires (key_equal_is_transparent) + [[nodiscard]] std::pair equal_range(const K &key) { return m_ht.equal_range(key); } @@ -489,53 +469,47 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range(const K &key, - std::size_t precalculated_hash) { + template requires (key_equal_is_transparent) + [[nodiscard]] std::pair equal_range(const K &key, + std::size_t precalculated_hash) { return m_ht.equal_range(key, precalculated_hash); } /** * @copydoc equal_range(const K& key) */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range(const K &key) const { + template requires (key_equal_is_transparent) + [[nodiscard]] std::pair equal_range(const K &key) const { return m_ht.equal_range(key); } /** * @copydoc equal_range(const K& key, std::size_t precalculated_hash) */ - template< - class K, class KE = KeyEqual, - typename std::enable_if::value>::type * = nullptr> - std::pair equal_range( - const K &key, std::size_t precalculated_hash) const { + template requires (key_equal_is_transparent) + [[nodiscard]] std::pair equal_range(const K &key, + std::size_t precalculated_hash) const { return m_ht.equal_range(key, precalculated_hash); } - size_type bucket_count() const { return m_ht.bucket_count(); } - size_type max_bucket_count() const { return m_ht.max_bucket_count(); } + [[nodiscard]] size_type bucket_count() const { return m_ht.bucket_count(); } + [[nodiscard]] size_type max_bucket_count() const { return m_ht.max_bucket_count(); } - float load_factor() const { return m_ht.load_factor(); } - float max_load_factor() const { return m_ht.max_load_factor(); } + [[nodiscard]] float load_factor() const { return m_ht.load_factor(); } + [[nodiscard]] float max_load_factor() const { return m_ht.max_load_factor(); } void max_load_factor(float ml) { m_ht.max_load_factor(ml); } void rehash(size_type count) { m_ht.rehash(count); } void reserve(size_type count) { m_ht.reserve(count); } - hasher hash_function() const { return m_ht.hash_function(); } - key_equal key_eq() const { return m_ht.key_eq(); } + [[nodiscard]] hasher hash_function() const { return m_ht.hash_function(); } + [[nodiscard]] key_equal key_eq() const { return m_ht.key_eq(); } /** * Convert a `const_iterator` to an `iterator`. */ - iterator mutable_iterator(const_iterator pos) { + [[nodiscard]] iterator mutable_iterator(const_iterator pos) { return m_ht.mutable_iterator(pos); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ead0538..71a3f87 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,7 +5,6 @@ project(tsl_sparse_map_tests) add_executable(tsl_sparse_map_tests "main.cpp" "custom_allocator_tests.cpp" "policy_tests.cpp" - "popcount_tests.cpp" "sparse_map_tests.cpp" "sparse_set_tests.cpp" "fancy_pointer/sparse_array_tests.cpp" diff --git a/tests/popcount_tests.cpp b/tests/popcount_tests.cpp deleted file mode 100644 index 84e2851..0000000 --- a/tests/popcount_tests.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2017 Thibaut Goetghebuer-Planchon - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#include - -#include -#include -#include - -BOOST_AUTO_TEST_SUITE(test_popcount) - -BOOST_AUTO_TEST_CASE(test_popcount_1) { - std::uint32_t value = 0; - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::popcount(value), 0); - - value = 1; - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::popcount(value), 1); - - value = 2; - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::popcount(value), 1); - - value = 294967496; - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::popcount(value), 12); - - value = std::numeric_limits::max(); - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::popcount(value), 32); -} - -BOOST_AUTO_TEST_CASE(test_popcountll_1) { - std::uint64_t value = 0; - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::popcountll(value), 0); - - value = 1; - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::popcountll(value), 1); - - value = 2; - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::popcountll(value), 1); - - value = 294967496; - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::popcountll(value), 12); - - value = 8446744073709551416ull; - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::popcountll(value), 40); - - value = std::numeric_limits::max(); - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::popcountll(value), 64); -} - -BOOST_AUTO_TEST_CASE(test_fallback_popcount_1) { - std::uint32_t value = 0; - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::fallback_popcount(value), 0); - - value = 1; - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::fallback_popcount(value), 1); - - value = 2; - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::fallback_popcount(value), 1); - - value = 294967496; - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::fallback_popcount(value), 12); - - value = std::numeric_limits::max(); - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::fallback_popcount(value), 32); -} - -BOOST_AUTO_TEST_CASE(test_fallback_popcountll_1) { - std::uint64_t value = 0; - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::fallback_popcountll(value), 0); - - value = 1; - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::fallback_popcountll(value), 1); - - value = 2; - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::fallback_popcountll(value), 1); - - value = 294967496; - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::fallback_popcountll(value), 12); - - value = 8446744073709551416ull; - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::fallback_popcountll(value), 40); - - value = std::numeric_limits::max(); - BOOST_CHECK_EQUAL(dice::sparse_map::detail_popcount::fallback_popcountll(value), 64); -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/sparse_map_tests.cpp b/tests/sparse_map_tests.cpp index 3994e3d..a71274a 100644 --- a/tests/sparse_map_tests.cpp +++ b/tests/sparse_map_tests.cpp @@ -885,7 +885,10 @@ BOOST_AUTO_TEST_CASE(test_at) { BOOST_CHECK_EQUAL(map.at(0), 10); BOOST_CHECK_EQUAL(map.at(-2), 20); - BOOST_CHECK_THROW(map.at(1), std::out_of_range); + + std::int64_t no_discard_dummy; + BOOST_CHECK_THROW(no_discard_dummy = map.at(1), std::out_of_range); + (void) no_discard_dummy; } /** @@ -1240,7 +1243,10 @@ BOOST_AUTO_TEST_CASE(test_heterogeneous_lookups) { BOOST_CHECK_EQUAL(map.at(addr1), 4); BOOST_CHECK_EQUAL(map.at(addr2), 5); - BOOST_CHECK_THROW(map.at(addr_unknown), std::out_of_range); + + int no_discard_dummy; + BOOST_CHECK_THROW(no_discard_dummy = map.at(addr_unknown), std::out_of_range); + (void) no_discard_dummy; BOOST_REQUIRE(map.find(addr1) != map.end()); BOOST_CHECK_EQUAL(*map.find(addr1)->first, 1); @@ -1285,8 +1291,10 @@ BOOST_AUTO_TEST_CASE(test_empty_map) { BOOST_CHECK(!map.contains("")); BOOST_CHECK(!map.contains("test")); - BOOST_CHECK_THROW(map.at(""), std::out_of_range); - BOOST_CHECK_THROW(map.at("test"), std::out_of_range); + int no_discard_dummy; + BOOST_CHECK_THROW(no_discard_dummy = map.at(""), std::out_of_range); + BOOST_CHECK_THROW(no_discard_dummy = map.at("test"), std::out_of_range); + (void) no_discard_dummy; auto range = map.equal_range("test"); BOOST_CHECK(range.first == range.second); From 2e66267cddeed790ffdac10960610db204a05dcf Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Tue, 1 Aug 2023 15:04:50 +0200 Subject: [PATCH 03/41] proper iterator deref types --- include/dice/sparse-map/sparse_hash.hpp | 84 +++++++++---------- include/dice/sparse-map/sparse_map.hpp | 31 ++++--- include/dice/sparse-map/sparse_set.hpp | 7 +- tests/fancy_pointer/sparse_hash_map_tests.cpp | 49 ++++++----- tests/fancy_pointer/sparse_hash_set_tests.cpp | 8 +- .../sparse_hash_set_tests.cpp | 2 +- tests/sparse_map_tests.cpp | 8 +- 7 files changed, 103 insertions(+), 86 deletions(-) diff --git a/include/dice/sparse-map/sparse_hash.hpp b/include/dice/sparse-map/sparse_hash.hpp index afde4c6..360c1b4 100644 --- a/include/dice/sparse-map/sparse_hash.hpp +++ b/include/dice/sparse-map/sparse_hash.hpp @@ -901,7 +901,7 @@ namespace dice::sparse_map { * `sparse_array::sparse_ibucket(ibucket)` and * `sparse_array::index_in_sparse_bucket(ibucket)`. */ - template @@ -909,26 +909,26 @@ namespace dice::sparse_map { private: template struct GetMappedType { - using type = typename VSel::value_type; - using const_reference = const type &; - using reference = type &; - }; - - template<> - struct GetMappedType { using type = void; using const_reference = void; using reference = void; }; + template requires requires { typename VSel::value_type; } + struct GetMappedType { + using type = typename VSel::value_type; + using const_reference = const type &; + using reference = type &; + }; + public: template class sparse_iterator; - using key_type = typename KeySelect::key_type; - using mapped_type = typename GetMappedType::type; - using mapped_const_reference = typename GetMappedType::const_reference; - using mapped_reference = typename GetMappedType::reference; + using key_type = typename KeyValueSelect::key_type; + using mapped_type = typename GetMappedType::type; + using mapped_const_reference = typename GetMappedType::const_reference; + using mapped_reference = typename GetMappedType::reference; using value_type = ValueType; using hasher = Hash; using key_equal = KeyEqual; @@ -989,8 +989,13 @@ namespace dice::sparse_map { using iterator_category = std::forward_iterator_tag; using value_type = const typename sparse_hash::value_type; using difference_type = std::ptrdiff_t; - using reference = value_type &; - using pointer = typename sparse_hash::const_pointer; + using reference = std::conditional_t; + + using pointer = std::conditional_t::template rebind_traits::const_pointer, + typename std::allocator_traits::template rebind_traits::pointer>; sparse_iterator() noexcept {} @@ -1004,22 +1009,10 @@ namespace dice::sparse_map { sparse_iterator &operator=(const sparse_iterator &other) = default; sparse_iterator &operator=(sparse_iterator &&other) = default; - const typename sparse_hash::key_type &key() const { - return KeySelect()(*m_sparse_array_it); - } - - mapped_const_reference value() const requires (has_mapped_type && IsConst) { - return ValueSelect()(*m_sparse_array_it); - } - - mapped_reference value() requires (has_mapped_type && !IsConst) { - return ValueSelect()(*m_sparse_array_it); - } - - reference operator*() const { return *m_sparse_array_it; } + reference operator*() const { return KeyValueSelect::both(*m_sparse_array_it); } //with fancy pointers addressof might be problematic. - pointer operator->() const { return std::addressof(*m_sparse_array_it); } + pointer operator->() const { return &KeyValueSelect::both(*m_sparse_array_it); } sparse_iterator &operator++() { tsl_sh_assert(m_sparse_array_it != nullptr); @@ -1131,8 +1124,11 @@ namespace dice::sparse_map { : m_sparse_buckets_data.data(); } - sparse_hash(sparse_hash &&other) noexcept( - std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value) + sparse_hash(sparse_hash &&other) noexcept(std::is_nothrow_move_constructible::value + && std::is_nothrow_move_constructible::value + && std::is_nothrow_move_constructible::value + && std::is_nothrow_move_constructible::value + && std::is_nothrow_move_constructible::value) : m_sparse_buckets_data(std::move(other.m_sparse_buckets_data)), m_sparse_buckets(m_sparse_buckets_data.empty() ? static_empty_sparse_bucket_ptr() @@ -1293,13 +1289,13 @@ namespace dice::sparse_map { template std::pair insert(P &&value) { - return insert_impl(KeySelect()(value), std::forward

(value)); + return insert_impl(KeyValueSelect::key(value), std::forward

(value)); } template iterator insert_hint(const_iterator hint, P &&value) { if (hint != cend() && - m_keq(KeySelect()(*hint), KeySelect()(value))) { + m_keq(KeyValueSelect::key(*hint), KeyValueSelect::key(value))) { return mutable_iterator(hint); } @@ -1330,7 +1326,7 @@ namespace dice::sparse_map { std::pair insert_or_assign(K &&key, M &&obj) { auto it = try_emplace(std::forward(key), std::forward(obj)); if (!it.second) { - it.first.value() = std::forward(obj); + it.first->second = std::forward(obj); } return it; @@ -1338,9 +1334,9 @@ namespace dice::sparse_map { template iterator insert_or_assign(const_iterator hint, K &&key, M &&obj) { - if (hint != cend() && m_keq(KeySelect()(*hint), key)) { + if (hint != cend() && m_keq(KeyValueSelect::key(*hint), key)) { auto it = mutable_iterator(hint); - it.value() = std::forward(obj); + it->second = std::forward(obj); return it; } @@ -1367,7 +1363,7 @@ namespace dice::sparse_map { template iterator try_emplace_hint(const_iterator hint, K &&key, Args &&...args) { - if (hint != cend() && m_keq(KeySelect()(*hint), key)) { + if (hint != cend() && m_keq(KeyValueSelect::key(*hint), key)) { return mutable_iterator(hint); } @@ -1461,7 +1457,7 @@ namespace dice::sparse_map { template requires (has_mapped_type) mapped_reference at(const K &key, std::size_t hash) { - return const_cast( + return const_cast( static_cast(this)->at(key, hash)); } @@ -1474,7 +1470,7 @@ namespace dice::sparse_map { mapped_const_reference at(const K &key, std::size_t hash) const { auto it = find(key, hash); if (it != cend()) { - return it.value(); + return it->second; } else { throw std::out_of_range("Couldn't find key."); } @@ -1482,7 +1478,7 @@ namespace dice::sparse_map { template requires (has_mapped_type) mapped_reference operator[](K &&key) { - return try_emplace(std::forward(key)).first.value(); + return try_emplace(std::forward(key)).first->second; } template @@ -1715,7 +1711,7 @@ namespace dice::sparse_map { if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { auto value_it = m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); - if (m_keq(key, KeySelect()(*value_it))) { + if (m_keq(key, KeyValueSelect::key(*value_it))) { return std::make_pair( iterator(m_sparse_buckets_data.begin() + sparse_ibucket, value_it), @@ -1781,7 +1777,7 @@ namespace dice::sparse_map { if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { auto value_it = m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); - if (m_keq(key, KeySelect()(*value_it))) { + if (m_keq(key, KeyValueSelect::key(*value_it))) { m_sparse_buckets[sparse_ibucket].erase(m_alloc, value_it, index_in_sparse_bucket); m_nb_elements--; @@ -1822,7 +1818,7 @@ namespace dice::sparse_map { if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { auto value_it = m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); - if (m_keq(key, KeySelect()(*value_it))) { + if (m_keq(key, KeyValueSelect::key(*value_it))) { return const_iterator(m_sparse_buckets_data.cbegin() + sparse_ibucket, value_it); } @@ -1884,7 +1880,7 @@ namespace dice::sparse_map { template void insert_on_rehash(K &&key_value) { - const key_type &key = KeySelect()(key_value); + const key_type &key = KeyValueSelect::key(key_value); const std::size_t hash = m_h(key); std::size_t ibucket = bucket_for_hash(hash); @@ -1903,7 +1899,7 @@ namespace dice::sparse_map { return; } else { tsl_sh_assert(!m_keq( - key, KeySelect()(*m_sparse_buckets[sparse_ibucket].value( + key, KeyValueSelect::key(*m_sparse_buckets[sparse_ibucket].value( index_in_sparse_bucket)))); } diff --git a/include/dice/sparse-map/sparse_map.hpp b/include/dice/sparse-map/sparse_map.hpp index 6a27b78..4b772b8 100644 --- a/include/dice/sparse-map/sparse_map.hpp +++ b/include/dice/sparse-map/sparse_map.hpp @@ -93,31 +93,38 @@ namespace dice::sparse_map { typename KeyEqual::is_transparent; }; - struct KeySelect { + struct KVSelect { using key_type = Key; + using value_type = T; + using both_type = std::pair; - const key_type &operator()(const std::pair &key_value) const noexcept { + template + static key_type const &key(std::pair const &key_value) noexcept { return key_value.first; } - key_type &operator()(std::pair &key_value) noexcept { - return key_value.first; + template + static const value_type &value(std::pair const &key_value) noexcept { + return key_value.second; } - }; - struct ValueSelect { - using value_type = T; - - const value_type &operator()(const std::pair &key_value) const noexcept { + template + static value_type &value(std::pair &key_value) noexcept { return key_value.second; } - value_type &operator()(std::pair &key_value) noexcept { - return key_value.second; + template + static const both_type &both(std::pair const &key_value) noexcept { + return reinterpret_cast(key_value); + } + + template + static both_type &both(std::pair &key_value) noexcept { + return reinterpret_cast(key_value); } }; - using ht = detail_sparse_hash::sparse_hash, KeySelect, ValueSelect, Hash, KeyEqual, Allocator, + using ht = detail_sparse_hash::sparse_hash, KVSelect, Hash, KeyEqual, Allocator, GrowthPolicy, ExceptionSafety, Sparsity, dice::sparse_map::sh::probing::quadratic>; public: diff --git a/include/dice/sparse-map/sparse_set.hpp b/include/dice/sparse-map/sparse_set.hpp index f963765..4e9ea8b 100644 --- a/include/dice/sparse-map/sparse_set.hpp +++ b/include/dice/sparse-map/sparse_set.hpp @@ -94,17 +94,18 @@ namespace dice::sparse_map { struct KeySelect { using key_type = Key; + using both_type = Key const; - const key_type &operator()(const Key &key) const noexcept { + static key_type const &key(Key const &key) noexcept { return key; } - key_type &operator()(Key &key) noexcept { + static both_type &both(Key const &key) noexcept { return key; } }; - using ht = detail_sparse_hash::sparse_hash; diff --git a/tests/fancy_pointer/sparse_hash_map_tests.cpp b/tests/fancy_pointer/sparse_hash_map_tests.cpp index c3a97fc..34696c7 100644 --- a/tests/fancy_pointer/sparse_hash_map_tests.cpp +++ b/tests/fancy_pointer/sparse_hash_map_tests.cpp @@ -13,30 +13,41 @@ */ namespace details { template - struct KeySelect { + struct KeyValueSelect { using key_type = Key; - const key_type &operator()(std::pair const &key_value) const noexcept { - return key_value.first; - } - key_type &operator()(std::pair &key_value) noexcept { - return key_value.first; - } + using value_type = T; + using both_type = std::pair; + + template + static key_type const &key(std::pair const &key_value) noexcept { + return key_value.first; + } + + template + static value_type const &value(std::pair const &key_value) noexcept { + return key_value.second; + } + + template + static value_type &value(std::pair &key_value) noexcept { + return key_value.second; + } + + template + static both_type const &both(std::pair const &key_value) noexcept { + return reinterpret_cast(key_value); + } + + template + static both_type &both(std::pair &key_value) noexcept { + return reinterpret_cast(key_value); + } }; - template - struct ValueSelect { - using value_type = T; - const value_type &operator()(std::pair const &key_value) const noexcept { - return key_value.second; - } - value_type &operator()(std::pair &key_value) noexcept { - return key_value.second; - } - }; template using sparse_map= dice::sparse_map::detail_sparse_hash::sparse_hash< - std::pair, KeySelect, ValueSelect, std::hash, std::equal_to, Alloc, + std::pair, KeyValueSelect, std::hash, std::equal_to, Alloc, dice::sparse_map::sh::power_of_two_growth_policy<2>, dice::sparse_map::sh::exception_safety::basic, dice::sparse_map::sh::sparsity::medium, @@ -98,7 +109,7 @@ void iterator_access(typename T::value_type single_value) { auto map = details::default_construct_map(); map.insert(single_value); //iterator cannot access single value - BOOST_REQUIRE( (*(map.begin()) == single_value)); + BOOST_REQUIRE((*map.begin()).first == single_value.first && (*map.begin()).second == single_value.second); } template diff --git a/tests/fancy_pointer/sparse_hash_set_tests.cpp b/tests/fancy_pointer/sparse_hash_set_tests.cpp index d0a9127..41a3483 100644 --- a/tests/fancy_pointer/sparse_hash_set_tests.cpp +++ b/tests/fancy_pointer/sparse_hash_set_tests.cpp @@ -15,13 +15,15 @@ namespace details { template struct KeySelect { using key_type = Key; - const key_type &operator()(Key const &key) const noexcept { return key; } - key_type &operator()(Key &key) noexcept { return key; } + using both_type = Key const; + + static key_type const &both(Key const &key) noexcept { return key; } + static key_type const &key(Key const &key) noexcept { return key; } }; template using sparse_set = dice::sparse_map::detail_sparse_hash::sparse_hash< - T, KeySelect, void, std::hash, std::equal_to, Alloc, + T, KeySelect, std::hash, std::equal_to, Alloc, dice::sparse_map::sh::power_of_two_growth_policy<2>, dice::sparse_map::sh::exception_safety::basic, dice::sparse_map::sh::sparsity::medium, diff --git a/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp b/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp index ca3bdef..44d55f2 100644 --- a/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp +++ b/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp @@ -11,7 +11,7 @@ template struct KeySelect { template using sparse_set = dice::sparse_map::detail_sparse_hash::sparse_hash< - T, details::KeySelect, void, std::hash, std::equal_to, Alloc, + T, details::KeySelect, std::hash, std::equal_to, Alloc, dice::sparse_map::sh::power_of_two_growth_policy<2>, dice::sparse_map::sh::exception_safety::basic, dice::sparse_map::sh::sparsity::medium, diff --git a/tests/sparse_map_tests.cpp b/tests/sparse_map_tests.cpp index a71274a..9e6f01f 100644 --- a/tests/sparse_map_tests.cpp +++ b/tests/sparse_map_tests.cpp @@ -504,8 +504,8 @@ BOOST_AUTO_TEST_CASE(test_range_erase_same_iterators) { BOOST_CHECK(map.mutable_iterator(it_const) == it_mutable); BOOST_CHECK_EQUAL(map.size(), 100); - it_mutable.value() = -100; - BOOST_CHECK_EQUAL(it_const.value(), -100); + it_mutable->second = -100; + BOOST_CHECK_EQUAL(it_const->second, -100); } /** @@ -617,8 +617,8 @@ BOOST_AUTO_TEST_CASE(test_modify_value_through_iterator) { nb_values); for (auto it = map.begin(); it != map.end(); it++) { - if (it.key() % 2 == 0) { - it.value() = -1; + if (it->first % 2 == 0) { + it->second = -1; } } From bab0712258190ce7c719e7d394e2577a46013064 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Tue, 1 Aug 2023 17:15:14 +0200 Subject: [PATCH 04/41] eliminate some const casts --- .../dice/sparse-map/boost_offset_pointer.hpp | 24 ---- include/dice/sparse-map/sparse_hash.hpp | 115 ++++++++---------- include/dice/sparse-map/sparse_map.hpp | 9 -- include/dice/sparse-map/sparse_set.hpp | 9 -- tests/fancy_pointer/sparse_hash_set_tests.cpp | 1 - tests/sparse_map_tests.cpp | 2 +- 6 files changed, 54 insertions(+), 106 deletions(-) delete mode 100644 include/dice/sparse-map/boost_offset_pointer.hpp diff --git a/include/dice/sparse-map/boost_offset_pointer.hpp b/include/dice/sparse-map/boost_offset_pointer.hpp deleted file mode 100644 index d50c432..0000000 --- a/include/dice/sparse-map/boost_offset_pointer.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef DICE_SPARSE_MAP_BOOST_OFFSET_POINTER_HPP -#define DICE_SPARSE_MAP_BOOST_OFFSET_POINTER_HPP - -#include "dice/sparse-map/sparse_hash.hpp"//needed, so the basic template is already included -#include - -namespace dice::sparse_map { - /* Template specialisation for a "const_cast" of a boost offset_ptr. - * @tparam PT PointedType - * @tparam DT DifferenceType - * @tparam OT OffsetType - * @tparam OA OffsetAlignment - */ - template - struct Remove_Const> { - template - static boost::interprocess::offset_ptr - remove(T const &const_iter) { - return boost::interprocess::const_pointer_cast(const_iter); - } - }; -}// namespace dice::sparse_map - -#endif// DICE_SPARSE_MAP_BOOST_OFFSET_POINTER_HPP diff --git a/include/dice/sparse-map/sparse_hash.hpp b/include/dice/sparse-map/sparse_hash.hpp index 360c1b4..9d3e2a4 100644 --- a/include/dice/sparse-map/sparse_hash.hpp +++ b/include/dice/sparse-map/sparse_hash.hpp @@ -69,20 +69,6 @@ namespace dice::sparse_map { }; }// namespace sh - /* Replacement for const_cast in sparse_array. - * Can be overloaded for specific fancy pointers - * (see: include/dice/boost_offset_pointer.h). - * This is just a workaround. - * The clean way would be to change the implementation to stop using const_cast. - */ - template - struct Remove_Const { - template - static T remove(V iter) { - return const_cast(iter); - } - }; - namespace detail_sparse_hash { template struct make_void { @@ -402,6 +388,17 @@ namespace dice::sparse_map { tsl_sh_assert(m_capacity == 0 && m_nb_elements == 0 && m_values == nullptr); } + /** + * @safety This function is only safe to call if the underlying object is non-const + */ + static iterator unsafe_mutable_iterator(const_iterator pos) noexcept { + if constexpr (std::is_pointer_v) { + return const_cast(pos); + } else { + return iterator{const_cast(std::to_address(pos))}; + } + } + iterator begin() noexcept { return m_values; } iterator end() noexcept { return m_values + m_nb_elements; } const_iterator begin() const noexcept { return cbegin(); } @@ -506,10 +503,6 @@ namespace dice::sparse_map { swap(m_last_array, other.m_last_array); } - static iterator mutable_iterator(const_iterator pos) { - return ::dice::sparse_map::Remove_Const::template remove(pos); - } - template void serialize(Serializer &serializer) const { const slz_size_type sparse_bucket_size = m_nb_elements; @@ -1058,6 +1051,17 @@ namespace dice::sparse_map { sparse_array_iterator m_sparse_array_it; }; + iterator mutable_iterator(const_iterator pos) noexcept { + // SAFETY: this is non-const therefore the underlying buckets are also non-const + // as evidenced by the fact that we can call begin on them + auto it_sparse_buckets = m_sparse_buckets_data.begin() + std::distance(m_sparse_buckets_data.cbegin(), pos.m_sparse_buckets_it); + + // SAFETY: this is non-const therefore the underlying sparse array is also non-const + auto it_array = sparse_array::unsafe_mutable_iterator(pos.m_sparse_array_it); + + return iterator(it_sparse_buckets, it_array); + } + public: sparse_hash(size_type bucket_count, const Hash &hash, const KeyEqual &equal, const Allocator &alloc, float max_load_factor) @@ -1452,28 +1456,22 @@ namespace dice::sparse_map { template requires (has_mapped_type) mapped_reference at(const K &key) { - return at(key, m_h(key)); + return at_impl(*this, key, m_h(key)); } template requires (has_mapped_type) mapped_reference at(const K &key, std::size_t hash) { - return const_cast( - static_cast(this)->at(key, hash)); + return at_impl(*this, key, hash); } template requires (has_mapped_type) mapped_const_reference at(const K &key) const { - return at(key, m_h(key)); + return at_impl(*this, key, m_h(key)); } template requires (has_mapped_type) mapped_const_reference at(const K &key, std::size_t hash) const { - auto it = find(key, hash); - if (it != cend()) { - return it->second; - } else { - throw std::out_of_range("Couldn't find key."); - } + return at_impl(*this, key, hash); } template requires (has_mapped_type) @@ -1507,22 +1505,22 @@ namespace dice::sparse_map { template iterator find(const K &key) { - return find_impl(key, m_h(key)); + return find_impl(*this, key, m_h(key)); } template iterator find(const K &key, std::size_t hash) { - return find_impl(key, hash); + return find_impl(*this, key, hash); } template const_iterator find(const K &key) const { - return find_impl(key, m_h(key)); + return find_impl(*this, key, m_h(key)); } template const_iterator find(const K &key, std::size_t hash) const { - return find_impl(key, hash); + return find_impl(*this, key, hash); } template @@ -1591,15 +1589,6 @@ namespace dice::sparse_map { key_equal key_eq() const { return m_keq; } - iterator mutable_iterator(const_iterator pos) { - auto it_sparse_buckets = - m_sparse_buckets_data.begin() + - std::distance(m_sparse_buckets_data.cbegin(), pos.m_sparse_buckets_it); - - return iterator(it_sparse_buckets, - sparse_array::mutable_iterator(pos.m_sparse_array_it)); - } - template void serialize(Serializer &serializer) const { serialize_impl(serializer); @@ -1796,15 +1785,10 @@ namespace dice::sparse_map { } } - template - iterator find_impl(const K &key, std::size_t hash) { - return mutable_iterator( - static_cast(this)->find(key, hash)); - } - - template - const_iterator find_impl(const K &key, std::size_t hash) const { - std::size_t ibucket = bucket_for_hash(hash); + template + static auto find_impl(Self &&self, const K &key, std::size_t hash) { + static constexpr bool is_const = std::is_const_v>; + std::size_t ibucket = self.bucket_for_hash(hash); std::size_t probe = 0; while (true) { @@ -1812,27 +1796,34 @@ namespace dice::sparse_map { const auto index_in_sparse_bucket = sparse_array::index_in_sparse_bucket(ibucket); - if (m_sparse_buckets == static_empty_sparse_bucket_ptr()) { - return cend(); + if (self.m_sparse_buckets == static_empty_sparse_bucket_ptr()) { + return self.end(); } - if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { - auto value_it = - m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); - if (m_keq(key, KeyValueSelect::key(*value_it))) { - return const_iterator(m_sparse_buckets_data.cbegin() + sparse_ibucket, - value_it); + if (self.m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { + auto value_it = self.m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); + if (self.m_keq(key, KeyValueSelect::key(*value_it))) { + return sparse_iterator{self.m_sparse_buckets_data.begin() + sparse_ibucket, value_it}; } - } else if (!m_sparse_buckets[sparse_ibucket].has_deleted_value( + } else if (!self.m_sparse_buckets[sparse_ibucket].has_deleted_value( index_in_sparse_bucket) || - probe >= m_bucket_count) { - return cend(); + probe >= self.m_bucket_count) { + return self.end(); } probe++; - ibucket = next_bucket(ibucket, probe); + ibucket = self.next_bucket(ibucket, probe); } } + template + static decltype(auto) at_impl(Self &&self, K const &key, std::size_t hash) { + if (auto it = find_impl(self, key, hash); it != self.end()) { + return KeyValueSelect::value(*it); + } + + throw std::out_of_range{"Couldn't find key."}; + } + void clear_deleted_buckets() { // TODO could be optimized, we could do it in-place instead of allocating a // new bucket array. diff --git a/include/dice/sparse-map/sparse_map.hpp b/include/dice/sparse-map/sparse_map.hpp index 4b772b8..fbc1e43 100644 --- a/include/dice/sparse-map/sparse_map.hpp +++ b/include/dice/sparse-map/sparse_map.hpp @@ -31,7 +31,6 @@ #include #include -#include "dice/sparse-map/boost_offset_pointer.hpp" #include "dice/sparse-map/sparse_hash.hpp" namespace dice::sparse_map { @@ -640,14 +639,6 @@ namespace dice::sparse_map { [[nodiscard]] hasher hash_function() const { return m_ht.hash_function(); } [[nodiscard]] key_equal key_eq() const { return m_ht.key_eq(); } - - /** - * Convert a `const_iterator` to an `iterator`. - */ - [[nodiscard]] iterator mutable_iterator(const_iterator pos) { - return m_ht.mutable_iterator(pos); - } - /** * Serialize the map through the `serializer` parameter. * diff --git a/include/dice/sparse-map/sparse_set.hpp b/include/dice/sparse-map/sparse_set.hpp index 4e9ea8b..4804ab1 100644 --- a/include/dice/sparse-map/sparse_set.hpp +++ b/include/dice/sparse-map/sparse_set.hpp @@ -31,7 +31,6 @@ #include #include -#include "dice/sparse-map/boost_offset_pointer.hpp" #include "dice/sparse-map/sparse_hash.hpp" namespace dice::sparse_map { @@ -506,14 +505,6 @@ namespace dice::sparse_map { [[nodiscard]] hasher hash_function() const { return m_ht.hash_function(); } [[nodiscard]] key_equal key_eq() const { return m_ht.key_eq(); } - - /** - * Convert a `const_iterator` to an `iterator`. - */ - [[nodiscard]] iterator mutable_iterator(const_iterator pos) { - return m_ht.mutable_iterator(pos); - } - /** * Serialize the set through the `serializer` parameter. * diff --git a/tests/fancy_pointer/sparse_hash_set_tests.cpp b/tests/fancy_pointer/sparse_hash_set_tests.cpp index 41a3483..ea1db12 100644 --- a/tests/fancy_pointer/sparse_hash_set_tests.cpp +++ b/tests/fancy_pointer/sparse_hash_set_tests.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include "CustomAllocator.hpp" /* Tests are analogous to the tests in sparse_array_tests.cpp. diff --git a/tests/sparse_map_tests.cpp b/tests/sparse_map_tests.cpp index 9e6f01f..88567ed 100644 --- a/tests/sparse_map_tests.cpp +++ b/tests/sparse_map_tests.cpp @@ -501,7 +501,7 @@ BOOST_AUTO_TEST_CASE(test_range_erase_same_iterators) { dice::sparse_map::sparse_map::iterator it_mutable = map.erase(it_const, it_const); BOOST_CHECK(it_const == it_mutable); - BOOST_CHECK(map.mutable_iterator(it_const) == it_mutable); + //BOOST_CHECK(map.mutable_iterator(it_const) == it_mutable); BOOST_CHECK_EQUAL(map.size(), 100); it_mutable->second = -100; From 47cc81fca5fd6370661563632ed9771bf5140892 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Wed, 2 Aug 2023 10:26:59 +0200 Subject: [PATCH 05/41] cleanup sparse array --- .../dice/sparse-map/sparse_growth_policy.hpp | 3 +- include/dice/sparse-map/sparse_hash.hpp | 635 +++++------------- include/dice/sparse-map/sparse_map.hpp | 50 -- include/dice/sparse-map/sparse_set.hpp | 50 -- tests/fancy_pointer/sparse_array_tests.cpp | 5 +- .../sparse_array_tests.cpp | 5 +- tests/sparse_map_tests.cpp | 100 --- tests/sparse_set_tests.cpp | 55 -- 8 files changed, 188 insertions(+), 715 deletions(-) diff --git a/include/dice/sparse-map/sparse_growth_policy.hpp b/include/dice/sparse-map/sparse_growth_policy.hpp index dfcce35..3040932 100644 --- a/include/dice/sparse-map/sparse_growth_policy.hpp +++ b/include/dice/sparse-map/sparse_growth_policy.hpp @@ -73,8 +73,7 @@ namespace dice::sparse_map::sh { } if (min_bucket_count_in_out > 0) { - min_bucket_count_in_out = - round_up_to_power_of_two(min_bucket_count_in_out); + min_bucket_count_in_out = round_up_to_power_of_two(min_bucket_count_in_out); m_mask = min_bucket_count_in_out - 1; } else { m_mask = 0; diff --git a/include/dice/sparse-map/sparse_hash.hpp b/include/dice/sparse-map/sparse_hash.hpp index 9d3e2a4..9058753 100644 --- a/include/dice/sparse-map/sparse_hash.hpp +++ b/include/dice/sparse-map/sparse_hash.hpp @@ -172,8 +172,7 @@ namespace dice::sparse_map { * TODO Check to use std::realloc and std::memmove when possible */ template - class sparse_array { - public: + struct sparse_array { using value_type = T; using size_type = std::uint_least8_t; using allocator_type = Allocator; @@ -184,29 +183,21 @@ namespace dice::sparse_map { using const_iterator = const_pointer; private: - static const size_type CAPACITY_GROWTH_STEP = - (Sparsity == dice::sparse_map::sh::sparsity::high) ? 2 - : (Sparsity == dice::sparse_map::sh::sparsity::medium) - ? 4 - : 8;// (Sparsity == dice::sh::sparsity::low) + using alloc_traits = std::allocator_traits; + + static constexpr size_type CAPACITY_GROWTH_STEP = []() { + switch (Sparsity) { + case dice::sparse_map::sh::sparsity::high: return 2; + case dice::sparse_map::sh::sparsity::medium: return 4; + case dice::sparse_map::sh::sparsity::low: return 8; + } + }(); - /** - * Bitmap size configuration. - * Use 32 bits for the bitmap on 32-bits or less environnement as popcount on - * 64 bits numbers is slow on these environnement. Use 64 bits bitmap - * otherwise. - */ -#if SIZE_MAX <= UINT32_MAX - using bitmap_type = std::uint_least32_t; - static const std::size_t BITMAP_NB_BITS = 32; - static const std::size_t BUCKET_SHIFT = 5; -#else using bitmap_type = std::uint_least64_t; - static const std::size_t BITMAP_NB_BITS = 64; - static const std::size_t BUCKET_SHIFT = 6; -#endif + static constexpr std::size_t BITMAP_NB_BITS = 64; + static constexpr std::size_t BUCKET_SHIFT = 6; - static const std::size_t BUCKET_MASK = BITMAP_NB_BITS - 1; + static constexpr std::size_t BUCKET_MASK = BITMAP_NB_BITS - 1; static_assert(is_power_of_two(BITMAP_NB_BITS), "BITMAP_NB_BITS must be a power of two."); @@ -219,8 +210,17 @@ namespace dice::sparse_map { static_assert(std::is_unsigned::value, "bitmap_type must be unsigned."); static_assert((std::numeric_limits::max() & BUCKET_MASK) == - BITMAP_NB_BITS - 1, - ""); + BITMAP_NB_BITS - 1); + + private: + pointer m_values = nullptr; + + bitmap_type m_bitmap_vals = 0; + bitmap_type m_bitmap_deleted_vals = 0; + + size_type m_nb_elements = 0; + size_type m_capacity = 0; + bool m_last_array = false; public: /** @@ -232,7 +232,7 @@ namespace dice::sparse_map { * m_sparse_buckets[sparse_ibucket(ibucket)][index_in_sparse_bucket(ibucket)] * instead of something like m_buckets[ibucket] in a classical hash table. */ - static std::size_t sparse_ibucket(std::size_t ibucket) { + static constexpr std::size_t sparse_ibucket(std::size_t ibucket) noexcept { return ibucket >> BUCKET_SHIFT; } @@ -244,79 +244,72 @@ namespace dice::sparse_map { * m_sparse_buckets[sparse_ibucket(ibucket)][index_in_sparse_bucket(ibucket)] * instead of something like m_buckets[ibucket] in a classical hash table. */ - static typename sparse_array::size_type index_in_sparse_bucket( - std::size_t ibucket) { - return static_cast( - ibucket & sparse_array::BUCKET_MASK); + static constexpr size_type index_in_sparse_bucket(std::size_t ibucket) noexcept { + return static_cast(ibucket & BUCKET_MASK); } - static std::size_t nb_sparse_buckets(std::size_t bucket_count) noexcept { + static constexpr std::size_t nb_sparse_buckets(std::size_t bucket_count) noexcept { if (bucket_count == 0) { return 0; } - return std::max( - 1, sparse_ibucket(dice::sparse_map::detail_sparse_hash::round_up_to_power_of_two( - bucket_count))); + return std::max(1, sparse_ibucket(round_up_to_power_of_two(bucket_count))); } public: - sparse_array() noexcept - : m_values(nullptr), - m_bitmap_vals(0), - m_bitmap_deleted_vals(0), - m_nb_elements(0), - m_capacity(0), - m_last_array(false) {} + constexpr sparse_array() noexcept = default; //needed for "is_constructible" with no parameters - sparse_array(std::allocator_arg_t, Allocator const &) noexcept : sparse_array() {} + constexpr sparse_array(std::allocator_arg_t, [[maybe_unused]] allocator_type const &alloc) noexcept { + } - explicit sparse_array(bool last_bucket) noexcept + /*explicit sparse_array(bool last_bucket) noexcept : m_values(nullptr), m_bitmap_vals(0), m_bitmap_deleted_vals(0), m_nb_elements(0), m_capacity(0), - m_last_array(last_bucket) {} + m_last_array(last_bucket) {}*/ - //const Allocator needed for MoveInsertable requirement - sparse_array(size_type capacity, Allocator const &const_alloc) - : m_values(nullptr), - m_bitmap_vals(0), - m_bitmap_deleted_vals(0), - m_nb_elements(0), - m_capacity(capacity), - m_last_array(false) { - if (m_capacity > 0) { - auto alloc = const_cast(const_alloc); - m_values = alloc.allocate(m_capacity); - tsl_sh_assert(m_values != - nullptr);// allocate should throw if there is a failure + sparse_array(size_type capacity, allocator_type const &calloc) : m_capacity{capacity} { + if (m_capacity == 0) { + return; } + + auto alloc = calloc; + m_values = alloc_traits::allocate(alloc, m_capacity); + tsl_sh_assert(m_values != nullptr);// allocate should throw if there is a failure } - //const Allocator needed for MoveInsertable requirement - sparse_array(const sparse_array &other, Allocator const &const_alloc) - : m_values(nullptr), - m_bitmap_vals(other.m_bitmap_vals), - m_bitmap_deleted_vals(other.m_bitmap_deleted_vals), - m_nb_elements(0), - m_capacity(other.m_capacity), - m_last_array(other.m_last_array) { + sparse_array(sparse_array const &other) = delete; + + constexpr sparse_array(sparse_array &&other) noexcept : m_values{std::exchange(other.m_values, nullptr)}, + m_bitmap_vals{std::exchange(other.m_bitmap_vals, 0)}, + m_bitmap_deleted_vals{std::exchange(other.m_bitmap_deleted_vals, 0)}, + m_nb_elements{std::exchange(other.m_nb_elements, 0)}, + m_capacity{std::exchange(other.m_capacity, 0)}, + m_last_array{other.m_last_array} { + } + + sparse_array(sparse_array const &other, allocator_type const &calloc) : m_values{nullptr}, + m_bitmap_vals{other.m_bitmap_vals}, + m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, + m_nb_elements{0}, + m_capacity(other.m_capacity), + m_last_array(other.m_last_array) { + auto alloc = calloc; + tsl_sh_assert(other.m_capacity >= other.m_nb_elements); if (m_capacity == 0) { return; } - auto alloc = const_cast(const_alloc); - m_values = alloc.allocate(m_capacity); - tsl_sh_assert(m_values != - nullptr);// allocate should throw if there is a failure + m_values = alloc_traits::allocate(alloc, m_capacity); + tsl_sh_assert(m_values != nullptr);// allocate should throw if there is a failure + try { - for (size_type i = 0; i < other.m_nb_elements; i++) { - construct_value(alloc, m_values + i, other.m_values[i]); - m_nb_elements++; + for (; m_nb_elements < other.m_nb_elements; ++m_nb_elements) { + new (&m_values[m_nb_elements]) value_type{other.m_values[m_nb_elements]}; } } catch (...) { clear(alloc); @@ -324,40 +317,25 @@ namespace dice::sparse_map { } } - sparse_array(sparse_array &&other) noexcept - : m_values(other.m_values), - m_bitmap_vals(other.m_bitmap_vals), - m_bitmap_deleted_vals(other.m_bitmap_deleted_vals), - m_nb_elements(other.m_nb_elements), - m_capacity(other.m_capacity), - m_last_array(other.m_last_array) { - other.m_values = nullptr; - other.m_bitmap_vals = 0; - other.m_bitmap_deleted_vals = 0; - other.m_nb_elements = 0; - other.m_capacity = 0; - } + sparse_array(sparse_array &&other, Allocator const &calloc) : m_values{nullptr}, + m_bitmap_vals{other.m_bitmap_vals}, + m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, + m_nb_elements{0}, + m_capacity{other.m_capacity}, + m_last_array{other.m_last_array} { + auto alloc = calloc; // the only reason the allocator above is not mutable is because of scoped allocators - //const Allocator needed for MoveInsertable requirement - sparse_array(sparse_array &&other, Allocator const &const_alloc) - : m_values(nullptr), - m_bitmap_vals(other.m_bitmap_vals), - m_bitmap_deleted_vals(other.m_bitmap_deleted_vals), - m_nb_elements(0), - m_capacity(other.m_capacity), - m_last_array(other.m_last_array) { tsl_sh_assert(other.m_capacity >= other.m_nb_elements); if (m_capacity == 0) { return; } - auto alloc = const_cast(const_alloc); - m_values = alloc.allocate(m_capacity); - tsl_sh_assert(m_values != - nullptr);// allocate should throw if there is a failure + m_values = alloc_traits::allocate(alloc, m_capacity); + tsl_sh_assert(m_values != nullptr);// allocate should throw if there is a failure + try { for (size_type i = 0; i < other.m_nb_elements; i++) { - construct_value(alloc, m_values + i, std::move(other.m_values[i])); + new (&m_values[i]) value_type{std::move(other.m_values[i])}; m_nb_elements++; } } catch (...) { @@ -366,27 +344,24 @@ namespace dice::sparse_map { } } - sparse_array &operator=(const sparse_array &) = delete; - sparse_array &operator=(sparse_array &&other) noexcept { - this->m_values = other.m_values; - this->m_bitmap_vals = other.m_bitmap_vals; - this->m_bitmap_deleted_vals = other.m_bitmap_deleted_vals; - this->m_nb_elements = other.m_nb_elements; - this->m_capacity = other.m_capacity; - other.m_values = nullptr; - other.m_bitmap_vals = 0; - other.m_bitmap_deleted_vals = 0; - other.m_nb_elements = 0; - other.m_capacity = 0; + sparse_array &operator=(sparse_array const &) = delete; + + constexpr sparse_array &operator=(sparse_array &&other) noexcept { + tsl_sh_assert(this != &other); + + this->m_values = std::exchange(other.m_values, nullptr); + this->m_bitmap_vals = std::exchange(other.m_bitmap_vals, 0); + this->m_bitmap_deleted_vals = std::exchange(other.m_bitmap_deleted_vals, 0); + this->m_nb_elements = std::exchange(other.m_nb_elements, 0); + this->m_capacity = std::exchange(other.m_capacity, 0); + return *this; } - ~sparse_array() noexcept { - // The code that manages the sparse_array must have called clear before - // destruction. See documentation of sparse_array for more details. - tsl_sh_assert(m_capacity == 0 && m_nb_elements == 0 && m_values == nullptr); - } + // The code that manages the sparse_array must have called clear before + // destruction. See documentation of sparse_array for more details. + ~sparse_array() noexcept = default; /** * @safety This function is only safe to call if the underlying object is non-const @@ -399,16 +374,16 @@ namespace dice::sparse_map { } } - iterator begin() noexcept { return m_values; } - iterator end() noexcept { return m_values + m_nb_elements; } - const_iterator begin() const noexcept { return cbegin(); } - const_iterator end() const noexcept { return cend(); } - const_iterator cbegin() const noexcept { return m_values; } - const_iterator cend() const noexcept { return m_values + m_nb_elements; } + [[nodiscard]] constexpr iterator begin() noexcept { return m_values; } + [[nodiscard]] constexpr iterator end() noexcept { return m_values + m_nb_elements; } + [[nodiscard]] constexpr const_iterator begin() const noexcept { return cbegin(); } + [[nodiscard]] constexpr const_iterator end() const noexcept { return cend(); } + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return m_values; } + [[nodiscard]] constexpr const_iterator cend() const noexcept { return m_values + m_nb_elements; } - bool empty() const noexcept { return m_nb_elements == 0; } + [[nodiscard]] constexpr bool empty() const noexcept { return m_nb_elements == 0; } - size_type size() const noexcept { return m_nb_elements; } + [[nodiscard]] constexpr size_type size() const noexcept { return m_nb_elements; } void clear(allocator_type &alloc) noexcept { destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); @@ -420,16 +395,16 @@ namespace dice::sparse_map { m_capacity = 0; } - bool last() const noexcept { return m_last_array; } + [[nodiscard]] constexpr bool last() const noexcept { return m_last_array; } - void set_as_last() noexcept { m_last_array = true; } + constexpr void set_as_last() noexcept { m_last_array = true; } - bool has_value(size_type index) const noexcept { + [[nodiscard]] constexpr bool has_value(size_type index) const noexcept { tsl_sh_assert(index < BITMAP_NB_BITS); return (m_bitmap_vals & (bitmap_type(1) << index)) != 0; } - bool has_deleted_value(size_type index) const noexcept { + [[nodiscard]] constexpr bool has_deleted_value(size_type index) const noexcept { tsl_sh_assert(index < BITMAP_NB_BITS); return (m_bitmap_deleted_vals & (bitmap_type(1) << index)) != 0; } @@ -455,8 +430,7 @@ namespace dice::sparse_map { insert_at_offset(alloc, offset, std::forward(value_args)...); m_bitmap_vals = (m_bitmap_vals | (bitmap_type(1) << index)); - m_bitmap_deleted_vals = - (m_bitmap_deleted_vals & ~(bitmap_type(1) << index)); + m_bitmap_deleted_vals = (m_bitmap_deleted_vals & ~(bitmap_type(1) << index)); m_nb_elements++; @@ -467,8 +441,7 @@ namespace dice::sparse_map { } iterator erase(allocator_type &alloc, iterator position) { - const size_type offset = - static_cast(std::distance(begin(), position)); + auto const offset = static_cast(std::distance(begin(), position)); return erase(alloc, position, offset_to_index(offset)); } @@ -477,8 +450,7 @@ namespace dice::sparse_map { tsl_sh_assert(has_value(index)); tsl_sh_assert(!has_deleted_value(index)); - const size_type offset = - static_cast(std::distance(begin(), position)); + auto const offset = static_cast(std::distance(begin(), position)); erase_at_offset(alloc, offset); m_bitmap_vals = (m_bitmap_vals & ~(bitmap_type(1) << index)); @@ -503,123 +475,29 @@ namespace dice::sparse_map { swap(m_last_array, other.m_last_array); } - template - void serialize(Serializer &serializer) const { - const slz_size_type sparse_bucket_size = m_nb_elements; - serializer(sparse_bucket_size); - - const slz_size_type bitmap_vals = m_bitmap_vals; - serializer(bitmap_vals); - - const slz_size_type bitmap_deleted_vals = m_bitmap_deleted_vals; - serializer(bitmap_deleted_vals); - - for (const value_type &value : *this) { - serializer(value); - } - } - - template - static sparse_array deserialize_hash_compatible(Deserializer &deserializer, - Allocator &alloc) { - const slz_size_type sparse_bucket_size = - deserialize_value(deserializer); - const slz_size_type bitmap_vals = - deserialize_value(deserializer); - const slz_size_type bitmap_deleted_vals = - deserialize_value(deserializer); - - if (sparse_bucket_size > BITMAP_NB_BITS) { - throw std::runtime_error( - "Deserialized sparse_bucket_size is too big for the platform. " - "Maximum should be BITMAP_NB_BITS."); - } - - sparse_array sarray; - if (sparse_bucket_size == 0) { - return sarray; - } - - sarray.m_bitmap_vals = numeric_cast( - bitmap_vals, "Deserialized bitmap_vals is too big."); - sarray.m_bitmap_deleted_vals = numeric_cast( - bitmap_deleted_vals, "Deserialized bitmap_deleted_vals is too big."); - - sarray.m_capacity = numeric_cast( - sparse_bucket_size, "Deserialized sparse_bucket_size is too big."); - sarray.m_values = alloc.allocate(sarray.m_capacity); - - try { - for (size_type ivalue = 0; ivalue < sarray.m_capacity; ivalue++) { - construct_value(alloc, sarray.m_values + ivalue, - deserialize_value(deserializer)); - sarray.m_nb_elements++; - } - } catch (...) { - sarray.clear(alloc); - throw; - } - - return sarray; - } - - /** - * Deserialize the values of the bucket and insert them all in sparse_hash - * through sparse_hash.insert(...). - */ - template - static void deserialize_values_into_sparse_hash(Deserializer &deserializer, - SparseHash &sparse_hash) { - const slz_size_type sparse_bucket_size = - deserialize_value(deserializer); - - const slz_size_type bitmap_vals = - deserialize_value(deserializer); - static_cast(bitmap_vals);// Ignore, not needed - - const slz_size_type bitmap_deleted_vals = - deserialize_value(deserializer); - static_cast(bitmap_deleted_vals);// Ignore, not needed - - for (slz_size_type ivalue = 0; ivalue < sparse_bucket_size; ivalue++) { - sparse_hash.insert(deserialize_value(deserializer)); - } - } - private: - template - static void construct_value(allocator_type &alloc, pointer value, - Args &&...value_args) { - std::allocator_traits::construct( - alloc, std::to_address(value), std::forward(value_args)...); - } - - static void destroy_value(allocator_type &alloc, pointer value) noexcept { - std::allocator_traits::destroy(alloc, std::to_address(value)); - } - - static void destroy_and_deallocate_values( - allocator_type &alloc, pointer values, size_type nb_values, - size_type capacity_values) noexcept { + static void destroy_and_deallocate_values(allocator_type &alloc, + pointer values, + size_type nb_values, + size_type capacity_values) noexcept { for (size_type i = 0; i < nb_values; i++) { - destroy_value(alloc, values + i); + values[i].~value_type(); } - alloc.deallocate(values, capacity_values); + alloc_traits::deallocate(alloc, values, capacity_values); } - static size_type popcount(bitmap_type val) noexcept { + [[nodiscard]] static constexpr size_type popcount(bitmap_type val) noexcept { return std::popcount(val); } - size_type index_to_offset(size_type index) const noexcept { + [[nodiscard]] constexpr size_type index_to_offset(size_type index) const noexcept { tsl_sh_assert(index < BITMAP_NB_BITS); - return popcount(m_bitmap_vals & - ((bitmap_type(1) << index) - bitmap_type(1))); + return popcount(m_bitmap_vals & ((bitmap_type(1) << index) - bitmap_type(1))); } // TODO optimize - size_type offset_to_index(size_type offset) const noexcept { + [[nodiscard]] constexpr size_type offset_to_index(size_type offset) const noexcept { tsl_sh_assert(offset < m_nb_elements); bitmap_type bitmap_vals = m_bitmap_vals; @@ -642,7 +520,7 @@ namespace dice::sparse_map { return index; } - size_type next_capacity() const noexcept { + [[nodiscard]] constexpr size_type next_capacity() const noexcept { return static_cast(m_capacity + CAPACITY_GROWTH_STEP); } @@ -663,80 +541,64 @@ namespace dice::sparse_map { * success, we set m_values to this new area. Even if slower, it's the only * way to preserve to strong exception guarantee. */ - template::value>::type * = nullptr> - void insert_at_offset(allocator_type &alloc, size_type offset, - Args &&...value_args) { + template requires (std::is_nothrow_move_constructible_v) + void insert_at_offset(allocator_type &alloc, size_type offset, Args &&...value_args) { if (m_nb_elements < m_capacity) { - insert_at_offset_no_realloc(alloc, offset, - std::forward(value_args)...); + insert_at_offset_no_realloc(offset, std::forward(value_args)...); } else { - insert_at_offset_realloc(alloc, offset, next_capacity(), - std::forward(value_args)...); + insert_at_offset_realloc(alloc, offset, next_capacity(), std::forward(value_args)...); } } - template::value>::type * = nullptr> - void insert_at_offset(allocator_type &alloc, size_type offset, - Args &&...value_args) { - insert_at_offset_realloc(alloc, offset, m_nb_elements + 1, - std::forward(value_args)...); + template requires (!std::is_nothrow_move_constructible_v) + void insert_at_offset(allocator_type &alloc, size_type offset, Args &&...value_args) { + insert_at_offset_realloc(alloc, offset, m_nb_elements + 1, std::forward(value_args)...); } - template::value>::type * = nullptr> - void insert_at_offset_no_realloc(allocator_type &alloc, size_type offset, - Args &&...value_args) { + template requires (std::is_nothrow_move_constructible_v) + void insert_at_offset_no_realloc(size_type offset, Args &&...value_args) { tsl_sh_assert(offset <= m_nb_elements); tsl_sh_assert(m_nb_elements < m_capacity); for (size_type i = m_nb_elements; i > offset; i--) { - construct_value(alloc, m_values + i, std::move(m_values[i - 1])); - destroy_value(alloc, m_values + i - 1); + new (&m_values[i]) value_type{std::move(m_values[i - 1])}; + m_values[i - 1].~value_type(); } try { - construct_value(alloc, m_values + offset, - std::forward(value_args)...); + new (&m_values[offset]) value_type{std::forward(value_args)...}; } catch (...) { + // revert for (size_type i = offset; i < m_nb_elements; i++) { - construct_value(alloc, m_values + i, std::move(m_values[i + 1])); - destroy_value(alloc, m_values + i + 1); + new (&m_values[i]) value_type{std::move(m_values[i + 1])}; + m_values[i + 1].~value_type(); } throw; } } - template::value>::type * = nullptr> + template requires (std::is_nothrow_move_constructible_v) void insert_at_offset_realloc(allocator_type &alloc, size_type offset, size_type new_capacity, Args &&...value_args) { tsl_sh_assert(new_capacity > m_nb_elements); - pointer new_values = alloc.allocate(new_capacity); - // Allocate should throw if there is a failure - tsl_sh_assert(new_values != nullptr); + pointer new_values = alloc_traits::allocate(alloc, new_capacity); + tsl_sh_assert(new_values != nullptr); // Allocate should throw if there is a failure try { - construct_value(alloc, new_values + offset, - std::forward(value_args)...); + new (&new_values[offset]) value_type{std::forward(value_args)...}; } catch (...) { - alloc.deallocate(new_values, new_capacity); + alloc_traits::deallocate(alloc, new_values, new_capacity); throw; } // Should not throw from here for (size_type i = 0; i < offset; i++) { - construct_value(alloc, new_values + i, std::move(m_values[i])); + new (&new_values[i]) value_type{std::move(m_values[i])}; } for (size_type i = offset; i < m_nb_elements; i++) { - construct_value(alloc, new_values + i + 1, std::move(m_values[i])); + new (&new_values[i + 1]) value_type{std::move(m_values[i])}; } destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); @@ -745,35 +607,29 @@ namespace dice::sparse_map { m_capacity = new_capacity; } - template::value>::type * = nullptr> - void insert_at_offset_realloc(allocator_type &alloc, size_type offset, - size_type new_capacity, Args &&...value_args) { + template requires (!std::is_nothrow_move_constructible_v) + void insert_at_offset_realloc(allocator_type &alloc, size_type offset, size_type new_capacity, Args &&...value_args) { tsl_sh_assert(new_capacity > m_nb_elements); - value_type *new_values = alloc.allocate(new_capacity); - // Allocate should throw if there is a failure - tsl_sh_assert(new_values != nullptr); + pointer new_values = alloc_traits::allocate(alloc, new_capacity); + tsl_sh_assert(new_values != nullptr); // Allocate should throw if there is a failure size_type nb_new_values = 0; try { for (size_type i = 0; i < offset; i++) { - construct_value(alloc, new_values + i, m_values[i]); + new (&new_values[i]) value_type{m_values[i]}; nb_new_values++; } - construct_value(alloc, new_values + offset, - std::forward(value_args)...); + new (&new_values[offset]) value_type{std::forward(value_args)...}; nb_new_values++; for (size_type i = offset; i < m_nb_elements; i++) { - construct_value(alloc, new_values + i + 1, m_values[i]); + new (&new_values[i + 1]) value_type{m_values[i]}; nb_new_values++; } } catch (...) { - destroy_and_deallocate_values(alloc, new_values, nb_new_values, - new_capacity); + destroy_and_deallocate_values(alloc, new_values, nb_new_values, new_capacity); throw; } @@ -790,58 +646,55 @@ namespace dice::sparse_map { * * Two situations: * - Either we are in a situation where - * std::is_nothrow_move_constructible::value is true. Simply - * destroy the value and left-shift move the value on the right of offset. + * std::is_nothrow_move_constructible::value is true. Simply + * destroy the value and left-shift move the value on the right of offset. * - Otherwise we are in a situation where - * std::is_nothrow_move_constructible::value is false. Copy all - * the values except the one at offset into a new heap area. On success, we - * set m_values to this new area. Even if slower, it's the only way to - * preserve to strong exception guarantee. + * std::is_nothrow_move_constructible::value is false. Copy all + * the values except the one at offset into a new heap area. On success, we + * set m_values to this new area. Even if slower, it's the only way to + * preserve to strong exception guarantee. */ - template::value>::type * = nullptr> - void erase_at_offset(allocator_type &alloc, size_type offset) noexcept { + template requires (std::is_nothrow_move_constructible_v) + void erase_at_offset([[maybe_unused]] allocator_type &alloc, size_type offset) noexcept { tsl_sh_assert(offset < m_nb_elements); - destroy_value(alloc, m_values + offset); + m_values[offset].~value_type(); - for (size_type i = offset + 1; i < m_nb_elements; i++) { - construct_value(alloc, m_values + i - 1, std::move(m_values[i])); - destroy_value(alloc, m_values + i); + for (size_type i = offset + 1; i < m_nb_elements; ++i) { + new (&m_values[i - 1]) value_type{std::move(m_values[i])}; + m_values[i].~value_type(); } } - template::value>::type * = nullptr> + template requires (!std::is_nothrow_move_constructible_v) void erase_at_offset(allocator_type &alloc, size_type offset) { tsl_sh_assert(offset < m_nb_elements); - // Erasing the last element, don't need to reallocate. We keep the capacity. if (offset + 1 == m_nb_elements) { - destroy_value(alloc, m_values + offset); + // Erasing the last element, don't need to reallocate. We keep the capacity. + m_values[offset].~value_type(); return; } tsl_sh_assert(m_nb_elements > 1); - const size_type new_capacity = m_nb_elements - 1; + auto const new_capacity = m_nb_elements - 1; - value_type *new_values = alloc.allocate(new_capacity); - // Allocate should throw if there is a failure - tsl_sh_assert(new_values != nullptr); + pointer new_values = alloc_traits::allocate(alloc, new_capacity); + tsl_sh_assert(new_values != nullptr); // Allocate should throw if there is a failure size_type nb_new_values = 0; try { - for (size_type i = 0; i < m_nb_elements; i++) { - if (i != offset) { - construct_value(alloc, new_values + nb_new_values, m_values[i]); - nb_new_values++; - } + for (size_type i = 0; i < offset; ++i) { + new (&new_values[i]) value_type{m_values[i]}; + nb_new_values++; + } + + for (size_type i = offset + 1; i < m_nb_elements; ++i) { + new (&new_values[i - 1]) value_type{m_values[i]}; + nb_new_values++; } } catch (...) { - destroy_and_deallocate_values(alloc, new_values, nb_new_values, - new_capacity); + destroy_and_deallocate_values(alloc, new_values, nb_new_values, new_capacity); throw; } @@ -852,16 +705,6 @@ namespace dice::sparse_map { m_values = new_values; m_capacity = new_capacity; } - - private: - pointer m_values; - - bitmap_type m_bitmap_vals; - bitmap_type m_bitmap_deleted_vals; - - size_type m_nb_elements; - size_type m_capacity; - bool m_last_array; }; /** @@ -1585,26 +1428,14 @@ namespace dice::sparse_map { rehash(size_type(std::ceil(float(count) / max_load_factor()))); } - hasher hash_function() const { return m_h; } - - key_equal key_eq() const { return m_keq; } - - template - void serialize(Serializer &serializer) const { - serialize_impl(serializer); - } - - template - void deserialize(Deserializer &deserializer, bool hash_compatible) { - deserialize_impl(deserializer, hash_compatible); - } + [[nodiscard]] hasher hash_function() const { return m_h; } + [[nodiscard]] key_equal key_eq() const { return m_keq; } private: size_type bucket_for_hash(std::size_t hash) const { - const std::size_t bucket = m_gpol.bucket_for_hash(hash); - tsl_sh_assert(sparse_array::sparse_ibucket(bucket) < - m_sparse_buckets_data.size() || - (bucket == 0 && m_sparse_buckets_data.empty())); + auto const bucket = m_gpol.bucket_for_hash(hash); + tsl_sh_assert(sparse_array::sparse_ibucket(bucket) < m_sparse_buckets_data.size() + || (bucket == 0 && m_sparse_buckets_data.empty())); return bucket; } @@ -1899,120 +1730,12 @@ namespace dice::sparse_map { } } - template - void serialize_impl(Serializer &serializer) const { - const slz_size_type version = SERIALIZATION_PROTOCOL_VERSION; - serializer(version); - - const slz_size_type bucket_count = m_bucket_count; - serializer(bucket_count); - - const slz_size_type nb_sparse_buckets = m_sparse_buckets_data.size(); - serializer(nb_sparse_buckets); - - const slz_size_type nb_elements = m_nb_elements; - serializer(nb_elements); - - const slz_size_type nb_deleted_buckets = m_nb_deleted_buckets; - serializer(nb_deleted_buckets); - - const float max_load_factor = m_max_load_factor; - serializer(max_load_factor); - - for (const auto &bucket : m_sparse_buckets_data) { - bucket.serialize(serializer); - } - } - - template - void deserialize_impl(Deserializer &deserializer, bool hash_compatible) { - tsl_sh_assert( - m_bucket_count == 0 && - m_sparse_buckets_data.empty());// Current hash table must be empty - - const slz_size_type version = - deserialize_value(deserializer); - // For now we only have one version of the serialization protocol. - // If it doesn't match there is a problem with the file. - if (version != SERIALIZATION_PROTOCOL_VERSION) { - throw std::runtime_error( - "Can't deserialize the sparse_map/set. The " - "protocol version header is invalid."); - } - - const slz_size_type bucket_count_ds = - deserialize_value(deserializer); - const slz_size_type nb_sparse_buckets = - deserialize_value(deserializer); - const slz_size_type nb_elements = - deserialize_value(deserializer); - const slz_size_type nb_deleted_buckets = - deserialize_value(deserializer); - const float max_load_factor = deserialize_value(deserializer); - - if (!hash_compatible) { - this->max_load_factor(max_load_factor); - reserve(numeric_cast(nb_elements, - "Deserialized nb_elements is too big.")); - for (slz_size_type ibucket = 0; ibucket < nb_sparse_buckets; ibucket++) { - sparse_array::deserialize_values_into_sparse_hash(deserializer, *this); - } - } else { - m_bucket_count = numeric_cast( - bucket_count_ds, "Deserialized bucket_count is too big."); - - m_gpol = GrowthPolicy(m_bucket_count); - // GrowthPolicy should not modify the bucket count we got from - // deserialization - if (m_bucket_count != bucket_count_ds) { - throw std::runtime_error( - "The GrowthPolicy is not the same even though " - "hash_compatible is true."); - } - - if (nb_sparse_buckets != - sparse_array::nb_sparse_buckets(m_bucket_count)) { - throw std::runtime_error("Deserialized nb_sparse_buckets is invalid."); - } - - m_nb_elements = numeric_cast( - nb_elements, "Deserialized nb_elements is too big."); - m_nb_deleted_buckets = numeric_cast( - nb_deleted_buckets, "Deserialized nb_deleted_buckets is too big."); - - m_sparse_buckets_data.reserve(numeric_cast( - nb_sparse_buckets, "Deserialized nb_sparse_buckets is too big.")); - for (slz_size_type ibucket = 0; ibucket < nb_sparse_buckets; ibucket++) { - m_sparse_buckets_data.emplace_back( - sparse_array::deserialize_hash_compatible( - deserializer, m_alloc)); - } - - if (!m_sparse_buckets_data.empty()) { - m_sparse_buckets_data.back().set_as_last(); - m_sparse_buckets = m_sparse_buckets_data.data(); - } - - this->max_load_factor(max_load_factor); - if (load_factor() > this->max_load_factor()) { - throw std::runtime_error( - "Invalid max_load_factor. Check that the serializer and " - "deserializer support " - "floats correctly as they can be converted implicitely to ints."); - } - } - } - public: - static const size_type DEFAULT_INIT_BUCKET_COUNT = 0; + static constexpr size_type DEFAULT_INIT_BUCKET_COUNT = 0; static constexpr float DEFAULT_MAX_LOAD_FACTOR = 0.5f; - /** - * Protocol version currenlty used for serialization. - */ - static const slz_size_type SERIALIZATION_PROTOCOL_VERSION = 1; - using sparse_array_ptr = typename std::allocator_traits::template rebind_traits::pointer; + /** * Return an nullptr to indicate an empty bucket */ diff --git a/include/dice/sparse-map/sparse_map.hpp b/include/dice/sparse-map/sparse_map.hpp index fbc1e43..41a44b8 100644 --- a/include/dice/sparse-map/sparse_map.hpp +++ b/include/dice/sparse-map/sparse_map.hpp @@ -639,56 +639,6 @@ namespace dice::sparse_map { [[nodiscard]] hasher hash_function() const { return m_ht.hash_function(); } [[nodiscard]] key_equal key_eq() const { return m_ht.key_eq(); } - /** - * Serialize the map through the `serializer` parameter. - * - * The `serializer` parameter must be a function object that supports the - * following call: - * - `template void operator()(const U& value);` where the types - * `std::uint64_t`, `float` and `std::pair` must be supported for U. - * - * The implementation leaves binary compatibility (endianness, IEEE 754 for - * floats, ...) of the types it serializes in the hands of the `Serializer` - * function object if compatibility is required. - */ - template - void serialize(Serializer &serializer) const { - m_ht.serialize(serializer); - } - - /** - * Deserialize a previously serialized map through the `deserializer` - * parameter. - * - * The `deserializer` parameter must be a function object that supports the - * following calls: - * - `template U operator()();` where the types `std::uint64_t`, - * `float` and `std::pair` must be supported for U. - * - * If the deserialized hash map type is hash compatible with the serialized - * map, the deserialization process can be sped up by setting - * `hash_compatible` to true. To be hash compatible, the Hash, KeyEqual and - * GrowthPolicy must behave the same way than the ones used on the serialized - * map. The `std::size_t` must also be of the same size as the one on the - * platform used to serialize the map. If these criteria are not met, the - * behaviour is undefined with `hash_compatible` sets to true. - * - * The behaviour is undefined if the type `Key` and `T` of the `sparse_map` - * are not the same as the types used during serialization. - * - * The implementation leaves binary compatibility (endianness, IEEE 754 for - * floats, size of int, ...) of the types it deserializes in the hands of the - * `Deserializer` function object if compatibility is required. - */ - template - static sparse_map deserialize(Deserializer &deserializer, - bool hash_compatible = false) { - sparse_map map(0); - map.m_ht.deserialize(deserializer, hash_compatible); - - return map; - } - friend bool operator==(const sparse_map &lhs, const sparse_map &rhs) { if (lhs.size() != rhs.size()) { return false; diff --git a/include/dice/sparse-map/sparse_set.hpp b/include/dice/sparse-map/sparse_set.hpp index 4804ab1..ebda9dc 100644 --- a/include/dice/sparse-map/sparse_set.hpp +++ b/include/dice/sparse-map/sparse_set.hpp @@ -505,56 +505,6 @@ namespace dice::sparse_map { [[nodiscard]] hasher hash_function() const { return m_ht.hash_function(); } [[nodiscard]] key_equal key_eq() const { return m_ht.key_eq(); } - /** - * Serialize the set through the `serializer` parameter. - * - * The `serializer` parameter must be a function object that supports the - * following call: - * - `void operator()(const U& value);` where the types `std::uint64_t`, - * `float` and `Key` must be supported for U. - * - * The implementation leaves binary compatibility (endianness, IEEE 754 for - * floats, ...) of the types it serializes in the hands of the `Serializer` - * function object if compatibility is required. - */ - template - void serialize(Serializer &serializer) const { - m_ht.serialize(serializer); - } - - /** - * Deserialize a previously serialized set through the `deserializer` - * parameter. - * - * The `deserializer` parameter must be a function object that supports the - * following calls: - * - `template U operator()();` where the types `std::uint64_t`, - * `float` and `Key` must be supported for U. - * - * If the deserialized hash set type is hash compatible with the serialized - * set, the deserialization process can be sped up by setting - * `hash_compatible` to true. To be hash compatible, the Hash, KeyEqual and - * GrowthPolicy must behave the same way than the ones used on the serialized - * set. The `std::size_t` must also be of the same size as the one on the - * platform used to serialize the set. If these criteria are not met, the - * behaviour is undefined with `hash_compatible` sets to true. - * - * The behaviour is undefined if the type `Key` of the `sparse_set` is not the - * same as the type used during serialization. - * - * The implementation leaves binary compatibility (endianness, IEEE 754 for - * floats, size of int, ...) of the types it deserializes in the hands of the - * `Deserializer` function object if compatibility is required. - */ - template - static sparse_set deserialize(Deserializer &deserializer, - bool hash_compatible = false) { - sparse_set set(0); - set.m_ht.deserialize(deserializer, hash_compatible); - - return set; - } - friend bool operator==(const sparse_set &lhs, const sparse_set &rhs) { if (lhs.size() != rhs.size()) { return false; diff --git a/tests/fancy_pointer/sparse_array_tests.cpp b/tests/fancy_pointer/sparse_array_tests.cpp index e243def..f35666a 100644 --- a/tests/fancy_pointer/sparse_array_tests.cpp +++ b/tests/fancy_pointer/sparse_array_tests.cpp @@ -17,6 +17,7 @@ constexpr auto MAX_INDEX = 32; //BITMAP_NB_BITS template void compilation() { typename T::Array test; + (void) test; } template @@ -31,7 +32,7 @@ namespace details { typename T::Array generate_test_array(typename T::Allocator &a) { typename T::Array arr(MAX_INDEX, a); for (std::size_t i = 0; i < MAX_INDEX; ++i) { - arr.set(a, i, i); + arr.set(a, i, static_cast(i)); } return arr; } @@ -100,6 +101,7 @@ struct STD { using Allocator = std::allocator; using Array = dice::sparse_map::detail_sparse_hash::sparse_array, Sparsity>; using Const_Iterator = T const*; + using Value_Type = T; }; template @@ -107,6 +109,7 @@ struct CUSTOM { using Allocator = OffsetAllocator; using Array = dice::sparse_map::detail_sparse_hash::sparse_array, Sparsity>; using Const_Iterator = boost::interprocess::offset_ptr; + using Value_Type = T; }; diff --git a/tests/scoped_allocator_adaptor/sparse_array_tests.cpp b/tests/scoped_allocator_adaptor/sparse_array_tests.cpp index bd7b45a..93ce947 100644 --- a/tests/scoped_allocator_adaptor/sparse_array_tests.cpp +++ b/tests/scoped_allocator_adaptor/sparse_array_tests.cpp @@ -9,7 +9,10 @@ // Globals constexpr auto MAX_INDEX = 32; // BITMAP_NB_BITS -template void compilation() { typename T::Array test; } +template void compilation() { + typename T::Array test; + (void) test; +} template void construction() { typename T::Allocator a; diff --git a/tests/sparse_map_tests.cpp b/tests/sparse_map_tests.cpp index 88567ed..776a8df 100644 --- a/tests/sparse_map_tests.cpp +++ b/tests/sparse_map_tests.cpp @@ -974,106 +974,6 @@ BOOST_AUTO_TEST_CASE(test_swap_empty) { {1, 10}, {8, 80}, {3, 30}, {4, 40}})); } -/** - * serialize and deserialize - */ -BOOST_AUTO_TEST_CASE(test_serialize_deserialize_empty) { - // serialize empty map; deserialize in new map; check equal. - // for deserialization, test it with and without hash compatibility. - const dice::sparse_map::sparse_map empty_map(0); - - serializer serial; - empty_map.serialize(serial); - - deserializer dserial(serial.str()); - auto empty_map_deserialized = decltype(empty_map)::deserialize(dserial, true); - BOOST_CHECK(empty_map_deserialized == empty_map); - - deserializer dserial2(serial.str()); - empty_map_deserialized = decltype(empty_map)::deserialize(dserial2, false); - BOOST_CHECK(empty_map_deserialized == empty_map); -} - -BOOST_AUTO_TEST_CASE(test_serialize_deserialize_few) { - // insert x values that fits into one sparse bucket; delete some values; - // serialize map; deserialize in new map; check equal. for deserialization, - // test it with and without hash compatibility. - const dice::sparse_map::sparse_map map{ - {10, 100}, {4, 14}, {9, 201}}; - - serializer serial; - map.serialize(serial); - - deserializer dserial(serial.str()); - auto map_deserialized = decltype(map)::deserialize(dserial, true); - BOOST_CHECK(map_deserialized == map); - - deserializer dserial2(serial.str()); - map_deserialized = decltype(map)::deserialize(dserial2, false); - BOOST_CHECK(map_deserialized == map); -} - -BOOST_AUTO_TEST_CASE(test_serialize_deserialize) { - // insert x values; delete some values; serialize map; deserialize in new map; - // check equal. for deserialization, test it with and without hash - // compatibility. - const std::size_t nb_values = 1000; - - dice::sparse_map::sparse_map map; - for (std::size_t i = 0; i < nb_values + 40; i++) { - map.insert( - {utils::get_key(i), utils::get_value(i)}); - } - - for (std::size_t i = nb_values; i < nb_values + 40; i++) { - map.erase(utils::get_key(i)); - } - BOOST_CHECK_EQUAL(map.size(), nb_values); - - serializer serial; - map.serialize(serial); - - deserializer dserial(serial.str()); - auto map_deserialized = decltype(map)::deserialize(dserial, true); - BOOST_CHECK(map == map_deserialized); - - deserializer dserial2(serial.str()); - map_deserialized = decltype(map)::deserialize(dserial2, false); - BOOST_CHECK(map_deserialized == map); -} - -BOOST_AUTO_TEST_CASE(test_serialize_deserialize_with_different_hash) { - // insert x values; serialize map; deserialize in new map which has a - // different hash; check equal - struct hash_str_diff { - std::size_t operator()(const std::string& str) const { - return std::hash()(str) + 123; - } - }; - - const std::size_t nb_values = 1000; - - dice::sparse_map::sparse_map map; - for (std::size_t i = 0; i < nb_values; i++) { - map.insert( - {utils::get_key(i), utils::get_value(i)}); - } - BOOST_CHECK_EQUAL(map.size(), nb_values); - - serializer serial; - map.serialize(serial); - - deserializer dserial(serial.str()); - auto map_deserialized = - dice::sparse_map::sparse_map::deserialize( - dserial, false); - - BOOST_CHECK_EQUAL(map_deserialized.size(), map.size()); - for (const auto& val : map) { - BOOST_CHECK(map_deserialized.find(val.first) != map_deserialized.end()); - } -} - /** * KeyEqual */ diff --git a/tests/sparse_set_tests.cpp b/tests/sparse_set_tests.cpp index e78cb43..ca09b19 100644 --- a/tests/sparse_set_tests.cpp +++ b/tests/sparse_set_tests.cpp @@ -130,59 +130,4 @@ BOOST_AUTO_TEST_CASE(test_insert_pointer) { BOOST_CHECK_EQUAL(**set.begin(), value); } -/** - * serialize and deserialize - */ -BOOST_AUTO_TEST_CASE(test_serialize_deserialize_reserve) { - // insert x values values without intermediate resizes; serialize set; - // deserialize in new set; check equal. for deserialization, - // test it with and without hash compatibility. - for (std::size_t nb_values : {0, 1, 3, 17, 1000}) { - dice::sparse_map::sparse_set set; - set.reserve(nb_values); - for (std::size_t i = 0; i < nb_values; i++) { - set.insert(utils::get_key(i)); - } - - serializer serial; - set.serialize(serial); - - deserializer dserial(serial.str()); - auto set_deserialized = decltype(set)::deserialize(dserial, true); - BOOST_CHECK(set == set_deserialized); - - deserializer dserial2(serial.str()); - set_deserialized = decltype(set)::deserialize(dserial2, false); - BOOST_CHECK(set_deserialized == set); - } -} - -BOOST_AUTO_TEST_CASE(test_serialize_deserialize) { - // insert x values; delete some values; serialize set; deserialize in new - // set; check equal. for deserialization, test it with and without hash - // compatibility. - for (std::size_t nb_values : {0, 1, 3, 17, 1000}) { - dice::sparse_map::sparse_set set; - for (std::size_t i = 0; i < nb_values + 40; i++) { - set.insert(utils::get_key(i)); - } - - for (std::size_t i = nb_values; i < nb_values + 40; i++) { - set.erase(utils::get_key(i)); - } - BOOST_CHECK_EQUAL(set.size(), nb_values); - - serializer serial; - set.serialize(serial); - - deserializer dserial(serial.str()); - auto set_deserialized = decltype(set)::deserialize(dserial, true); - BOOST_CHECK(set == set_deserialized); - - deserializer dserial2(serial.str()); - set_deserialized = decltype(set)::deserialize(dserial2, false); - BOOST_CHECK(set_deserialized == set); - } -} - BOOST_AUTO_TEST_SUITE_END() From 703d4a57822db658c996dc277c1ed5eea14f57ad Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Wed, 2 Aug 2023 11:30:50 +0200 Subject: [PATCH 06/41] seperate sparse hash file --- include/dice/sparse-map/sparse_array.hpp | 581 ++++ .../dice/sparse-map/sparse_growth_policy.hpp | 4 +- include/dice/sparse-map/sparse_hash.hpp | 2393 ++++++----------- include/dice/sparse-map/sparse_map.hpp | 29 +- include/dice/sparse-map/sparse_props.hpp | 32 + include/dice/sparse-map/sparse_set.hpp | 30 +- tests/fancy_pointer/sparse_array_tests.cpp | 8 +- tests/fancy_pointer/sparse_hash_map_tests.cpp | 10 +- tests/fancy_pointer/sparse_hash_set_tests.cpp | 10 +- tests/policy_tests.cpp | 9 +- .../sparse_array_tests.cpp | 8 +- .../sparse_hash_set_tests.cpp | 10 +- tests/sparse_map_tests.cpp | 74 +- tests/sparse_set_tests.cpp | 14 +- 14 files changed, 1571 insertions(+), 1641 deletions(-) create mode 100644 include/dice/sparse-map/sparse_array.hpp create mode 100644 include/dice/sparse-map/sparse_props.hpp diff --git a/include/dice/sparse-map/sparse_array.hpp b/include/dice/sparse-map/sparse_array.hpp new file mode 100644 index 0000000..ddf838f --- /dev/null +++ b/include/dice/sparse-map/sparse_array.hpp @@ -0,0 +1,581 @@ +#ifndef DICE_SPARSE_MAP_SPARSE_ARRAY_HPP +#define DICE_SPARSE_MAP_SPARSE_ARRAY_HPP + +#include "dice/sparse-map/sparse_props.hpp" + +namespace dice::sparse_map::detail { + + template + constexpr U round_up_to_power_of_2(U value) { + assert(value > 0); + auto const highest_bit_pos = sizeof(U) * 8 - std::countl_zero(value - 1); + return U{1} << highest_bit_pos; + } + + /** + * WARNING: the sparse_array class doesn't free the ressources allocated through + * the allocator passed in parameter in each method. You have to manually call + * `clear(Allocator&)` when you don't need a sparse_array object anymore. + * + * The reason is that the sparse_array doesn't store the allocator to avoid + * wasting space in each sparse_array when the allocator has a size > 0. It only + * allocates/deallocates objects with the allocator that is passed in parameter. + * + * + * + * Index denotes a value between [0, BITMAP_NB_BITS), it is an index similar to + * std::vector. Offset denotes the real position in `m_values` corresponding to + * an index. + * + * We are using raw pointers instead of std::vector to avoid loosing + * 2*sizeof(size_t) bytes to store the capacity and size of the vector in each + * sparse_array. We know we can only store up to BITMAP_NB_BITS elements in the + * array, we don't need such big types. + * + * + * T must be nothrow move constructible and/or copy constructible. + * Behaviour is undefined if the destructor of T throws an exception. + * + * See https://smerity.com/articles/2015/google_sparsehash.html for details on + * the idea behinds the implementation. + * + * TODO Check to use std::realloc and std::memmove when possible + */ + template + struct sparse_array { + using value_type = T; + using size_type = std::uint_least8_t; + using allocator_type = Allocator; + using allocator_traits = std::allocator_traits; + using pointer = typename allocator_traits::pointer; + using const_pointer = typename allocator_traits::const_pointer; + using iterator = pointer; + using const_iterator = const_pointer; + + private: + using alloc_traits = std::allocator_traits; + + static constexpr size_type CAPACITY_GROWTH_STEP = []() { + switch (Sparsity) { + case sparsity::high: return 2; + case sparsity::medium: return 4; + case sparsity::low: return 8; + } + }(); + + using bitmap_type = std::uint_least64_t; + static constexpr std::size_t BITMAP_NB_BITS = 64; + static constexpr std::size_t BUCKET_SHIFT = 6; + + static constexpr std::size_t BUCKET_MASK = BITMAP_NB_BITS - 1; + + static_assert(std::popcount(BITMAP_NB_BITS) == 1, + "BITMAP_NB_BITS must be a power of two."); + static_assert(std::numeric_limits::digits >= BITMAP_NB_BITS, + "bitmap_type must be able to hold at least BITMAP_NB_BITS."); + static_assert((std::size_t(1) << BUCKET_SHIFT) == BITMAP_NB_BITS, + "(1 << BUCKET_SHIFT) must be equal to BITMAP_NB_BITS."); + static_assert(std::numeric_limits::max() >= BITMAP_NB_BITS, + "size_type must be big enough to hold BITMAP_NB_BITS."); + static_assert(std::is_unsigned::value, + "bitmap_type must be unsigned."); + static_assert((std::numeric_limits::max() & BUCKET_MASK) == BITMAP_NB_BITS - 1); + + private: + pointer m_values = nullptr; + + bitmap_type m_bitmap_vals = 0; + bitmap_type m_bitmap_deleted_vals = 0; + + size_type m_nb_elements = 0; + size_type m_capacity = 0; + bool m_last_array = false; + + public: + /** + * Map an ibucket [0, bucket_count) in the hash table to a sparse_ibucket + * (a sparse_array holds multiple buckets, so there is less sparse_array than + * bucket_count). + * + * The bucket ibucket is in + * m_sparse_buckets[sparse_ibucket(ibucket)][index_in_sparse_bucket(ibucket)] + * instead of something like m_buckets[ibucket] in a classical hash table. + */ + static constexpr std::size_t sparse_ibucket(std::size_t ibucket) noexcept { + return ibucket >> BUCKET_SHIFT; + } + + /** + * Map an ibucket [0, bucket_count) in the hash table to an index in the + * sparse_array which corresponds to the bucket. + * + * The bucket ibucket is in + * m_sparse_buckets[sparse_ibucket(ibucket)][index_in_sparse_bucket(ibucket)] + * instead of something like m_buckets[ibucket] in a classical hash table. + */ + static constexpr size_type index_in_sparse_bucket(std::size_t ibucket) noexcept { + return static_cast(ibucket & BUCKET_MASK); + } + + static constexpr std::size_t nb_sparse_buckets(std::size_t bucket_count) noexcept { + if (bucket_count == 0) { + return 0; + } + + return std::max(1, sparse_ibucket(round_up_to_power_of_2(bucket_count))); + } + + public: + constexpr sparse_array() noexcept = default; + + //needed for "is_constructible" with no parameters + constexpr sparse_array(std::allocator_arg_t, [[maybe_unused]] allocator_type const &alloc) noexcept { + } + + /*explicit sparse_array(bool last_bucket) noexcept + : m_values(nullptr), + m_bitmap_vals(0), + m_bitmap_deleted_vals(0), + m_nb_elements(0), + m_capacity(0), + m_last_array(last_bucket) {}*/ + + sparse_array(size_type capacity, allocator_type const &calloc) : m_capacity{capacity} { + if (m_capacity == 0) { + return; + } + + auto alloc = calloc; + m_values = alloc_traits::allocate(alloc, m_capacity); + assert(m_values != nullptr);// allocate should throw if there is a failure + } + + sparse_array(sparse_array const &other) = delete; + + constexpr sparse_array(sparse_array &&other) noexcept : m_values{std::exchange(other.m_values, nullptr)}, + m_bitmap_vals{std::exchange(other.m_bitmap_vals, 0)}, + m_bitmap_deleted_vals{std::exchange(other.m_bitmap_deleted_vals, 0)}, + m_nb_elements{std::exchange(other.m_nb_elements, 0)}, + m_capacity{std::exchange(other.m_capacity, 0)}, + m_last_array{other.m_last_array} { + } + + sparse_array(sparse_array const &other, allocator_type const &calloc) : m_values{nullptr}, + m_bitmap_vals{other.m_bitmap_vals}, + m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, + m_nb_elements{0}, + m_capacity(other.m_capacity), + m_last_array(other.m_last_array) { + auto alloc = calloc; + + assert(other.m_capacity >= other.m_nb_elements); + if (m_capacity == 0) { + return; + } + + m_values = alloc_traits::allocate(alloc, m_capacity); + assert(m_values != nullptr);// allocate should throw if there is a failure + + try { + for (; m_nb_elements < other.m_nb_elements; ++m_nb_elements) { + new (&m_values[m_nb_elements]) value_type{other.m_values[m_nb_elements]}; + } + } catch (...) { + clear(alloc); + throw; + } + } + + sparse_array(sparse_array &&other, Allocator const &calloc) : m_values{nullptr}, + m_bitmap_vals{other.m_bitmap_vals}, + m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, + m_nb_elements{0}, + m_capacity{other.m_capacity}, + m_last_array{other.m_last_array} { + auto alloc = calloc; // the only reason the allocator above is not mutable is because of scoped allocators + + assert(other.m_capacity >= other.m_nb_elements); + if (m_capacity == 0) { + return; + } + + m_values = alloc_traits::allocate(alloc, m_capacity); + assert(m_values != nullptr);// allocate should throw if there is a failure + + try { + for (size_type i = 0; i < other.m_nb_elements; i++) { + new (&m_values[i]) value_type{std::move(other.m_values[i])}; + m_nb_elements++; + } + } catch (...) { + clear(alloc); + throw; + } + } + + sparse_array &operator=(sparse_array const &) = delete; + + constexpr sparse_array &operator=(sparse_array &&other) noexcept { + assert(this != &other); + + this->m_values = std::exchange(other.m_values, nullptr); + this->m_bitmap_vals = std::exchange(other.m_bitmap_vals, 0); + this->m_bitmap_deleted_vals = std::exchange(other.m_bitmap_deleted_vals, 0); + this->m_nb_elements = std::exchange(other.m_nb_elements, 0); + this->m_capacity = std::exchange(other.m_capacity, 0); + + return *this; + } + + + // The code that manages the sparse_array must have called clear before + // destruction. See documentation of sparse_array for more details. + ~sparse_array() noexcept = default; + + /** + * @safety This function is only safe to call if the underlying object is non-const + */ + static iterator unsafe_mutable_iterator(const_iterator pos) noexcept { + if constexpr (std::is_pointer_v) { + return const_cast(pos); + } else { + return iterator{const_cast(std::to_address(pos))}; + } + } + + [[nodiscard]] constexpr iterator begin() noexcept { return m_values; } + [[nodiscard]] constexpr iterator end() noexcept { return m_values + m_nb_elements; } + [[nodiscard]] constexpr const_iterator begin() const noexcept { return cbegin(); } + [[nodiscard]] constexpr const_iterator end() const noexcept { return cend(); } + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return m_values; } + [[nodiscard]] constexpr const_iterator cend() const noexcept { return m_values + m_nb_elements; } + + [[nodiscard]] constexpr bool empty() const noexcept { return m_nb_elements == 0; } + + [[nodiscard]] constexpr size_type size() const noexcept { return m_nb_elements; } + + void clear(allocator_type &alloc) noexcept { + destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); + + m_values = nullptr; + m_bitmap_vals = 0; + m_bitmap_deleted_vals = 0; + m_nb_elements = 0; + m_capacity = 0; + } + + [[nodiscard]] constexpr bool last() const noexcept { return m_last_array; } + + constexpr void set_as_last() noexcept { m_last_array = true; } + + [[nodiscard]] constexpr bool has_value(size_type index) const noexcept { + assert(index < BITMAP_NB_BITS); + return (m_bitmap_vals & (bitmap_type(1) << index)) != 0; + } + + [[nodiscard]] constexpr bool has_deleted_value(size_type index) const noexcept { + assert(index < BITMAP_NB_BITS); + return (m_bitmap_deleted_vals & (bitmap_type(1) << index)) != 0; + } + + iterator value(size_type index) noexcept { + assert(has_value(index)); + return m_values + index_to_offset(index); + } + + const_iterator value(size_type index) const noexcept { + assert(has_value(index)); + return m_values + index_to_offset(index); + } + + /** + * Return iterator to set value. + */ + template + iterator set(allocator_type &alloc, size_type index, Args &&...value_args) { + assert(!has_value(index)); + + const size_type offset = index_to_offset(index); + insert_at_offset(alloc, offset, std::forward(value_args)...); + + m_bitmap_vals = (m_bitmap_vals | (bitmap_type(1) << index)); + m_bitmap_deleted_vals = (m_bitmap_deleted_vals & ~(bitmap_type(1) << index)); + + m_nb_elements++; + + assert(has_value(index)); + assert(!has_deleted_value(index)); + + return m_values + offset; + } + + iterator erase(allocator_type &alloc, iterator position) { + auto const offset = static_cast(std::distance(begin(), position)); + return erase(alloc, position, offset_to_index(offset)); + } + + // Return the next value or end if no next value + iterator erase(allocator_type &alloc, iterator position, size_type index) { + assert(has_value(index)); + assert(!has_deleted_value(index)); + + auto const offset = static_cast(std::distance(begin(), position)); + erase_at_offset(alloc, offset); + + m_bitmap_vals = (m_bitmap_vals & ~(bitmap_type(1) << index)); + m_bitmap_deleted_vals = (m_bitmap_deleted_vals | (bitmap_type(1) << index)); + + m_nb_elements--; + + assert(!has_value(index)); + assert(has_deleted_value(index)); + + return m_values + offset; + } + + void swap(sparse_array &other) { + using std::swap; + + swap(m_values, other.m_values); + swap(m_bitmap_vals, other.m_bitmap_vals); + swap(m_bitmap_deleted_vals, other.m_bitmap_deleted_vals); + swap(m_nb_elements, other.m_nb_elements); + swap(m_capacity, other.m_capacity); + swap(m_last_array, other.m_last_array); + } + + private: + static void destroy_and_deallocate_values(allocator_type &alloc, + pointer values, + size_type nb_values, + size_type capacity_values) noexcept { + for (size_type i = 0; i < nb_values; i++) { + values[i].~value_type(); + } + + alloc_traits::deallocate(alloc, values, capacity_values); + } + + [[nodiscard]] static constexpr size_type popcount(bitmap_type val) noexcept { + return std::popcount(val); + } + + [[nodiscard]] constexpr size_type index_to_offset(size_type index) const noexcept { + assert(index < BITMAP_NB_BITS); + return popcount(m_bitmap_vals & ((bitmap_type(1) << index) - bitmap_type(1))); + } + + // TODO optimize + [[nodiscard]] constexpr size_type offset_to_index(size_type offset) const noexcept { + assert(offset < m_nb_elements); + + bitmap_type bitmap_vals = m_bitmap_vals; + size_type index = 0; + size_type nb_ones = 0; + + while (bitmap_vals != 0) { + if ((bitmap_vals & 0x1) == 1) { + if (nb_ones == offset) { + break; + } + + nb_ones++; + } + + index++; + bitmap_vals = bitmap_vals >> 1; + } + + return index; + } + + [[nodiscard]] constexpr size_type next_capacity() const noexcept { + return static_cast(m_capacity + CAPACITY_GROWTH_STEP); + } + + /** + * Insertion + * + * Two situations: + * - Either we are in a situation where + * std::is_nothrow_move_constructible::value is true. In this + * case, on insertion we just reallocate m_values when we reach its capacity + * (i.e. m_nb_elements == m_capacity), otherwise we just put the new value at + * its appropriate place. We can easily keep the strong exception guarantee as + * moving the values around is safe. + * - Otherwise we are in a situation where + * std::is_nothrow_move_constructible::value is false. In this + * case on EACH insertion we allocate a new area of m_nb_elements + 1 where we + * copy the values of m_values into it and put the new value there. On + * success, we set m_values to this new area. Even if slower, it's the only + * way to preserve to strong exception guarantee. + */ + template requires (std::is_nothrow_move_constructible_v) + void insert_at_offset(allocator_type &alloc, size_type offset, Args &&...value_args) { + if (m_nb_elements < m_capacity) { + insert_at_offset_no_realloc(offset, std::forward(value_args)...); + } else { + insert_at_offset_realloc(alloc, offset, next_capacity(), std::forward(value_args)...); + } + } + + template requires (!std::is_nothrow_move_constructible_v) + void insert_at_offset(allocator_type &alloc, size_type offset, Args &&...value_args) { + insert_at_offset_realloc(alloc, offset, m_nb_elements + 1, std::forward(value_args)...); + } + + template requires (std::is_nothrow_move_constructible_v) + void insert_at_offset_no_realloc(size_type offset, Args &&...value_args) { + assert(offset <= m_nb_elements); + assert(m_nb_elements < m_capacity); + + for (size_type i = m_nb_elements; i > offset; i--) { + new (&m_values[i]) value_type{std::move(m_values[i - 1])}; + m_values[i - 1].~value_type(); + } + + try { + new (&m_values[offset]) value_type{std::forward(value_args)...}; + } catch (...) { + // revert + for (size_type i = offset; i < m_nb_elements; i++) { + new (&m_values[i]) value_type{std::move(m_values[i + 1])}; + m_values[i + 1].~value_type(); + } + throw; + } + } + + template requires (std::is_nothrow_move_constructible_v) + void insert_at_offset_realloc(allocator_type &alloc, size_type offset, + size_type new_capacity, Args &&...value_args) { + assert(new_capacity > m_nb_elements); + + pointer new_values = alloc_traits::allocate(alloc, new_capacity); + assert(new_values != nullptr); // Allocate should throw if there is a failure + + try { + new (&new_values[offset]) value_type{std::forward(value_args)...}; + } catch (...) { + alloc_traits::deallocate(alloc, new_values, new_capacity); + throw; + } + + // Should not throw from here + for (size_type i = 0; i < offset; i++) { + new (&new_values[i]) value_type{std::move(m_values[i])}; + } + + for (size_type i = offset; i < m_nb_elements; i++) { + new (&new_values[i + 1]) value_type{std::move(m_values[i])}; + } + + destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); + + m_values = new_values; + m_capacity = new_capacity; + } + + template requires (!std::is_nothrow_move_constructible_v) + void insert_at_offset_realloc(allocator_type &alloc, size_type offset, size_type new_capacity, Args &&...value_args) { + assert(new_capacity > m_nb_elements); + + pointer new_values = alloc_traits::allocate(alloc, new_capacity); + assert(new_values != nullptr); // Allocate should throw if there is a failure + + size_type nb_new_values = 0; + try { + for (size_type i = 0; i < offset; i++) { + new (&new_values[i]) value_type{m_values[i]}; + nb_new_values++; + } + + new (&new_values[offset]) value_type{std::forward(value_args)...}; + nb_new_values++; + + for (size_type i = offset; i < m_nb_elements; i++) { + new (&new_values[i + 1]) value_type{m_values[i]}; + nb_new_values++; + } + } catch (...) { + destroy_and_deallocate_values(alloc, new_values, nb_new_values, new_capacity); + throw; + } + + assert(nb_new_values == m_nb_elements + 1); + + destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); + + m_values = new_values; + m_capacity = new_capacity; + } + + /** + * Erasure + * + * Two situations: + * - Either we are in a situation where + * std::is_nothrow_move_constructible::value is true. Simply + * destroy the value and left-shift move the value on the right of offset. + * - Otherwise we are in a situation where + * std::is_nothrow_move_constructible::value is false. Copy all + * the values except the one at offset into a new heap area. On success, we + * set m_values to this new area. Even if slower, it's the only way to + * preserve to strong exception guarantee. + */ + template requires (std::is_nothrow_move_constructible_v) + void erase_at_offset([[maybe_unused]] allocator_type &alloc, size_type offset) noexcept { + assert(offset < m_nb_elements); + + m_values[offset].~value_type(); + + for (size_type i = offset + 1; i < m_nb_elements; ++i) { + new (&m_values[i - 1]) value_type{std::move(m_values[i])}; + m_values[i].~value_type(); + } + } + + template requires (!std::is_nothrow_move_constructible_v) + void erase_at_offset(allocator_type &alloc, size_type offset) { + assert(offset < m_nb_elements); + + if (offset + 1 == m_nb_elements) { + // Erasing the last element, don't need to reallocate. We keep the capacity. + m_values[offset].~value_type(); + return; + } + + assert(m_nb_elements > 1); + auto const new_capacity = m_nb_elements - 1; + + pointer new_values = alloc_traits::allocate(alloc, new_capacity); + assert(new_values != nullptr); // Allocate should throw if there is a failure + + size_type nb_new_values = 0; + try { + for (size_type i = 0; i < offset; ++i) { + new (&new_values[i]) value_type{m_values[i]}; + nb_new_values++; + } + + for (size_type i = offset + 1; i < m_nb_elements; ++i) { + new (&new_values[i - 1]) value_type{m_values[i]}; + nb_new_values++; + } + } catch (...) { + destroy_and_deallocate_values(alloc, new_values, nb_new_values, new_capacity); + throw; + } + + assert(nb_new_values == m_nb_elements - 1); + + destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); + + m_values = new_values; + m_capacity = new_capacity; + } + }; + +} // namespace dice::sparse_map::detail + +#endif//DICE_SPARSE_MAP_SPARSE_ARRAY_HPP diff --git a/include/dice/sparse-map/sparse_growth_policy.hpp b/include/dice/sparse-map/sparse_growth_policy.hpp index 3040932..43a1658 100644 --- a/include/dice/sparse-map/sparse_growth_policy.hpp +++ b/include/dice/sparse-map/sparse_growth_policy.hpp @@ -35,7 +35,7 @@ #include #include -namespace dice::sparse_map::sh { +namespace dice::sparse_map { template concept growth_policy = requires (G const cgpol, G gpol, std::size_t &min_bucket_count_in_out, std::size_t hash) { @@ -310,6 +310,6 @@ namespace dice::sparse_map::sh { unsigned int m_iprime; }; -}// namespace dice::sparse_map::sh +}// namespace dice::sparse_map #endif diff --git a/include/dice/sparse-map/sparse_hash.hpp b/include/dice/sparse-map/sparse_hash.hpp index 9058753..4cfc7fb 100644 --- a/include/dice/sparse-map/sparse_hash.hpp +++ b/include/dice/sparse-map/sparse_hash.hpp @@ -40,1748 +40,1083 @@ #include #include -#include "boost/container/vector.hpp" -#include "dice/sparse-map/sparse_growth_policy.hpp" - -#ifdef TSL_DEBUG -#define tsl_sh_assert(expr) assert(expr) -#else -#define tsl_sh_assert(expr) (static_cast(0)) -#endif - -namespace dice::sparse_map { - - namespace sh { - enum class probing { - linear, - quadratic - }; - - enum class exception_safety { - basic, - strong - }; +#include - enum class sparsity { - high, - medium, - low - }; - }// namespace sh - - namespace detail_sparse_hash { - template - struct make_void { +#include "dice/sparse-map/sparse_growth_policy.hpp" +#include "dice/sparse-map/sparse_array.hpp" + +namespace dice::sparse_map::detail { + template + struct is_power_of_two_policy : std::false_type { + }; + + template + struct is_power_of_two_policy> : std::true_type { + }; + + /** + * Internal common class used by `sparse_map` and `sparse_set`. + * + * `ValueType` is what will be stored by `sparse_hash` (usually `std::pair` for map and `Key` for set). + * + * `KeySelect` should be a `FunctionObject` which takes a `ValueType` in + * parameter and returns a reference to the key. + * + * `ValueSelect` should be a `FunctionObject` which takes a `ValueType` in + * parameter and returns a reference to the value. `ValueSelect` should be void + * if there is no value (in a set for example). + * + * The strong exception guarantee only holds if `ExceptionSafety` is set to + * `dice::sh::exception_safety::strong`. + * + * `ValueType` must be nothrow move constructible and/or copy constructible. + * Behaviour is undefined if the destructor of `ValueType` throws. + * + * + * The class holds its buckets in a 2-dimensional fashion. Instead of having a + * linear `std::vector` for [0, bucket_count) where each bucket stores + * one value, we have a `std::vector` (m_sparse_buckets_data) + * where each `sparse_array` stores multiple values (up to + * `sparse_array::BITMAP_NB_BITS`). To convert a one dimensional `ibucket` + * position to a position in `std::vector` and a position in + * `sparse_array`, use respectively the methods + * `sparse_array::sparse_ibucket(ibucket)` and + * `sparse_array::index_in_sparse_bucket(ibucket)`. + */ + template + class sparse_hash { + private: + template + struct GetMappedType { using type = void; + using const_reference = void; + using reference = void; }; - template - struct is_power_of_two_policy : std::false_type {}; - - template - struct is_power_of_two_policy> - : std::true_type {}; - - inline constexpr bool is_power_of_two(std::size_t value) { - return value != 0 && (value & (value - 1)) == 0; - } - - inline std::size_t round_up_to_power_of_two(std::size_t value) { - if (is_power_of_two(value)) { - return value; - } - - if (value == 0) { - return 1; - } - - --value; - for (std::size_t i = 1; i < sizeof(std::size_t) * CHAR_BIT; i *= 2) { - value |= value >> i; - } - - return value + 1; - } - - template - static T numeric_cast(U value, - const char *error_message = "numeric_cast() failed.") { - T ret = static_cast(value); - if (static_cast(ret) != value) { - throw std::runtime_error(error_message); - } - - const bool is_same_signedness = - (std::is_unsigned::value && std::is_unsigned::value) || - (std::is_signed::value && std::is_signed::value); - if (!is_same_signedness && (ret < T{}) != (value < U{})) { - throw std::runtime_error(error_message); - } - - return ret; - } - - /** - * Fixed size type used to represent size_type values on serialization. Need to - * be big enough to represent a std::size_t on 32 and 64 bits platforms, and - * must be the same size on both platforms. - */ - using slz_size_type = std::uint64_t; - static_assert(std::numeric_limits::max() >= - std::numeric_limits::max(), - "slz_size_type must be >= std::size_t"); - - template - static T deserialize_value(Deserializer &deserializer) { - // MSVC < 2017 is not conformant, circumvent the problem by removing the - // template keyword -#if defined(_MSC_VER) && _MSC_VER < 1910 - return deserializer.Deserializer::operator()(); -#else - return deserializer.Deserializer::template operator()(); -#endif - } - - /** - * WARNING: the sparse_array class doesn't free the ressources allocated through - * the allocator passed in parameter in each method. You have to manually call - * `clear(Allocator&)` when you don't need a sparse_array object anymore. - * - * The reason is that the sparse_array doesn't store the allocator to avoid - * wasting space in each sparse_array when the allocator has a size > 0. It only - * allocates/deallocates objects with the allocator that is passed in parameter. - * - * - * - * Index denotes a value between [0, BITMAP_NB_BITS), it is an index similar to - * std::vector. Offset denotes the real position in `m_values` corresponding to - * an index. - * - * We are using raw pointers instead of std::vector to avoid loosing - * 2*sizeof(size_t) bytes to store the capacity and size of the vector in each - * sparse_array. We know we can only store up to BITMAP_NB_BITS elements in the - * array, we don't need such big types. - * - * - * T must be nothrow move constructible and/or copy constructible. - * Behaviour is undefined if the destructor of T throws an exception. - * - * See https://smerity.com/articles/2015/google_sparsehash.html for details on - * the idea behinds the implementation. - * - * TODO Check to use std::realloc and std::memmove when possible - */ - template - struct sparse_array { - using value_type = T; - using size_type = std::uint_least8_t; - using allocator_type = Allocator; - using allocator_traits = std::allocator_traits; - using pointer = typename allocator_traits::pointer; - using const_pointer = typename allocator_traits::const_pointer; - using iterator = pointer; - using const_iterator = const_pointer; + template requires requires { typename VSel::value_type; } + struct GetMappedType { + using type = typename VSel::value_type; + using const_reference = const type &; + using reference = type &; + }; - private: - using alloc_traits = std::allocator_traits; - - static constexpr size_type CAPACITY_GROWTH_STEP = []() { - switch (Sparsity) { - case dice::sparse_map::sh::sparsity::high: return 2; - case dice::sparse_map::sh::sparsity::medium: return 4; - case dice::sparse_map::sh::sparsity::low: return 8; - } - }(); - - using bitmap_type = std::uint_least64_t; - static constexpr std::size_t BITMAP_NB_BITS = 64; - static constexpr std::size_t BUCKET_SHIFT = 6; - - static constexpr std::size_t BUCKET_MASK = BITMAP_NB_BITS - 1; - - static_assert(is_power_of_two(BITMAP_NB_BITS), - "BITMAP_NB_BITS must be a power of two."); - static_assert(std::numeric_limits::digits >= BITMAP_NB_BITS, - "bitmap_type must be able to hold at least BITMAP_NB_BITS."); - static_assert((std::size_t(1) << BUCKET_SHIFT) == BITMAP_NB_BITS, - "(1 << BUCKET_SHIFT) must be equal to BITMAP_NB_BITS."); - static_assert(std::numeric_limits::max() >= BITMAP_NB_BITS, - "size_type must be big enough to hold BITMAP_NB_BITS."); - static_assert(std::is_unsigned::value, - "bitmap_type must be unsigned."); - static_assert((std::numeric_limits::max() & BUCKET_MASK) == - BITMAP_NB_BITS - 1); + public: + template + class sparse_iterator; + + using key_type = typename KeyValueSelect::key_type; + using mapped_type = typename GetMappedType::type; + using mapped_const_reference = typename GetMappedType::const_reference; + using mapped_reference = typename GetMappedType::reference; + using value_type = ValueType; + using hasher = Hash; + using key_equal = KeyEqual; + using allocator_type = Allocator; + using growth_policy = GrowthPolicy; + using reference = value_type &; + using const_reference = const value_type &; + using size_type = typename std::allocator_traits::size_type; + using pointer = typename std::allocator_traits::pointer; + using const_pointer = typename std::allocator_traits::const_pointer; + using difference_type = typename std::allocator_traits::difference_type; + using iterator = sparse_iterator; + using const_iterator = sparse_iterator; + + private: + static constexpr bool has_mapped_type = !std::is_same_v; + + using sparse_array = detail::sparse_array; + + using sparse_buckets_allocator = typename std::allocator_traits::template rebind_alloc; + using sparse_buckets_container = boost::container::vector; + + public: + template + class sparse_iterator { + friend class sparse_hash; private: - pointer m_values = nullptr; - - bitmap_type m_bitmap_vals = 0; - bitmap_type m_bitmap_deleted_vals = 0; - - size_type m_nb_elements = 0; - size_type m_capacity = 0; - bool m_last_array = false; + using sparse_bucket_iterator = std::conditional_t; - public: - /** - * Map an ibucket [0, bucket_count) in the hash table to a sparse_ibucket - * (a sparse_array holds multiple buckets, so there is less sparse_array than - * bucket_count). - * - * The bucket ibucket is in - * m_sparse_buckets[sparse_ibucket(ibucket)][index_in_sparse_bucket(ibucket)] - * instead of something like m_buckets[ibucket] in a classical hash table. - */ - static constexpr std::size_t sparse_ibucket(std::size_t ibucket) noexcept { - return ibucket >> BUCKET_SHIFT; - } + using sparse_array_iterator = std::conditional_t; /** - * Map an ibucket [0, bucket_count) in the hash table to an index in the - * sparse_array which corresponds to the bucket. - * - * The bucket ibucket is in - * m_sparse_buckets[sparse_ibucket(ibucket)][index_in_sparse_bucket(ibucket)] - * instead of something like m_buckets[ibucket] in a classical hash table. - */ - static constexpr size_type index_in_sparse_bucket(std::size_t ibucket) noexcept { - return static_cast(ibucket & BUCKET_MASK); - } - - static constexpr std::size_t nb_sparse_buckets(std::size_t bucket_count) noexcept { - if (bucket_count == 0) { - return 0; - } - - return std::max(1, sparse_ibucket(round_up_to_power_of_two(bucket_count))); - } + * sparse_array_it should be nullptr if sparse_bucket_it == + * m_sparse_buckets_data.end(). (TODO better way?) + */ + sparse_iterator(sparse_bucket_iterator sparse_bucket_it, + sparse_array_iterator sparse_array_it) + : m_sparse_buckets_it(sparse_bucket_it), + m_sparse_array_it(sparse_array_it) {} public: - constexpr sparse_array() noexcept = default; - - //needed for "is_constructible" with no parameters - constexpr sparse_array(std::allocator_arg_t, [[maybe_unused]] allocator_type const &alloc) noexcept { - } - - /*explicit sparse_array(bool last_bucket) noexcept - : m_values(nullptr), - m_bitmap_vals(0), - m_bitmap_deleted_vals(0), - m_nb_elements(0), - m_capacity(0), - m_last_array(last_bucket) {}*/ + using iterator_category = std::forward_iterator_tag; + using value_type = const typename sparse_hash::value_type; + using difference_type = std::ptrdiff_t; + using reference = std::conditional_t; - sparse_array(size_type capacity, allocator_type const &calloc) : m_capacity{capacity} { - if (m_capacity == 0) { - return; - } + using pointer = std::conditional_t::template rebind_traits::const_pointer, + typename std::allocator_traits::template rebind_traits::pointer>; - auto alloc = calloc; - m_values = alloc_traits::allocate(alloc, m_capacity); - tsl_sh_assert(m_values != nullptr);// allocate should throw if there is a failure + // Copy constructor from iterator to const_iterator. + sparse_iterator(sparse_iterator const &other) noexcept requires (IsConst) : m_sparse_buckets_it(other.m_sparse_buckets_it), + m_sparse_array_it(other.m_sparse_array_it) { } - sparse_array(sparse_array const &other) = delete; + sparse_iterator(const sparse_iterator &other) = default; + sparse_iterator(sparse_iterator &&other) = default; + sparse_iterator &operator=(const sparse_iterator &other) = default; + sparse_iterator &operator=(sparse_iterator &&other) = default; - constexpr sparse_array(sparse_array &&other) noexcept : m_values{std::exchange(other.m_values, nullptr)}, - m_bitmap_vals{std::exchange(other.m_bitmap_vals, 0)}, - m_bitmap_deleted_vals{std::exchange(other.m_bitmap_deleted_vals, 0)}, - m_nb_elements{std::exchange(other.m_nb_elements, 0)}, - m_capacity{std::exchange(other.m_capacity, 0)}, - m_last_array{other.m_last_array} { - } - - sparse_array(sparse_array const &other, allocator_type const &calloc) : m_values{nullptr}, - m_bitmap_vals{other.m_bitmap_vals}, - m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, - m_nb_elements{0}, - m_capacity(other.m_capacity), - m_last_array(other.m_last_array) { - auto alloc = calloc; + reference operator*() const { return KeyValueSelect::both(*m_sparse_array_it); } - tsl_sh_assert(other.m_capacity >= other.m_nb_elements); - if (m_capacity == 0) { - return; - } - - m_values = alloc_traits::allocate(alloc, m_capacity); - tsl_sh_assert(m_values != nullptr);// allocate should throw if there is a failure - - try { - for (; m_nb_elements < other.m_nb_elements; ++m_nb_elements) { - new (&m_values[m_nb_elements]) value_type{other.m_values[m_nb_elements]}; - } - } catch (...) { - clear(alloc); - throw; - } - } + //with fancy pointers addressof might be problematic. + pointer operator->() const { return &KeyValueSelect::both(*m_sparse_array_it); } - sparse_array(sparse_array &&other, Allocator const &calloc) : m_values{nullptr}, - m_bitmap_vals{other.m_bitmap_vals}, - m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, - m_nb_elements{0}, - m_capacity{other.m_capacity}, - m_last_array{other.m_last_array} { - auto alloc = calloc; // the only reason the allocator above is not mutable is because of scoped allocators + sparse_iterator &operator++() { + assert(m_sparse_array_it != nullptr); + ++m_sparse_array_it; - tsl_sh_assert(other.m_capacity >= other.m_nb_elements); - if (m_capacity == 0) { - return; - } + //vector iterator with fancy pointers have a problem with -> + if (m_sparse_array_it == (*m_sparse_buckets_it).end()) { + do { + if ((*m_sparse_buckets_it).last()) { + ++m_sparse_buckets_it; + m_sparse_array_it = nullptr; + return *this; + } - m_values = alloc_traits::allocate(alloc, m_capacity); - tsl_sh_assert(m_values != nullptr);// allocate should throw if there is a failure + ++m_sparse_buckets_it; + } while ((*m_sparse_buckets_it).empty()); - try { - for (size_type i = 0; i < other.m_nb_elements; i++) { - new (&m_values[i]) value_type{std::move(other.m_values[i])}; - m_nb_elements++; - } - } catch (...) { - clear(alloc); - throw; + m_sparse_array_it = (*m_sparse_buckets_it).begin(); } - } - - sparse_array &operator=(sparse_array const &) = delete; - - constexpr sparse_array &operator=(sparse_array &&other) noexcept { - tsl_sh_assert(this != &other); - - this->m_values = std::exchange(other.m_values, nullptr); - this->m_bitmap_vals = std::exchange(other.m_bitmap_vals, 0); - this->m_bitmap_deleted_vals = std::exchange(other.m_bitmap_deleted_vals, 0); - this->m_nb_elements = std::exchange(other.m_nb_elements, 0); - this->m_capacity = std::exchange(other.m_capacity, 0); return *this; } + sparse_iterator operator++(int) { + sparse_iterator tmp(*this); + ++*this; - // The code that manages the sparse_array must have called clear before - // destruction. See documentation of sparse_array for more details. - ~sparse_array() noexcept = default; - - /** - * @safety This function is only safe to call if the underlying object is non-const - */ - static iterator unsafe_mutable_iterator(const_iterator pos) noexcept { - if constexpr (std::is_pointer_v) { - return const_cast(pos); - } else { - return iterator{const_cast(std::to_address(pos))}; - } - } - - [[nodiscard]] constexpr iterator begin() noexcept { return m_values; } - [[nodiscard]] constexpr iterator end() noexcept { return m_values + m_nb_elements; } - [[nodiscard]] constexpr const_iterator begin() const noexcept { return cbegin(); } - [[nodiscard]] constexpr const_iterator end() const noexcept { return cend(); } - [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return m_values; } - [[nodiscard]] constexpr const_iterator cend() const noexcept { return m_values + m_nb_elements; } - - [[nodiscard]] constexpr bool empty() const noexcept { return m_nb_elements == 0; } - - [[nodiscard]] constexpr size_type size() const noexcept { return m_nb_elements; } - - void clear(allocator_type &alloc) noexcept { - destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); - - m_values = nullptr; - m_bitmap_vals = 0; - m_bitmap_deleted_vals = 0; - m_nb_elements = 0; - m_capacity = 0; - } - - [[nodiscard]] constexpr bool last() const noexcept { return m_last_array; } - - constexpr void set_as_last() noexcept { m_last_array = true; } - - [[nodiscard]] constexpr bool has_value(size_type index) const noexcept { - tsl_sh_assert(index < BITMAP_NB_BITS); - return (m_bitmap_vals & (bitmap_type(1) << index)) != 0; - } - - [[nodiscard]] constexpr bool has_deleted_value(size_type index) const noexcept { - tsl_sh_assert(index < BITMAP_NB_BITS); - return (m_bitmap_deleted_vals & (bitmap_type(1) << index)) != 0; - } - - iterator value(size_type index) noexcept { - tsl_sh_assert(has_value(index)); - return m_values + index_to_offset(index); - } - - const_iterator value(size_type index) const noexcept { - tsl_sh_assert(has_value(index)); - return m_values + index_to_offset(index); - } - - /** - * Return iterator to set value. - */ - template - iterator set(allocator_type &alloc, size_type index, Args &&...value_args) { - tsl_sh_assert(!has_value(index)); - - const size_type offset = index_to_offset(index); - insert_at_offset(alloc, offset, std::forward(value_args)...); - - m_bitmap_vals = (m_bitmap_vals | (bitmap_type(1) << index)); - m_bitmap_deleted_vals = (m_bitmap_deleted_vals & ~(bitmap_type(1) << index)); - - m_nb_elements++; - - tsl_sh_assert(has_value(index)); - tsl_sh_assert(!has_deleted_value(index)); - - return m_values + offset; - } - - iterator erase(allocator_type &alloc, iterator position) { - auto const offset = static_cast(std::distance(begin(), position)); - return erase(alloc, position, offset_to_index(offset)); + return tmp; } - // Return the next value or end if no next value - iterator erase(allocator_type &alloc, iterator position, size_type index) { - tsl_sh_assert(has_value(index)); - tsl_sh_assert(!has_deleted_value(index)); - - auto const offset = static_cast(std::distance(begin(), position)); - erase_at_offset(alloc, offset); - - m_bitmap_vals = (m_bitmap_vals & ~(bitmap_type(1) << index)); - m_bitmap_deleted_vals = (m_bitmap_deleted_vals | (bitmap_type(1) << index)); - - m_nb_elements--; - - tsl_sh_assert(!has_value(index)); - tsl_sh_assert(has_deleted_value(index)); - - return m_values + offset; + template + bool operator==(const sparse_iterator &other) const noexcept { + return m_sparse_buckets_it == other.m_sparse_buckets_it && m_sparse_array_it == other.m_sparse_array_it; } - void swap(sparse_array &other) { - using std::swap; - - swap(m_values, other.m_values); - swap(m_bitmap_vals, other.m_bitmap_vals); - swap(m_bitmap_deleted_vals, other.m_bitmap_deleted_vals); - swap(m_nb_elements, other.m_nb_elements); - swap(m_capacity, other.m_capacity); - swap(m_last_array, other.m_last_array); + template + bool operator!=(const sparse_iterator &other) const noexcept { + return m_sparse_buckets_it != other.m_sparse_buckets_it || m_sparse_array_it != other.m_sparse_array_it; } private: - static void destroy_and_deallocate_values(allocator_type &alloc, - pointer values, - size_type nb_values, - size_type capacity_values) noexcept { - for (size_type i = 0; i < nb_values; i++) { - values[i].~value_type(); - } - - alloc_traits::deallocate(alloc, values, capacity_values); - } - - [[nodiscard]] static constexpr size_type popcount(bitmap_type val) noexcept { - return std::popcount(val); - } - - [[nodiscard]] constexpr size_type index_to_offset(size_type index) const noexcept { - tsl_sh_assert(index < BITMAP_NB_BITS); - return popcount(m_bitmap_vals & ((bitmap_type(1) << index) - bitmap_type(1))); - } - - // TODO optimize - [[nodiscard]] constexpr size_type offset_to_index(size_type offset) const noexcept { - tsl_sh_assert(offset < m_nb_elements); - - bitmap_type bitmap_vals = m_bitmap_vals; - size_type index = 0; - size_type nb_ones = 0; - - while (bitmap_vals != 0) { - if ((bitmap_vals & 0x1) == 1) { - if (nb_ones == offset) { - break; - } - - nb_ones++; - } - - index++; - bitmap_vals = bitmap_vals >> 1; - } - - return index; - } - - [[nodiscard]] constexpr size_type next_capacity() const noexcept { - return static_cast(m_capacity + CAPACITY_GROWTH_STEP); - } - - /** - * Insertion - * - * Two situations: - * - Either we are in a situation where - * std::is_nothrow_move_constructible::value is true. In this - * case, on insertion we just reallocate m_values when we reach its capacity - * (i.e. m_nb_elements == m_capacity), otherwise we just put the new value at - * its appropriate place. We can easily keep the strong exception guarantee as - * moving the values around is safe. - * - Otherwise we are in a situation where - * std::is_nothrow_move_constructible::value is false. In this - * case on EACH insertion we allocate a new area of m_nb_elements + 1 where we - * copy the values of m_values into it and put the new value there. On - * success, we set m_values to this new area. Even if slower, it's the only - * way to preserve to strong exception guarantee. - */ - template requires (std::is_nothrow_move_constructible_v) - void insert_at_offset(allocator_type &alloc, size_type offset, Args &&...value_args) { - if (m_nb_elements < m_capacity) { - insert_at_offset_no_realloc(offset, std::forward(value_args)...); - } else { - insert_at_offset_realloc(alloc, offset, next_capacity(), std::forward(value_args)...); - } - } - - template requires (!std::is_nothrow_move_constructible_v) - void insert_at_offset(allocator_type &alloc, size_type offset, Args &&...value_args) { - insert_at_offset_realloc(alloc, offset, m_nb_elements + 1, std::forward(value_args)...); - } - - template requires (std::is_nothrow_move_constructible_v) - void insert_at_offset_no_realloc(size_type offset, Args &&...value_args) { - tsl_sh_assert(offset <= m_nb_elements); - tsl_sh_assert(m_nb_elements < m_capacity); - - for (size_type i = m_nb_elements; i > offset; i--) { - new (&m_values[i]) value_type{std::move(m_values[i - 1])}; - m_values[i - 1].~value_type(); - } - - try { - new (&m_values[offset]) value_type{std::forward(value_args)...}; - } catch (...) { - // revert - for (size_type i = offset; i < m_nb_elements; i++) { - new (&m_values[i]) value_type{std::move(m_values[i + 1])}; - m_values[i + 1].~value_type(); - } - throw; - } - } - - template requires (std::is_nothrow_move_constructible_v) - void insert_at_offset_realloc(allocator_type &alloc, size_type offset, - size_type new_capacity, Args &&...value_args) { - tsl_sh_assert(new_capacity > m_nb_elements); - - pointer new_values = alloc_traits::allocate(alloc, new_capacity); - tsl_sh_assert(new_values != nullptr); // Allocate should throw if there is a failure - - try { - new (&new_values[offset]) value_type{std::forward(value_args)...}; - } catch (...) { - alloc_traits::deallocate(alloc, new_values, new_capacity); - throw; - } - - // Should not throw from here - for (size_type i = 0; i < offset; i++) { - new (&new_values[i]) value_type{std::move(m_values[i])}; - } - - for (size_type i = offset; i < m_nb_elements; i++) { - new (&new_values[i + 1]) value_type{std::move(m_values[i])}; - } - - destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); - - m_values = new_values; - m_capacity = new_capacity; - } - - template requires (!std::is_nothrow_move_constructible_v) - void insert_at_offset_realloc(allocator_type &alloc, size_type offset, size_type new_capacity, Args &&...value_args) { - tsl_sh_assert(new_capacity > m_nb_elements); - - pointer new_values = alloc_traits::allocate(alloc, new_capacity); - tsl_sh_assert(new_values != nullptr); // Allocate should throw if there is a failure - - size_type nb_new_values = 0; - try { - for (size_type i = 0; i < offset; i++) { - new (&new_values[i]) value_type{m_values[i]}; - nb_new_values++; - } - - new (&new_values[offset]) value_type{std::forward(value_args)...}; - nb_new_values++; - - for (size_type i = offset; i < m_nb_elements; i++) { - new (&new_values[i + 1]) value_type{m_values[i]}; - nb_new_values++; - } - } catch (...) { - destroy_and_deallocate_values(alloc, new_values, nb_new_values, new_capacity); - throw; - } - - tsl_sh_assert(nb_new_values == m_nb_elements + 1); - - destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); - - m_values = new_values; - m_capacity = new_capacity; - } - - /** - * Erasure - * - * Two situations: - * - Either we are in a situation where - * std::is_nothrow_move_constructible::value is true. Simply - * destroy the value and left-shift move the value on the right of offset. - * - Otherwise we are in a situation where - * std::is_nothrow_move_constructible::value is false. Copy all - * the values except the one at offset into a new heap area. On success, we - * set m_values to this new area. Even if slower, it's the only way to - * preserve to strong exception guarantee. - */ - template requires (std::is_nothrow_move_constructible_v) - void erase_at_offset([[maybe_unused]] allocator_type &alloc, size_type offset) noexcept { - tsl_sh_assert(offset < m_nb_elements); - - m_values[offset].~value_type(); - - for (size_type i = offset + 1; i < m_nb_elements; ++i) { - new (&m_values[i - 1]) value_type{std::move(m_values[i])}; - m_values[i].~value_type(); - } - } - - template requires (!std::is_nothrow_move_constructible_v) - void erase_at_offset(allocator_type &alloc, size_type offset) { - tsl_sh_assert(offset < m_nb_elements); - - if (offset + 1 == m_nb_elements) { - // Erasing the last element, don't need to reallocate. We keep the capacity. - m_values[offset].~value_type(); - return; - } - - tsl_sh_assert(m_nb_elements > 1); - auto const new_capacity = m_nb_elements - 1; - - pointer new_values = alloc_traits::allocate(alloc, new_capacity); - tsl_sh_assert(new_values != nullptr); // Allocate should throw if there is a failure - - size_type nb_new_values = 0; - try { - for (size_type i = 0; i < offset; ++i) { - new (&new_values[i]) value_type{m_values[i]}; - nb_new_values++; - } - - for (size_type i = offset + 1; i < m_nb_elements; ++i) { - new (&new_values[i - 1]) value_type{m_values[i]}; - nb_new_values++; - } - } catch (...) { - destroy_and_deallocate_values(alloc, new_values, nb_new_values, new_capacity); - throw; - } - - tsl_sh_assert(nb_new_values == m_nb_elements - 1); - - destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); - - m_values = new_values; - m_capacity = new_capacity; - } + sparse_bucket_iterator m_sparse_buckets_it; + sparse_array_iterator m_sparse_array_it; }; - /** - * Internal common class used by `sparse_map` and `sparse_set`. - * - * `ValueType` is what will be stored by `sparse_hash` (usually `std::pair` for map and `Key` for set). - * - * `KeySelect` should be a `FunctionObject` which takes a `ValueType` in - * parameter and returns a reference to the key. - * - * `ValueSelect` should be a `FunctionObject` which takes a `ValueType` in - * parameter and returns a reference to the value. `ValueSelect` should be void - * if there is no value (in a set for example). - * - * The strong exception guarantee only holds if `ExceptionSafety` is set to - * `dice::sh::exception_safety::strong`. - * - * `ValueType` must be nothrow move constructible and/or copy constructible. - * Behaviour is undefined if the destructor of `ValueType` throws. - * - * - * The class holds its buckets in a 2-dimensional fashion. Instead of having a - * linear `std::vector` for [0, bucket_count) where each bucket stores - * one value, we have a `std::vector` (m_sparse_buckets_data) - * where each `sparse_array` stores multiple values (up to - * `sparse_array::BITMAP_NB_BITS`). To convert a one dimensional `ibucket` - * position to a position in `std::vector` and a position in - * `sparse_array`, use respectively the methods - * `sparse_array::sparse_ibucket(ibucket)` and - * `sparse_array::index_in_sparse_bucket(ibucket)`. - */ - template - class sparse_hash { - private: - template - struct GetMappedType { - using type = void; - using const_reference = void; - using reference = void; - }; - - template requires requires { typename VSel::value_type; } - struct GetMappedType { - using type = typename VSel::value_type; - using const_reference = const type &; - using reference = type &; - }; - - public: - template - class sparse_iterator; - - using key_type = typename KeyValueSelect::key_type; - using mapped_type = typename GetMappedType::type; - using mapped_const_reference = typename GetMappedType::const_reference; - using mapped_reference = typename GetMappedType::reference; - using value_type = ValueType; - using hasher = Hash; - using key_equal = KeyEqual; - using allocator_type = Allocator; - using growth_policy = GrowthPolicy; - using reference = value_type &; - using const_reference = const value_type &; - using size_type = typename std::allocator_traits::size_type; - using pointer = typename std::allocator_traits::pointer; - using const_pointer = typename std::allocator_traits::const_pointer; - using difference_type = typename std::allocator_traits::difference_type; - using iterator = sparse_iterator; - using const_iterator = sparse_iterator; - - private: - static constexpr bool has_mapped_type = !std::is_same_v; + iterator mutable_iterator(const_iterator pos) noexcept { + // SAFETY: this is non-const therefore the underlying buckets are also non-const + // as evidenced by the fact that we can call begin on them + auto it_sparse_buckets = m_sparse_buckets_data.begin() + std::distance(m_sparse_buckets_data.cbegin(), pos.m_sparse_buckets_it); - using sparse_array = - dice::sparse_map::detail_sparse_hash::sparse_array; + // SAFETY: this is non-const therefore the underlying sparse array is also non-const + auto it_array = sparse_array::unsafe_mutable_iterator(pos.m_sparse_array_it); - using sparse_buckets_allocator = typename std::allocator_traits< - allocator_type>::template rebind_alloc; - using sparse_buckets_container = - boost::container::vector; + return iterator(it_sparse_buckets, it_array); + } - public: - /** - * The `operator*()` and `operator->()` methods return a const reference and - * const pointer respectively to the stored value type (`Key` for a set, - * `std::pair` for a map). - * - * In case of a map, to get a mutable reference to the value `T` associated to - * a key (the `.second` in the stored pair), you have to call `value()`. - */ - template - class sparse_iterator { - friend class sparse_hash; - - private: - using sparse_bucket_iterator = std::conditional_t; - - using sparse_array_iterator = std::conditional_t; - - /** - * sparse_array_it should be nullptr if sparse_bucket_it == - * m_sparse_buckets_data.end(). (TODO better way?) + public: + sparse_hash(size_type bucket_count, const Hash &hash, const KeyEqual &equal, + const Allocator &alloc, float max_load_factor) + : m_sparse_buckets_data(alloc), + // m_sparse_buckets_data(std::allocator_traits::rebind_alloc(m_alloc)), + m_sparse_buckets(static_empty_sparse_bucket_ptr()), + m_bucket_count(bucket_count), + m_nb_elements(0), + m_nb_deleted_buckets(0), + m_alloc{alloc}, + m_h{hash}, + m_keq{equal}, + m_gpol{bucket_count} { + + if (m_bucket_count > max_bucket_count()) { + throw std::length_error("The map exceeds its maximum size."); + } + + if (m_bucket_count > 0) { + /* + * We can't use the `vector(size_type count, const Allocator& m_alloc)` + * constructor as it's only available in C++14 and we need to support + * C++11. We thus must resize after using the `vector(const Allocator& + * m_alloc)` constructor. + * + * We can't use `vector(size_type count, const T& value, const Allocator& + * m_alloc)` as it requires the value T to be copyable. */ - sparse_iterator(sparse_bucket_iterator sparse_bucket_it, - sparse_array_iterator sparse_array_it) - : m_sparse_buckets_it(sparse_bucket_it), - m_sparse_array_it(sparse_array_it) {} - - public: - using iterator_category = std::forward_iterator_tag; - using value_type = const typename sparse_hash::value_type; - using difference_type = std::ptrdiff_t; - using reference = std::conditional_t; - - using pointer = std::conditional_t::template rebind_traits::const_pointer, - typename std::allocator_traits::template rebind_traits::pointer>; - - sparse_iterator() noexcept {} - - // Copy constructor from iterator to const_iterator. - sparse_iterator(const sparse_iterator &other) noexcept requires (IsConst) - : m_sparse_buckets_it(other.m_sparse_buckets_it), - m_sparse_array_it(other.m_sparse_array_it) {} - - sparse_iterator(const sparse_iterator &other) = default; - sparse_iterator(sparse_iterator &&other) = default; - sparse_iterator &operator=(const sparse_iterator &other) = default; - sparse_iterator &operator=(sparse_iterator &&other) = default; - - reference operator*() const { return KeyValueSelect::both(*m_sparse_array_it); } - - //with fancy pointers addressof might be problematic. - pointer operator->() const { return &KeyValueSelect::both(*m_sparse_array_it); } - - sparse_iterator &operator++() { - tsl_sh_assert(m_sparse_array_it != nullptr); - ++m_sparse_array_it; - - //vector iterator with fancy pointers have a problem with -> - if (m_sparse_array_it == (*m_sparse_buckets_it).end()) { - do { - if ((*m_sparse_buckets_it).last()) { - ++m_sparse_buckets_it; - m_sparse_array_it = nullptr; - return *this; - } - - ++m_sparse_buckets_it; - } while ((*m_sparse_buckets_it).empty()); + m_sparse_buckets_data.resize( + sparse_array::nb_sparse_buckets(bucket_count)); + m_sparse_buckets = m_sparse_buckets_data.data(); - m_sparse_array_it = (*m_sparse_buckets_it).begin(); - } - - return *this; - } - - sparse_iterator operator++(int) { - sparse_iterator tmp(*this); - ++*this; - - return tmp; - } - - template - bool operator==(const sparse_iterator &other) const noexcept { - return m_sparse_buckets_it == other.m_sparse_buckets_it && m_sparse_array_it == other.m_sparse_array_it; - } - - template - bool operator!=(const sparse_iterator &other) const noexcept { - return m_sparse_buckets_it != other.m_sparse_buckets_it || m_sparse_array_it != other.m_sparse_array_it; - } - - private: - sparse_bucket_iterator m_sparse_buckets_it; - sparse_array_iterator m_sparse_array_it; - }; - - iterator mutable_iterator(const_iterator pos) noexcept { - // SAFETY: this is non-const therefore the underlying buckets are also non-const - // as evidenced by the fact that we can call begin on them - auto it_sparse_buckets = m_sparse_buckets_data.begin() + std::distance(m_sparse_buckets_data.cbegin(), pos.m_sparse_buckets_it); - - // SAFETY: this is non-const therefore the underlying sparse array is also non-const - auto it_array = sparse_array::unsafe_mutable_iterator(pos.m_sparse_array_it); - - return iterator(it_sparse_buckets, it_array); + assert(!m_sparse_buckets_data.empty()); + m_sparse_buckets_data.back().set_as_last(); } - public: - sparse_hash(size_type bucket_count, const Hash &hash, const KeyEqual &equal, - const Allocator &alloc, float max_load_factor) - : m_sparse_buckets_data(alloc), - // m_sparse_buckets_data(std::allocator_traits::rebind_alloc(m_alloc)), - m_sparse_buckets(static_empty_sparse_bucket_ptr()), - m_bucket_count(bucket_count), - m_nb_elements(0), - m_nb_deleted_buckets(0), - m_alloc{alloc}, - m_h{hash}, - m_keq{equal}, - m_gpol{bucket_count} { - - if (m_bucket_count > max_bucket_count()) { - throw std::length_error("The map exceeds its maximum size."); - } + this->max_load_factor(max_load_factor); - if (m_bucket_count > 0) { - /* - * We can't use the `vector(size_type count, const Allocator& m_alloc)` - * constructor as it's only available in C++14 and we need to support - * C++11. We thus must resize after using the `vector(const Allocator& - * m_alloc)` constructor. - * - * We can't use `vector(size_type count, const T& value, const Allocator& - * m_alloc)` as it requires the value T to be copyable. - */ - m_sparse_buckets_data.resize( - sparse_array::nb_sparse_buckets(bucket_count)); - m_sparse_buckets = m_sparse_buckets_data.data(); - - tsl_sh_assert(!m_sparse_buckets_data.empty()); - m_sparse_buckets_data.back().set_as_last(); - } - - this->max_load_factor(max_load_factor); - - // Check in the constructor instead of outside of a function to avoid - // compilation issues when value_type is not complete. - static_assert(std::is_nothrow_move_constructible::value || - std::is_copy_constructible::value, - "Key, and T if present, must be nothrow move constructible " - "and/or copy constructible."); - } - - ~sparse_hash() { clear(); } - - sparse_hash(const sparse_hash &other) - : m_sparse_buckets_data(std::allocator_traits::select_on_container_copy_construction(other.m_alloc)), - m_bucket_count(other.m_bucket_count), - m_nb_elements(other.m_nb_elements), - m_nb_deleted_buckets(other.m_nb_deleted_buckets), - m_load_threshold_rehash(other.m_load_threshold_rehash), - m_load_threshold_clear_deleted(other.m_load_threshold_clear_deleted), - m_max_load_factor(other.m_max_load_factor), - m_alloc{std::allocator_traits::select_on_container_copy_construction(other.m_alloc)}, - m_h{other.m_h}, - m_keq{other.m_keq}, - m_gpol{other.m_gpol} { - copy_buckets_from(other), - m_sparse_buckets = m_sparse_buckets_data.empty() - ? static_empty_sparse_bucket_ptr() - : m_sparse_buckets_data.data(); - } - - sparse_hash(sparse_hash &&other) noexcept(std::is_nothrow_move_constructible::value - && std::is_nothrow_move_constructible::value - && std::is_nothrow_move_constructible::value - && std::is_nothrow_move_constructible::value - && std::is_nothrow_move_constructible::value) - : m_sparse_buckets_data(std::move(other.m_sparse_buckets_data)), - m_sparse_buckets(m_sparse_buckets_data.empty() - ? static_empty_sparse_bucket_ptr() - : m_sparse_buckets_data.data()), - m_bucket_count(other.m_bucket_count), - m_nb_elements(other.m_nb_elements), - m_nb_deleted_buckets(other.m_nb_deleted_buckets), - m_load_threshold_rehash(other.m_load_threshold_rehash), - m_load_threshold_clear_deleted(other.m_load_threshold_clear_deleted), - m_max_load_factor(other.m_max_load_factor), - m_alloc{std::move(other.m_alloc)}, - m_h{std::move(other.m_h)}, - m_keq{std::move(other.m_keq)}, - m_gpol{std::move(other.m_gpol)} { - other.m_gpol.clear(); - other.m_sparse_buckets_data.clear(); - other.m_sparse_buckets = static_empty_sparse_bucket_ptr(); - other.m_bucket_count = 0; - other.m_nb_elements = 0; - other.m_nb_deleted_buckets = 0; - other.m_load_threshold_rehash = 0; - other.m_load_threshold_clear_deleted = 0; - } - - sparse_hash &operator=(const sparse_hash &other) { - if (this != &other) { - clear(); - - if (std::allocator_traits::propagate_on_container_copy_assignment::value) { - m_alloc = other.m_alloc; - } - - m_h = other.m_h; - m_keq = other.m_keq; - m_gpol = other.m_gpol; - - if (std::allocator_traits::propagate_on_container_copy_assignment::value) { - m_sparse_buckets_data = sparse_buckets_container(other.m_alloc); - } else { - if (m_sparse_buckets_data.size() != - other.m_sparse_buckets_data.size()) { - m_sparse_buckets_data = sparse_buckets_container(m_alloc); - } else { - m_sparse_buckets_data.clear(); - } - } + // Check in the constructor instead of outside of a function to avoid + // compilation issues when value_type is not complete. + static_assert(std::is_nothrow_move_constructible::value || + std::is_copy_constructible::value, + "Key, and T if present, must be nothrow move constructible " + "and/or copy constructible."); + } - copy_buckets_from(other); + ~sparse_hash() { clear(); } + + sparse_hash(const sparse_hash &other) + : m_sparse_buckets_data(std::allocator_traits::select_on_container_copy_construction(other.m_alloc)), + m_bucket_count(other.m_bucket_count), + m_nb_elements(other.m_nb_elements), + m_nb_deleted_buckets(other.m_nb_deleted_buckets), + m_load_threshold_rehash(other.m_load_threshold_rehash), + m_load_threshold_clear_deleted(other.m_load_threshold_clear_deleted), + m_max_load_factor(other.m_max_load_factor), + m_alloc{std::allocator_traits::select_on_container_copy_construction(other.m_alloc)}, + m_h{other.m_h}, + m_keq{other.m_keq}, + m_gpol{other.m_gpol} { + copy_buckets_from(other), m_sparse_buckets = m_sparse_buckets_data.empty() ? static_empty_sparse_bucket_ptr() : m_sparse_buckets_data.data(); + } - m_bucket_count = other.m_bucket_count; - m_nb_elements = other.m_nb_elements; - m_nb_deleted_buckets = other.m_nb_deleted_buckets; - m_load_threshold_rehash = other.m_load_threshold_rehash; - m_load_threshold_clear_deleted = other.m_load_threshold_clear_deleted; - m_max_load_factor = other.m_max_load_factor; - } - - return *this; - } + sparse_hash(sparse_hash &&other) noexcept(std::is_nothrow_move_constructible::value + && std::is_nothrow_move_constructible::value + && std::is_nothrow_move_constructible::value + && std::is_nothrow_move_constructible::value + && std::is_nothrow_move_constructible::value) + : m_sparse_buckets_data(std::move(other.m_sparse_buckets_data)), + m_sparse_buckets(m_sparse_buckets_data.empty() + ? static_empty_sparse_bucket_ptr() + : m_sparse_buckets_data.data()), + m_bucket_count(other.m_bucket_count), + m_nb_elements(other.m_nb_elements), + m_nb_deleted_buckets(other.m_nb_deleted_buckets), + m_load_threshold_rehash(other.m_load_threshold_rehash), + m_load_threshold_clear_deleted(other.m_load_threshold_clear_deleted), + m_max_load_factor(other.m_max_load_factor), + m_alloc{std::move(other.m_alloc)}, + m_h{std::move(other.m_h)}, + m_keq{std::move(other.m_keq)}, + m_gpol{std::move(other.m_gpol)} { + other.m_gpol.clear(); + other.m_sparse_buckets_data.clear(); + other.m_sparse_buckets = static_empty_sparse_bucket_ptr(); + other.m_bucket_count = 0; + other.m_nb_elements = 0; + other.m_nb_deleted_buckets = 0; + other.m_load_threshold_rehash = 0; + other.m_load_threshold_clear_deleted = 0; + } - sparse_hash &operator=(sparse_hash &&other) noexcept { + sparse_hash &operator=(const sparse_hash &other) { + if (this != &other) { clear(); - if (!std::allocator_traits::propagate_on_container_move_assignment::value && m_alloc != other.m_alloc) { - move_buckets_from(std::move(other)); + if (std::allocator_traits::propagate_on_container_copy_assignment::value) { + m_alloc = other.m_alloc; + } + + m_h = other.m_h; + m_keq = other.m_keq; + m_gpol = other.m_gpol; + + if (std::allocator_traits::propagate_on_container_copy_assignment::value) { + m_sparse_buckets_data = sparse_buckets_container(other.m_alloc); } else { - m_alloc = std::move(other.m_alloc); - m_sparse_buckets_data = std::move(other.m_sparse_buckets_data); + if (m_sparse_buckets_data.size() != + other.m_sparse_buckets_data.size()) { + m_sparse_buckets_data = sparse_buckets_container(m_alloc); + } else { + m_sparse_buckets_data.clear(); + } } + copy_buckets_from(other); m_sparse_buckets = m_sparse_buckets_data.empty() ? static_empty_sparse_bucket_ptr() : m_sparse_buckets_data.data(); - m_h = std::move(other.m_h); - m_keq = std::move(other.m_keq); - m_gpol = std::move(other.m_gpol); m_bucket_count = other.m_bucket_count; m_nb_elements = other.m_nb_elements; m_nb_deleted_buckets = other.m_nb_deleted_buckets; m_load_threshold_rehash = other.m_load_threshold_rehash; m_load_threshold_clear_deleted = other.m_load_threshold_clear_deleted; m_max_load_factor = other.m_max_load_factor; - - other.m_gpol.clear(); - other.m_sparse_buckets_data.clear(); - other.m_sparse_buckets = static_empty_sparse_bucket_ptr(); - other.m_bucket_count = 0; - other.m_nb_elements = 0; - other.m_nb_deleted_buckets = 0; - other.m_load_threshold_rehash = 0; - other.m_load_threshold_clear_deleted = 0; - - return *this; } - allocator_type get_allocator() const { - return static_cast(*this); - } + return *this; + } - iterator begin() noexcept { - auto begin = m_sparse_buckets_data.begin(); - //vector iterator with fancy pointers have a problem with -> - while (begin != m_sparse_buckets_data.end() && (*begin).empty()) { - ++begin; - } + sparse_hash &operator=(sparse_hash &&other) noexcept { + clear(); + + if (!std::allocator_traits::propagate_on_container_move_assignment::value && m_alloc != other.m_alloc) { + move_buckets_from(std::move(other)); + } else { + m_alloc = std::move(other.m_alloc); + m_sparse_buckets_data = std::move(other.m_sparse_buckets_data); + } + + m_sparse_buckets = m_sparse_buckets_data.empty() + ? static_empty_sparse_bucket_ptr() + : m_sparse_buckets_data.data(); + + m_h = std::move(other.m_h); + m_keq = std::move(other.m_keq); + m_gpol = std::move(other.m_gpol); + m_bucket_count = other.m_bucket_count; + m_nb_elements = other.m_nb_elements; + m_nb_deleted_buckets = other.m_nb_deleted_buckets; + m_load_threshold_rehash = other.m_load_threshold_rehash; + m_load_threshold_clear_deleted = other.m_load_threshold_clear_deleted; + m_max_load_factor = other.m_max_load_factor; + + other.m_gpol.clear(); + other.m_sparse_buckets_data.clear(); + other.m_sparse_buckets = static_empty_sparse_bucket_ptr(); + other.m_bucket_count = 0; + other.m_nb_elements = 0; + other.m_nb_deleted_buckets = 0; + other.m_load_threshold_rehash = 0; + other.m_load_threshold_clear_deleted = 0; + + return *this; + } - //vector iterator with fancy pointers have a problem with -> - return iterator(begin, (begin != m_sparse_buckets_data.end()) - ? (*begin).begin() - : nullptr); + allocator_type get_allocator() const { + return static_cast(*this); + } + + iterator begin() noexcept { + auto begin = m_sparse_buckets_data.begin(); + //vector iterator with fancy pointers have a problem with -> + while (begin != m_sparse_buckets_data.end() && (*begin).empty()) { + ++begin; } - const_iterator begin() const noexcept { return cbegin(); } + //vector iterator with fancy pointers have a problem with -> + return iterator(begin, (begin != m_sparse_buckets_data.end()) + ? (*begin).begin() + : nullptr); + } - const_iterator cbegin() const noexcept { - auto begin = m_sparse_buckets_data.cbegin(); - //vector iterator with fancy pointers have a problem with -> - while (begin != m_sparse_buckets_data.cend() && (*begin).empty()) { - ++begin; - } + const_iterator begin() const noexcept { return cbegin(); } - return const_iterator(begin, (begin != m_sparse_buckets_data.cend()) - ? (*begin).cbegin() - : nullptr); + const_iterator cbegin() const noexcept { + auto begin = m_sparse_buckets_data.cbegin(); + //vector iterator with fancy pointers have a problem with -> + while (begin != m_sparse_buckets_data.cend() && (*begin).empty()) { + ++begin; } - iterator end() noexcept { - return iterator(m_sparse_buckets_data.end(), nullptr); - } + return const_iterator(begin, (begin != m_sparse_buckets_data.cend()) + ? (*begin).cbegin() + : nullptr); + } - const_iterator end() const noexcept { return cend(); } + iterator end() noexcept { + return iterator(m_sparse_buckets_data.end(), nullptr); + } - const_iterator cend() const noexcept { - return const_iterator(m_sparse_buckets_data.cend(), nullptr); - } + const_iterator end() const noexcept { return cend(); } - bool empty() const noexcept { return m_nb_elements == 0; } + const_iterator cend() const noexcept { + return const_iterator(m_sparse_buckets_data.cend(), nullptr); + } - size_type size() const noexcept { return m_nb_elements; } + bool empty() const noexcept { return m_nb_elements == 0; } - size_type max_size() const noexcept { - return std::min(std::allocator_traits::max_size(), - m_sparse_buckets_data.max_size()); - } + size_type size() const noexcept { return m_nb_elements; } - void clear() noexcept { - for (auto &bucket : m_sparse_buckets_data) { - bucket.clear(m_alloc); - } + size_type max_size() const noexcept { + return std::min(std::allocator_traits::max_size(), + m_sparse_buckets_data.max_size()); + } - m_nb_elements = 0; - m_nb_deleted_buckets = 0; + void clear() noexcept { + for (auto &bucket : m_sparse_buckets_data) { + bucket.clear(m_alloc); } - template - std::pair insert(P &&value) { - return insert_impl(KeyValueSelect::key(value), std::forward

(value)); - } + m_nb_elements = 0; + m_nb_deleted_buckets = 0; + } - template - iterator insert_hint(const_iterator hint, P &&value) { - if (hint != cend() && - m_keq(KeyValueSelect::key(*hint), KeyValueSelect::key(value))) { - return mutable_iterator(hint); - } + template + std::pair insert(P &&value) { + return insert_impl(KeyValueSelect::key(value), std::forward

(value)); + } - return insert(std::forward

(value)).first; + template + iterator insert_hint(const_iterator hint, P &&value) { + if (hint != cend() && + m_keq(KeyValueSelect::key(*hint), KeyValueSelect::key(value))) { + return mutable_iterator(hint); } - template - void insert(InputIt first, InputIt last) { - if (std::is_base_of< - std::forward_iterator_tag, - typename std::iterator_traits::iterator_category>::value) { - const auto nb_elements_insert = std::distance(first, last); - const size_type nb_free_buckets = m_load_threshold_rehash - size(); - tsl_sh_assert(m_load_threshold_rehash >= size()); - - if (nb_elements_insert > 0 && - nb_free_buckets < size_type(nb_elements_insert)) { - reserve(size() + size_type(nb_elements_insert)); - } - } + return insert(std::forward

(value)).first; + } - for (; first != last; ++first) { - insert(*first); + template + void insert(InputIt first, InputIt last) { + if (std::is_base_of< + std::forward_iterator_tag, + typename std::iterator_traits::iterator_category>::value) { + const auto nb_elements_insert = std::distance(first, last); + const size_type nb_free_buckets = m_load_threshold_rehash - size(); + assert(m_load_threshold_rehash >= size()); + + if (nb_elements_insert > 0 && + nb_free_buckets < size_type(nb_elements_insert)) { + reserve(size() + size_type(nb_elements_insert)); } } - template - std::pair insert_or_assign(K &&key, M &&obj) { - auto it = try_emplace(std::forward(key), std::forward(obj)); - if (!it.second) { - it.first->second = std::forward(obj); - } + for (; first != last; ++first) { + insert(*first); + } + } - return it; + template + std::pair insert_or_assign(K &&key, M &&obj) { + auto it = try_emplace(std::forward(key), std::forward(obj)); + if (!it.second) { + it.first->second = std::forward(obj); } - template - iterator insert_or_assign(const_iterator hint, K &&key, M &&obj) { - if (hint != cend() && m_keq(KeyValueSelect::key(*hint), key)) { - auto it = mutable_iterator(hint); - it->second = std::forward(obj); + return it; + } - return it; - } + template + iterator insert_or_assign(const_iterator hint, K &&key, M &&obj) { + if (hint != cend() && m_keq(KeyValueSelect::key(*hint), key)) { + auto it = mutable_iterator(hint); + it->second = std::forward(obj); - return insert_or_assign(std::forward(key), std::forward(obj)).first; + return it; } - template - std::pair emplace(Args &&...args) { - return insert(value_type(std::forward(args)...)); - } + return insert_or_assign(std::forward(key), std::forward(obj)).first; + } - template - iterator emplace_hint(const_iterator hint, Args &&...args) { - return insert_hint(hint, value_type(std::forward(args)...)); - } + template + std::pair emplace(Args &&...args) { + return insert(value_type(std::forward(args)...)); + } - template - std::pair try_emplace(K &&key, Args &&...args) { - return insert_impl(key, std::piecewise_construct, - std::forward_as_tuple(std::forward(key)), - std::forward_as_tuple(std::forward(args)...)); - } + template + iterator emplace_hint(const_iterator hint, Args &&...args) { + return insert_hint(hint, value_type(std::forward(args)...)); + } - template - iterator try_emplace_hint(const_iterator hint, K &&key, Args &&...args) { - if (hint != cend() && m_keq(KeyValueSelect::key(*hint), key)) { - return mutable_iterator(hint); - } + template + std::pair try_emplace(K &&key, Args &&...args) { + return insert_impl(key, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(args)...)); + } - return try_emplace(std::forward(key), std::forward(args)...).first; + template + iterator try_emplace_hint(const_iterator hint, K &&key, Args &&...args) { + if (hint != cend() && m_keq(KeyValueSelect::key(*hint), key)) { + return mutable_iterator(hint); } - /** - * Here to avoid `template size_type erase(const K& key)` being used - * when we use an iterator instead of a const_iterator. - */ - iterator erase(iterator pos) { - tsl_sh_assert(pos != end() && m_nb_elements > 0); - //vector iterator with fancy pointers have a problem with -> - auto it_sparse_array_next = - (*pos.m_sparse_buckets_it).erase(m_alloc, pos.m_sparse_array_it); - m_nb_elements--; - m_nb_deleted_buckets++; - - if (it_sparse_array_next == (*pos.m_sparse_buckets_it).end()) { - auto it_sparse_buckets_next = pos.m_sparse_buckets_it; - do { - ++it_sparse_buckets_next; - } while (it_sparse_buckets_next != m_sparse_buckets_data.end() && - (*it_sparse_buckets_next).empty()); + return try_emplace(std::forward(key), std::forward(args)...).first; + } - if (it_sparse_buckets_next == m_sparse_buckets_data.end()) { - return end(); - } else { - return iterator(it_sparse_buckets_next, - (*it_sparse_buckets_next).begin()); - } + /** + * Here to avoid `template size_type erase(const K& key)` being used + * when we use an iterator instead of a const_iterator. + */ + iterator erase(iterator pos) { + assert(pos != end() && m_nb_elements > 0); + //vector iterator with fancy pointers have a problem with -> + auto it_sparse_array_next = + (*pos.m_sparse_buckets_it).erase(m_alloc, pos.m_sparse_array_it); + m_nb_elements--; + m_nb_deleted_buckets++; + + if (it_sparse_array_next == (*pos.m_sparse_buckets_it).end()) { + auto it_sparse_buckets_next = pos.m_sparse_buckets_it; + do { + ++it_sparse_buckets_next; + } while (it_sparse_buckets_next != m_sparse_buckets_data.end() && + (*it_sparse_buckets_next).empty()); + + if (it_sparse_buckets_next == m_sparse_buckets_data.end()) { + return end(); } else { - return iterator(pos.m_sparse_buckets_it, it_sparse_array_next); + return iterator(it_sparse_buckets_next, + (*it_sparse_buckets_next).begin()); } + } else { + return iterator(pos.m_sparse_buckets_it, it_sparse_array_next); } + } - iterator erase(const_iterator pos) { return erase(mutable_iterator(pos)); } - - iterator erase(const_iterator first, const_iterator last) { - if (first == last) { - return mutable_iterator(first); - } - - // TODO Optimize, could avoid the call to std::distance. - const size_type nb_elements_to_erase = - static_cast(std::distance(first, last)); - auto to_delete = mutable_iterator(first); - for (size_type i = 0; i < nb_elements_to_erase; i++) { - to_delete = erase(to_delete); - } - - return to_delete; - } + iterator erase(const_iterator pos) { return erase(mutable_iterator(pos)); } - template - size_type erase(const K &key) { - return erase(key, m_h(key)); + iterator erase(const_iterator first, const_iterator last) { + if (first == last) { + return mutable_iterator(first); } - template - size_type erase(const K &key, std::size_t hash) { - return erase_impl(key, hash); + // TODO Optimize, could avoid the call to std::distance. + const size_type nb_elements_to_erase = + static_cast(std::distance(first, last)); + auto to_delete = mutable_iterator(first); + for (size_type i = 0; i < nb_elements_to_erase; i++) { + to_delete = erase(to_delete); } - void swap(sparse_hash &other) { - using std::swap; + return to_delete; + } - if (std::allocator_traits::propagate_on_container_swap::value) { - swap(m_alloc, other.m_alloc); - } else { - tsl_sh_assert(m_alloc == other.m_alloc); - } + template + size_type erase(const K &key) { + return erase(key, m_h(key)); + } - swap(m_h, other.m_h); - swap(m_keq, other.m_keq); - swap(m_gpol, other.m_gpol); - swap(m_sparse_buckets_data, other.m_sparse_buckets_data); - swap(m_sparse_buckets, other.m_sparse_buckets); - swap(m_bucket_count, other.m_bucket_count); - swap(m_nb_elements, other.m_nb_elements); - swap(m_nb_deleted_buckets, other.m_nb_deleted_buckets); - swap(m_load_threshold_rehash, other.m_load_threshold_rehash); - swap(m_load_threshold_clear_deleted, other.m_load_threshold_clear_deleted); - swap(m_max_load_factor, other.m_max_load_factor); - } + template + size_type erase(const K &key, std::size_t hash) { + return erase_impl(key, hash); + } - template requires (has_mapped_type) - mapped_reference at(const K &key) { - return at_impl(*this, key, m_h(key)); - } + void swap(sparse_hash &other) { + using std::swap; + + if (std::allocator_traits::propagate_on_container_swap::value) { + swap(m_alloc, other.m_alloc); + } else { + assert(m_alloc == other.m_alloc); + } + + swap(m_h, other.m_h); + swap(m_keq, other.m_keq); + swap(m_gpol, other.m_gpol); + swap(m_sparse_buckets_data, other.m_sparse_buckets_data); + swap(m_sparse_buckets, other.m_sparse_buckets); + swap(m_bucket_count, other.m_bucket_count); + swap(m_nb_elements, other.m_nb_elements); + swap(m_nb_deleted_buckets, other.m_nb_deleted_buckets); + swap(m_load_threshold_rehash, other.m_load_threshold_rehash); + swap(m_load_threshold_clear_deleted, other.m_load_threshold_clear_deleted); + swap(m_max_load_factor, other.m_max_load_factor); + } - template requires (has_mapped_type) - mapped_reference at(const K &key, std::size_t hash) { - return at_impl(*this, key, hash); - } + template requires (has_mapped_type) + mapped_reference at(const K &key) { + return at_impl(*this, key, m_h(key)); + } - template requires (has_mapped_type) - mapped_const_reference at(const K &key) const { - return at_impl(*this, key, m_h(key)); - } + template requires (has_mapped_type) + mapped_reference at(const K &key, std::size_t hash) { + return at_impl(*this, key, hash); + } - template requires (has_mapped_type) - mapped_const_reference at(const K &key, std::size_t hash) const { - return at_impl(*this, key, hash); - } + template requires (has_mapped_type) + mapped_const_reference at(const K &key) const { + return at_impl(*this, key, m_h(key)); + } - template requires (has_mapped_type) - mapped_reference operator[](K &&key) { - return try_emplace(std::forward(key)).first->second; - } + template requires (has_mapped_type) + mapped_const_reference at(const K &key, std::size_t hash) const { + return at_impl(*this, key, hash); + } - template - bool contains(const K &key) const { - return contains(key, m_h(key)); - } + template requires (has_mapped_type) + mapped_reference operator[](K &&key) { + return try_emplace(std::forward(key)).first->second; + } - template - bool contains(const K &key, std::size_t hash) const { - return count(key, hash) != 0; - } + template + bool contains(const K &key) const { + return contains(key, m_h(key)); + } - template - size_type count(const K &key) const { - return count(key, m_h(key)); - } + template + bool contains(const K &key, std::size_t hash) const { + return count(key, hash) != 0; + } - template - size_type count(const K &key, std::size_t hash) const { - if (find(key, hash) != cend()) { - return 1; - } else { - return 0; - } - } + template + size_type count(const K &key) const { + return count(key, m_h(key)); + } - template - iterator find(const K &key) { - return find_impl(*this, key, m_h(key)); + template + size_type count(const K &key, std::size_t hash) const { + if (find(key, hash) != cend()) { + return 1; + } else { + return 0; } + } - template - iterator find(const K &key, std::size_t hash) { - return find_impl(*this, key, hash); - } + template + iterator find(const K &key) { + return find_impl(*this, key, m_h(key)); + } - template - const_iterator find(const K &key) const { - return find_impl(*this, key, m_h(key)); - } + template + iterator find(const K &key, std::size_t hash) { + return find_impl(*this, key, hash); + } - template - const_iterator find(const K &key, std::size_t hash) const { - return find_impl(*this, key, hash); - } + template + const_iterator find(const K &key) const { + return find_impl(*this, key, m_h(key)); + } - template - std::pair equal_range(const K &key) { - return equal_range(key, m_h(key)); - } + template + const_iterator find(const K &key, std::size_t hash) const { + return find_impl(*this, key, hash); + } - template - std::pair equal_range(const K &key, std::size_t hash) { - iterator it = find(key, hash); - return std::make_pair(it, (it == end()) ? it : std::next(it)); - } + template + std::pair equal_range(const K &key) { + return equal_range(key, m_h(key)); + } - template - std::pair equal_range(const K &key) const { - return equal_range(key, m_h(key)); - } + template + std::pair equal_range(const K &key, std::size_t hash) { + iterator it = find(key, hash); + return std::make_pair(it, (it == end()) ? it : std::next(it)); + } - template - std::pair equal_range( - const K &key, std::size_t hash) const { - const_iterator it = find(key, hash); - return std::make_pair(it, (it == cend()) ? it : std::next(it)); - } + template + std::pair equal_range(const K &key) const { + return equal_range(key, m_h(key)); + } - size_type bucket_count() const { return m_bucket_count; } + template + std::pair equal_range( + const K &key, std::size_t hash) const { + const_iterator it = find(key, hash); + return std::make_pair(it, (it == cend()) ? it : std::next(it)); + } - size_type max_bucket_count() const { - return m_sparse_buckets_data.max_size(); - } + size_type bucket_count() const { return m_bucket_count; } - float load_factor() const { - if (bucket_count() == 0) { - return 0; - } + size_type max_bucket_count() const { + return m_sparse_buckets_data.max_size(); + } - return float(m_nb_elements) / float(bucket_count()); + float load_factor() const { + if (bucket_count() == 0) { + return 0; } - float max_load_factor() const { return m_max_load_factor; } + return float(m_nb_elements) / float(bucket_count()); + } - void max_load_factor(float ml) { - m_max_load_factor = std::max(0.1f, std::min(ml, 0.8f)); - m_load_threshold_rehash = - size_type(float(bucket_count()) * m_max_load_factor); + float max_load_factor() const { return m_max_load_factor; } - const float max_load_factor_with_deleted_buckets = - m_max_load_factor + 0.5f * (1.0f - m_max_load_factor); - tsl_sh_assert(max_load_factor_with_deleted_buckets > 0.0f && - max_load_factor_with_deleted_buckets <= 1.0f); - m_load_threshold_clear_deleted = - size_type(float(bucket_count()) * max_load_factor_with_deleted_buckets); - } + void max_load_factor(float ml) { + m_max_load_factor = std::max(0.1f, std::min(ml, 0.8f)); + m_load_threshold_rehash = + size_type(float(bucket_count()) * m_max_load_factor); - void rehash(size_type count) { - count = std::max(count, - size_type(std::ceil(float(size()) / max_load_factor()))); - rehash_impl(count); - } + const float max_load_factor_with_deleted_buckets = + m_max_load_factor + 0.5f * (1.0f - m_max_load_factor); + assert(max_load_factor_with_deleted_buckets > 0.0f && + max_load_factor_with_deleted_buckets <= 1.0f); + m_load_threshold_clear_deleted = + size_type(float(bucket_count()) * max_load_factor_with_deleted_buckets); + } - void reserve(size_type count) { - rehash(size_type(std::ceil(float(count) / max_load_factor()))); - } + void rehash(size_type count) { + count = std::max(count, + size_type(std::ceil(float(size()) / max_load_factor()))); + rehash_impl(count); + } - [[nodiscard]] hasher hash_function() const { return m_h; } - [[nodiscard]] key_equal key_eq() const { return m_keq; } + void reserve(size_type count) { + rehash(size_type(std::ceil(float(count) / max_load_factor()))); + } - private: - size_type bucket_for_hash(std::size_t hash) const { - auto const bucket = m_gpol.bucket_for_hash(hash); - tsl_sh_assert(sparse_array::sparse_ibucket(bucket) < m_sparse_buckets_data.size() - || (bucket == 0 && m_sparse_buckets_data.empty())); + [[nodiscard]] hasher hash_function() const { return m_h; } + [[nodiscard]] key_equal key_eq() const { return m_keq; } - return bucket; - } + private: + size_type bucket_for_hash(std::size_t hash) const { + auto const bucket = m_gpol.bucket_for_hash(hash); + assert(sparse_array::sparse_ibucket(bucket) < m_sparse_buckets_data.size() + || (bucket == 0 && m_sparse_buckets_data.empty())); - size_type next_bucket(size_type ibucket, size_type iprobe) const requires (is_power_of_two_policy::value) { - (void) iprobe; - if (Probing == dice::sparse_map::sh::probing::linear) { - return (ibucket + 1) & m_gpol.mask(); - } else { - tsl_sh_assert(Probing == dice::sparse_map::sh::probing::quadratic); - return (ibucket + iprobe) & m_gpol.mask(); - } - } + return bucket; + } - size_type next_bucket(size_type ibucket, size_type iprobe) const requires (!is_power_of_two_policy::value) { - (void) iprobe; - if (Probing == dice::sparse_map::sh::probing::linear) { - ibucket++; - return (ibucket != bucket_count()) ? ibucket : 0; - } else { - tsl_sh_assert(Probing == dice::sparse_map::sh::probing::quadratic); - ibucket += iprobe; - return (ibucket < bucket_count()) ? ibucket : ibucket % bucket_count(); - } + size_type next_bucket(size_type ibucket, [[maybe_unused]] size_type iprobe) const requires (is_power_of_two_policy::value) { + if constexpr (Probing == probing::linear) { + return (ibucket + 1) & m_gpol.mask(); + } else { + assert(Probing == probing::quadratic); + return (ibucket + iprobe) & m_gpol.mask(); } + } - // TODO encapsulate m_sparse_buckets_data to avoid the managing the allocator - void copy_buckets_from(const sparse_hash &other) { - m_sparse_buckets_data.reserve(other.m_sparse_buckets_data.size()); - - try { - for (const auto &bucket : other.m_sparse_buckets_data) { - m_sparse_buckets_data.emplace_back(bucket, m_alloc); - } - } catch (...) { - clear(); - throw; - } - - tsl_sh_assert(m_sparse_buckets_data.empty() || - m_sparse_buckets_data.back().last()); + size_type next_bucket(size_type ibucket, [[maybe_unused]] size_type iprobe) const requires (!is_power_of_two_policy::value) { + if constexpr (Probing == probing::linear) { + ibucket++; + return (ibucket != bucket_count()) ? ibucket : 0; + } else { + assert(Probing == probing::quadratic); + ibucket += iprobe; + return (ibucket < bucket_count()) ? ibucket : ibucket % bucket_count(); } + } - void move_buckets_from(sparse_hash &&other) { - m_sparse_buckets_data.reserve(other.m_sparse_buckets_data.size()); + // TODO encapsulate m_sparse_buckets_data to avoid the managing the allocator + void copy_buckets_from(const sparse_hash &other) { + m_sparse_buckets_data.reserve(other.m_sparse_buckets_data.size()); - try { - for (auto &&bucket : other.m_sparse_buckets_data) { - m_sparse_buckets_data.emplace_back(std::move(bucket), m_alloc); - } - } catch (...) { - clear(); - throw; + try { + for (const auto &bucket : other.m_sparse_buckets_data) { + m_sparse_buckets_data.emplace_back(bucket, m_alloc); } - - tsl_sh_assert(m_sparse_buckets_data.empty() || - m_sparse_buckets_data.back().last()); + } catch (...) { + clear(); + throw; } - template - std::pair insert_impl(const K &key, - Args &&...value_type_args) { - if (size() >= m_load_threshold_rehash) { - rehash_impl(m_gpol.next_bucket_count()); - } else if (size() + m_nb_deleted_buckets >= - m_load_threshold_clear_deleted) { - clear_deleted_buckets(); - } - tsl_sh_assert(!m_sparse_buckets_data.empty()); - - /** - * We must insert the value in the first empty or deleted bucket we find. If - * we first find a deleted bucket, we still have to continue the search - * until we find an empty bucket or until we have searched all the buckets - * to be sure that the value is not in the hash table. We thus remember the - * position, if any, of the first deleted bucket we have encountered so we - * can insert it there if needed. - */ - bool found_first_deleted_bucket = false; - std::size_t sparse_ibucket_first_deleted = 0; - typename sparse_array::size_type index_in_sparse_bucket_first_deleted = 0; - - const std::size_t hash = m_h(key); - std::size_t ibucket = bucket_for_hash(hash); - - std::size_t probe = 0; - while (true) { - std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); - auto index_in_sparse_bucket = - sparse_array::index_in_sparse_bucket(ibucket); - - if (m_sparse_buckets != static_empty_sparse_bucket_ptr()) { - if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { - auto value_it = - m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); - if (m_keq(key, KeyValueSelect::key(*value_it))) { - return std::make_pair( - iterator(m_sparse_buckets_data.begin() + sparse_ibucket, - value_it), - false); - } - } else if (m_sparse_buckets[sparse_ibucket].has_deleted_value( - index_in_sparse_bucket) && - probe < m_bucket_count) { - if (!found_first_deleted_bucket) { - found_first_deleted_bucket = true; - sparse_ibucket_first_deleted = sparse_ibucket; - index_in_sparse_bucket_first_deleted = index_in_sparse_bucket; - } - } else if (found_first_deleted_bucket) { - auto it = insert_in_bucket(sparse_ibucket_first_deleted, - index_in_sparse_bucket_first_deleted, - std::forward(value_type_args)...); - m_nb_deleted_buckets--; - - return it; - } else { - return insert_in_bucket(sparse_ibucket, index_in_sparse_bucket, - std::forward(value_type_args)...); - } - } else { - return insert_in_bucket(sparse_ibucket, index_in_sparse_bucket, - std::forward(value_type_args)...); - } + assert(m_sparse_buckets_data.empty() || + m_sparse_buckets_data.back().last()); + } + + void move_buckets_from(sparse_hash &&other) { + m_sparse_buckets_data.reserve(other.m_sparse_buckets_data.size()); - probe++; - ibucket = next_bucket(ibucket, probe); + try { + for (auto &&bucket : other.m_sparse_buckets_data) { + m_sparse_buckets_data.emplace_back(std::move(bucket), m_alloc); } + } catch (...) { + clear(); + throw; } - template - std::pair insert_in_bucket( - std::size_t sparse_ibucket, - typename sparse_array::size_type index_in_sparse_bucket, - Args &&...value_type_args) { - // is not called when empty - auto value_it = m_sparse_buckets[sparse_ibucket].set( - m_alloc, index_in_sparse_bucket, std::forward(value_type_args)...); - m_nb_elements++; - - return std::make_pair( - iterator(m_sparse_buckets_data.begin() + sparse_ibucket, value_it), - true); + assert(m_sparse_buckets_data.empty() || + m_sparse_buckets_data.back().last()); + } + + template + std::pair insert_impl(const K &key, + Args &&...value_type_args) { + if (size() >= m_load_threshold_rehash) { + rehash_impl(m_gpol.next_bucket_count()); + } else if (size() + m_nb_deleted_buckets >= + m_load_threshold_clear_deleted) { + clear_deleted_buckets(); } + assert(!m_sparse_buckets_data.empty()); - template - size_type erase_impl(const K &key, std::size_t hash) { - std::size_t ibucket = bucket_for_hash(hash); + /** + * We must insert the value in the first empty or deleted bucket we find. If + * we first find a deleted bucket, we still have to continue the search + * until we find an empty bucket or until we have searched all the buckets + * to be sure that the value is not in the hash table. We thus remember the + * position, if any, of the first deleted bucket we have encountered so we + * can insert it there if needed. + */ + bool found_first_deleted_bucket = false; + std::size_t sparse_ibucket_first_deleted = 0; + typename sparse_array::size_type index_in_sparse_bucket_first_deleted = 0; - std::size_t probe = 0; + const std::size_t hash = m_h(key); + std::size_t ibucket = bucket_for_hash(hash); - if (m_sparse_buckets == static_empty_sparse_bucket_ptr()) - return 0; - while (true) { - const std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); - const auto index_in_sparse_bucket = - sparse_array::index_in_sparse_bucket(ibucket); + std::size_t probe = 0; + while (true) { + std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); + auto index_in_sparse_bucket = + sparse_array::index_in_sparse_bucket(ibucket); + if (m_sparse_buckets != static_empty_sparse_bucket_ptr()) { if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { auto value_it = m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); if (m_keq(key, KeyValueSelect::key(*value_it))) { - m_sparse_buckets[sparse_ibucket].erase(m_alloc, value_it, - index_in_sparse_bucket); - m_nb_elements--; - m_nb_deleted_buckets++; - - return 1; + return std::make_pair( + iterator(m_sparse_buckets_data.begin() + sparse_ibucket, + value_it), + false); } - } else if (!m_sparse_buckets[sparse_ibucket].has_deleted_value( - index_in_sparse_bucket) || - probe >= m_bucket_count) { - return 0; - } + } else if (m_sparse_buckets[sparse_ibucket].has_deleted_value( + index_in_sparse_bucket) && + probe < m_bucket_count) { + if (!found_first_deleted_bucket) { + found_first_deleted_bucket = true; + sparse_ibucket_first_deleted = sparse_ibucket; + index_in_sparse_bucket_first_deleted = index_in_sparse_bucket; + } + } else if (found_first_deleted_bucket) { + auto it = insert_in_bucket(sparse_ibucket_first_deleted, + index_in_sparse_bucket_first_deleted, + std::forward(value_type_args)...); + m_nb_deleted_buckets--; - probe++; - ibucket = next_bucket(ibucket, probe); + return it; + } else { + return insert_in_bucket(sparse_ibucket, index_in_sparse_bucket, + std::forward(value_type_args)...); + } + } else { + return insert_in_bucket(sparse_ibucket, index_in_sparse_bucket, + std::forward(value_type_args)...); } - } - template - static auto find_impl(Self &&self, const K &key, std::size_t hash) { - static constexpr bool is_const = std::is_const_v>; - std::size_t ibucket = self.bucket_for_hash(hash); + probe++; + ibucket = next_bucket(ibucket, probe); + } + } - std::size_t probe = 0; - while (true) { - const std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); - const auto index_in_sparse_bucket = - sparse_array::index_in_sparse_bucket(ibucket); + template + std::pair insert_in_bucket( + std::size_t sparse_ibucket, + typename sparse_array::size_type index_in_sparse_bucket, + Args &&...value_type_args) { + // is not called when empty + auto value_it = m_sparse_buckets[sparse_ibucket].set( + m_alloc, index_in_sparse_bucket, std::forward(value_type_args)...); + m_nb_elements++; + + return std::make_pair( + iterator(m_sparse_buckets_data.begin() + sparse_ibucket, value_it), + true); + } - if (self.m_sparse_buckets == static_empty_sparse_bucket_ptr()) { - return self.end(); + template + size_type erase_impl(const K &key, std::size_t hash) { + std::size_t ibucket = bucket_for_hash(hash); + + std::size_t probe = 0; + + if (m_sparse_buckets == static_empty_sparse_bucket_ptr()) + return 0; + while (true) { + const std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); + const auto index_in_sparse_bucket = + sparse_array::index_in_sparse_bucket(ibucket); + + if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { + auto value_it = + m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); + if (m_keq(key, KeyValueSelect::key(*value_it))) { + m_sparse_buckets[sparse_ibucket].erase(m_alloc, value_it, + index_in_sparse_bucket); + m_nb_elements--; + m_nb_deleted_buckets++; + + return 1; } - if (self.m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { - auto value_it = self.m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); - if (self.m_keq(key, KeyValueSelect::key(*value_it))) { - return sparse_iterator{self.m_sparse_buckets_data.begin() + sparse_ibucket, value_it}; - } - } else if (!self.m_sparse_buckets[sparse_ibucket].has_deleted_value( - index_in_sparse_bucket) || - probe >= self.m_bucket_count) { - return self.end(); - } - - probe++; - ibucket = self.next_bucket(ibucket, probe); + } else if (!m_sparse_buckets[sparse_ibucket].has_deleted_value( + index_in_sparse_bucket) || + probe >= m_bucket_count) { + return 0; } + + probe++; + ibucket = next_bucket(ibucket, probe); } + } + + template + static auto find_impl(Self &&self, const K &key, std::size_t hash) { + static constexpr bool is_const = std::is_const_v>; + std::size_t ibucket = self.bucket_for_hash(hash); + + std::size_t probe = 0; + while (true) { + const std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); + const auto index_in_sparse_bucket = + sparse_array::index_in_sparse_bucket(ibucket); - template - static decltype(auto) at_impl(Self &&self, K const &key, std::size_t hash) { - if (auto it = find_impl(self, key, hash); it != self.end()) { - return KeyValueSelect::value(*it); + if (self.m_sparse_buckets == static_empty_sparse_bucket_ptr()) { + return self.end(); + } + if (self.m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { + auto value_it = self.m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); + if (self.m_keq(key, KeyValueSelect::key(*value_it))) { + return sparse_iterator{self.m_sparse_buckets_data.begin() + sparse_ibucket, value_it}; + } + } else if (!self.m_sparse_buckets[sparse_ibucket].has_deleted_value( + index_in_sparse_bucket) || + probe >= self.m_bucket_count) { + return self.end(); } - throw std::out_of_range{"Couldn't find key."}; + probe++; + ibucket = self.next_bucket(ibucket, probe); } + } - void clear_deleted_buckets() { - // TODO could be optimized, we could do it in-place instead of allocating a - // new bucket array. - rehash_impl(m_bucket_count); - tsl_sh_assert(m_nb_deleted_buckets == 0); + template + static decltype(auto) at_impl(Self &&self, K const &key, std::size_t hash) { + if (auto it = find_impl(self, key, hash); it != self.end()) { + return KeyValueSelect::value(*it); } - template::type - * = nullptr> - void rehash_impl(size_type count) { - sparse_hash new_table(count, m_h, m_keq, m_alloc, m_max_load_factor); + throw std::out_of_range{"Couldn't find key."}; + } - for (auto &bucket : m_sparse_buckets_data) { - for (auto &val : bucket) { - new_table.insert_on_rehash(std::move(val)); - } + void clear_deleted_buckets() { + // TODO could be optimized, we could do it in-place instead of allocating a + // new bucket array. + rehash_impl(m_bucket_count); + assert(m_nb_deleted_buckets == 0); + } + + template::type + * = nullptr> + void rehash_impl(size_type count) { + sparse_hash new_table(count, m_h, m_keq, m_alloc, m_max_load_factor); - // TODO try to reuse some of the memory - bucket.clear(m_alloc); + for (auto &bucket : m_sparse_buckets_data) { + for (auto &val : bucket) { + new_table.insert_on_rehash(std::move(val)); } - new_table.swap(*this); + // TODO try to reuse some of the memory + bucket.clear(m_alloc); } - /** - * TODO: For now we copy each element into the new map. We could move - * them if they are nothrow_move_constructible without triggering - * any exception if we reserve enough space in the sparse arrays beforehand. - */ - template::type * = nullptr> - void rehash_impl(size_type count) { - sparse_hash new_table(count, m_h, m_keq, m_alloc, m_max_load_factor); - - for (const auto &bucket : m_sparse_buckets_data) { - for (const auto &val : bucket) { - new_table.insert_on_rehash(val); - } - } + new_table.swap(*this); + } - new_table.swap(*this); + /** + * TODO: For now we copy each element into the new map. We could move + * them if they are nothrow_move_constructible without triggering + * any exception if we reserve enough space in the sparse arrays beforehand. + */ + template::type * = nullptr> + void rehash_impl(size_type count) { + sparse_hash new_table(count, m_h, m_keq, m_alloc, m_max_load_factor); + + for (const auto &bucket : m_sparse_buckets_data) { + for (const auto &val : bucket) { + new_table.insert_on_rehash(val); + } } - template - void insert_on_rehash(K &&key_value) { - const key_type &key = KeyValueSelect::key(key_value); + new_table.swap(*this); + } - const std::size_t hash = m_h(key); - std::size_t ibucket = bucket_for_hash(hash); + template + void insert_on_rehash(K &&key_value) { + const key_type &key = KeyValueSelect::key(key_value); - std::size_t probe = 0; - while (true) { - std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); - auto index_in_sparse_bucket = - sparse_array::index_in_sparse_bucket(ibucket); + const std::size_t hash = m_h(key); + std::size_t ibucket = bucket_for_hash(hash); - if (!m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { - m_sparse_buckets[sparse_ibucket].set(m_alloc, index_in_sparse_bucket, - std::forward(key_value)); - m_nb_elements++; + std::size_t probe = 0; + while (true) { + std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); + auto index_in_sparse_bucket = + sparse_array::index_in_sparse_bucket(ibucket); - return; - } else { - tsl_sh_assert(!m_keq( - key, KeyValueSelect::key(*m_sparse_buckets[sparse_ibucket].value( - index_in_sparse_bucket)))); - } + if (!m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { + m_sparse_buckets[sparse_ibucket].set(m_alloc, index_in_sparse_bucket, + std::forward(key_value)); + m_nb_elements++; - probe++; - ibucket = next_bucket(ibucket, probe); + return; + } else { + assert(!m_keq( + key, KeyValueSelect::key(*m_sparse_buckets[sparse_ibucket].value( + index_in_sparse_bucket)))); } + + probe++; + ibucket = next_bucket(ibucket, probe); } + } - public: - static constexpr size_type DEFAULT_INIT_BUCKET_COUNT = 0; - static constexpr float DEFAULT_MAX_LOAD_FACTOR = 0.5f; + public: + static constexpr size_type DEFAULT_INIT_BUCKET_COUNT = 0; + static constexpr float DEFAULT_MAX_LOAD_FACTOR = 0.5f; - using sparse_array_ptr = typename std::allocator_traits::template rebind_traits::pointer; + using sparse_array_ptr = typename std::allocator_traits::template rebind_traits::pointer; - /** - * Return an nullptr to indicate an empty bucket - */ - static sparse_array_ptr static_empty_sparse_bucket_ptr() { - return {}; - } + /** + * Return an nullptr to indicate an empty bucket + */ + static sparse_array_ptr static_empty_sparse_bucket_ptr() { + return {}; + } - private: - sparse_buckets_container m_sparse_buckets_data; + private: + sparse_buckets_container m_sparse_buckets_data; - /** - * Points to m_sparse_buckets_data.data() if !m_sparse_buckets_data.empty() - * otherwise points to static_empty_sparse_bucket_ptr. This variable is useful - * to avoid the cost of checking if m_sparse_buckets_data is empty when trying - * to find an element. - * - * TODO Remove m_sparse_buckets_data and only use a pointer instead of a - * pointer+vector to save some space in the sparse_hash object. - */ - sparse_array_ptr m_sparse_buckets; - - size_type m_bucket_count; - size_type m_nb_elements; - size_type m_nb_deleted_buckets; + /** + * Points to m_sparse_buckets_data.data() if !m_sparse_buckets_data.empty() + * otherwise points to static_empty_sparse_bucket_ptr. This variable is useful + * to avoid the cost of checking if m_sparse_buckets_data is empty when trying + * to find an element. + * + * TODO Remove m_sparse_buckets_data and only use a pointer instead of a + * pointer+vector to save some space in the sparse_hash object. + */ + sparse_array_ptr m_sparse_buckets; - /** - * Maximum that m_nb_elements can reach before a rehash occurs automatically - * to grow the hash table. - */ - size_type m_load_threshold_rehash; + size_type m_bucket_count; + size_type m_nb_elements; + size_type m_nb_deleted_buckets; - /** - * Maximum that m_nb_elements + m_nb_deleted_buckets can reach before cleaning - * up the buckets marked as deleted. - */ - size_type m_load_threshold_clear_deleted; - float m_max_load_factor; - - [[no_unique_address]] allocator_type m_alloc; - [[no_unique_address]] hasher m_h; - [[no_unique_address]] key_equal m_keq; - [[no_unique_address]] growth_policy m_gpol; - }; + /** + * Maximum that m_nb_elements can reach before a rehash occurs automatically + * to grow the hash table. + */ + size_type m_load_threshold_rehash; - }// namespace detail_sparse_hash + /** + * Maximum that m_nb_elements + m_nb_deleted_buckets can reach before cleaning + * up the buckets marked as deleted. + */ + size_type m_load_threshold_clear_deleted; + float m_max_load_factor; + + [[no_unique_address]] allocator_type m_alloc; + [[no_unique_address]] hasher m_h; + [[no_unique_address]] key_equal m_keq; + [[no_unique_address]] growth_policy m_gpol; + }; }// namespace dice::sparse_map #endif diff --git a/include/dice/sparse-map/sparse_map.hpp b/include/dice/sparse-map/sparse_map.hpp index 41a44b8..4344f5b 100644 --- a/include/dice/sparse-map/sparse_map.hpp +++ b/include/dice/sparse-map/sparse_map.hpp @@ -79,14 +79,13 @@ namespace dice::sparse_map { * insert, invalidate the iterators. * - erase: always invalidate the iterators. */ - template, - class KeyEqual = std::equal_to, - class Allocator = std::allocator>, - class GrowthPolicy = dice::sparse_map::sh::power_of_two_growth_policy<2>, - dice::sparse_map::sh::exception_safety ExceptionSafety = - dice::sparse_map::sh::exception_safety::basic, - dice::sparse_map::sh::sparsity Sparsity = dice::sparse_map::sh::sparsity::medium> + template, + typename KeyEqual = std::equal_to, + typename Allocator = std::allocator>, + growth_policy GrowthPolicy = power_of_two_growth_policy<2>, + exception_safety ExceptionSafety = exception_safety::basic, + sparsity Sparsity = sparsity::medium> class sparse_map { static constexpr bool key_equal_is_transparent = requires { typename KeyEqual::is_transparent; @@ -123,8 +122,8 @@ namespace dice::sparse_map { } }; - using ht = detail_sparse_hash::sparse_hash, KVSelect, Hash, KeyEqual, Allocator, - GrowthPolicy, ExceptionSafety, Sparsity, dice::sparse_map::sh::probing::quadratic>; + using ht = detail::sparse_hash, KVSelect, Hash, KeyEqual, Allocator, + GrowthPolicy, ExceptionSafety, Sparsity, probing::quadratic>; public: using key_type = typename ht::key_type; @@ -669,11 +668,11 @@ namespace dice::sparse_map { * Same as `dice::sparse_map`. */ - template, - class KeyEqual = std::equal_to, - class Allocator = std::allocator>> - using sparse_pg_map = - sparse_map; + template, + typename KeyEqual = std::equal_to, + typename Allocator = std::allocator>> + using sparse_pg_map = sparse_map; }// namespace dice::sparse_map diff --git a/include/dice/sparse-map/sparse_props.hpp b/include/dice/sparse-map/sparse_props.hpp new file mode 100644 index 0000000..5aa65d9 --- /dev/null +++ b/include/dice/sparse-map/sparse_props.hpp @@ -0,0 +1,32 @@ +#ifndef DICE_SPARSE_MAP_SPARSE_PROPS_HPP +#define DICE_SPARSE_MAP_SPARSE_PROPS_HPP + +#include +#include + +namespace dice::sparse_map { + namespace detail { + template + constexpr bool is_power_of_2(U x) { + return std::popcount(x) == 1; + } + } // namespace detail + + enum class probing { + linear, + quadratic + }; + + enum class exception_safety { + basic, + strong + }; + + enum class sparsity { + high, + medium, + low + }; +} // namespace dice::sparse_map + +#endif//DICE_SPARSE_MAP_SPARSE_PROPS_HPP diff --git a/include/dice/sparse-map/sparse_set.hpp b/include/dice/sparse-map/sparse_set.hpp index ebda9dc..7e5e9b5 100644 --- a/include/dice/sparse-map/sparse_set.hpp +++ b/include/dice/sparse-map/sparse_set.hpp @@ -79,13 +79,13 @@ namespace dice::sparse_map { * the iterators. * - erase: always invalidate the iterators. */ - template, - class KeyEqual = std::equal_to, - class Allocator = std::allocator, - class GrowthPolicy = dice::sparse_map::sh::power_of_two_growth_policy<2>, - dice::sparse_map::sh::exception_safety ExceptionSafety = - dice::sparse_map::sh::exception_safety::basic, - dice::sparse_map::sh::sparsity Sparsity = dice::sparse_map::sh::sparsity::medium> + template, + typename KeyEqual = std::equal_to, + typename Allocator = std::allocator, + growth_policy GrowthPolicy = power_of_two_growth_policy<2>, + exception_safety ExceptionSafety = exception_safety::basic, + sparsity Sparsity = sparsity::medium> class sparse_set { static constexpr bool key_equal_is_transparent = requires { typename KeyEqual::is_transparent; @@ -104,9 +104,9 @@ namespace dice::sparse_map { } }; - using ht = detail_sparse_hash::sparse_hash; + using ht = detail::sparse_hash; public: using key_type = typename ht::key_type; @@ -534,11 +534,11 @@ namespace dice::sparse_map { * Same as `dice::sparse_set`. */ - template, - class KeyEqual = std::equal_to, - class Allocator = std::allocator> - using sparse_pg_set = - sparse_set; + template, + typename KeyEqual = std::equal_to, + typename Allocator = std::allocator> + using sparse_pg_set = sparse_set; }// namespace dice::sparse_map diff --git a/tests/fancy_pointer/sparse_array_tests.cpp b/tests/fancy_pointer/sparse_array_tests.cpp index f35666a..29d6329 100644 --- a/tests/fancy_pointer/sparse_array_tests.cpp +++ b/tests/fancy_pointer/sparse_array_tests.cpp @@ -96,18 +96,18 @@ void const_iterator() { /* * This are the types you can give the tests as template parameters. */ -template +template struct STD { using Allocator = std::allocator; - using Array = dice::sparse_map::detail_sparse_hash::sparse_array, Sparsity>; + using Array = dice::sparse_map::detail::sparse_array, Sparsity>; using Const_Iterator = T const*; using Value_Type = T; }; -template +template struct CUSTOM { using Allocator = OffsetAllocator; - using Array = dice::sparse_map::detail_sparse_hash::sparse_array, Sparsity>; + using Array = dice::sparse_map::detail::sparse_array, Sparsity>; using Const_Iterator = boost::interprocess::offset_ptr; using Value_Type = T; }; diff --git a/tests/fancy_pointer/sparse_hash_map_tests.cpp b/tests/fancy_pointer/sparse_hash_map_tests.cpp index 34696c7..ab161c7 100644 --- a/tests/fancy_pointer/sparse_hash_map_tests.cpp +++ b/tests/fancy_pointer/sparse_hash_map_tests.cpp @@ -46,12 +46,12 @@ namespace details { template - using sparse_map= dice::sparse_map::detail_sparse_hash::sparse_hash< + using sparse_map = dice::sparse_map::detail::sparse_hash< std::pair, KeyValueSelect, std::hash, std::equal_to, Alloc, - dice::sparse_map::sh::power_of_two_growth_policy<2>, - dice::sparse_map::sh::exception_safety::basic, - dice::sparse_map::sh::sparsity::medium, - dice::sparse_map::sh::probing::quadratic>; + dice::sparse_map::power_of_two_growth_policy<2>, + dice::sparse_map::exception_safety::basic, + dice::sparse_map::sparsity::medium, + dice::sparse_map::probing::quadratic>; template typename T::Map default_construct_map() { diff --git a/tests/fancy_pointer/sparse_hash_set_tests.cpp b/tests/fancy_pointer/sparse_hash_set_tests.cpp index ea1db12..46ffbb8 100644 --- a/tests/fancy_pointer/sparse_hash_set_tests.cpp +++ b/tests/fancy_pointer/sparse_hash_set_tests.cpp @@ -21,12 +21,12 @@ namespace details { }; template - using sparse_set = dice::sparse_map::detail_sparse_hash::sparse_hash< + using sparse_set = dice::sparse_map::detail::sparse_hash< T, KeySelect, std::hash, std::equal_to, Alloc, - dice::sparse_map::sh::power_of_two_growth_policy<2>, - dice::sparse_map::sh::exception_safety::basic, - dice::sparse_map::sh::sparsity::medium, - dice::sparse_map::sh::probing::quadratic>; + dice::sparse_map::power_of_two_growth_policy<2>, + dice::sparse_map::exception_safety::basic, + dice::sparse_map::sparsity::medium, + dice::sparse_map::probing::quadratic>; template typename T::Set default_construct_set() { diff --git a/tests/policy_tests.cpp b/tests/policy_tests.cpp index 74996c7..7866b2e 100644 --- a/tests/policy_tests.cpp +++ b/tests/policy_tests.cpp @@ -33,10 +33,11 @@ BOOST_AUTO_TEST_SUITE(test_policy) using test_types = - boost::mpl::list, - dice::sparse_map::sh::power_of_two_growth_policy<4>, - dice::sparse_map::sh::prime_growth_policy, dice::sparse_map::sh::mod_growth_policy<>, - dice::sparse_map::sh::mod_growth_policy>>; + boost::mpl::list, + dice::sparse_map::power_of_two_growth_policy<4>, + dice::sparse_map::prime_growth_policy, + dice::sparse_map::mod_growth_policy<>, + dice::sparse_map::mod_growth_policy>>; BOOST_AUTO_TEST_CASE_TEMPLATE(test_policy, Policy, test_types) { // Call next_bucket_count() on the policy until we reach its diff --git a/tests/scoped_allocator_adaptor/sparse_array_tests.cpp b/tests/scoped_allocator_adaptor/sparse_array_tests.cpp index 93ce947..5bcaf57 100644 --- a/tests/scoped_allocator_adaptor/sparse_array_tests.cpp +++ b/tests/scoped_allocator_adaptor/sparse_array_tests.cpp @@ -83,18 +83,18 @@ template void is_default_insertable() { std::allocator_traits::deallocate(m, p, 1); } -template +template struct NORMAL { using value_type = T; using Allocator = std::allocator; - using Array = dice::sparse_map::detail_sparse_hash::sparse_array; + using Array = dice::sparse_map::detail::sparse_array; }; -template +template struct SCOPED { using value_type = T; using Allocator = std::scoped_allocator_adaptor>; - using Array = dice::sparse_map::detail_sparse_hash::sparse_array; + using Array = dice::sparse_map::detail::sparse_array; }; BOOST_AUTO_TEST_SUITE(scoped_allocators) diff --git a/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp b/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp index 44d55f2..2ea487e 100644 --- a/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp +++ b/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp @@ -10,12 +10,12 @@ template struct KeySelect { }; template -using sparse_set = dice::sparse_map::detail_sparse_hash::sparse_hash< +using sparse_set = dice::sparse_map::detail::sparse_hash< T, details::KeySelect, std::hash, std::equal_to, Alloc, - dice::sparse_map::sh::power_of_two_growth_policy<2>, - dice::sparse_map::sh::exception_safety::basic, - dice::sparse_map::sh::sparsity::medium, - dice::sparse_map::sh::probing::quadratic>; + dice::sparse_map::power_of_two_growth_policy<2>, + dice::sparse_map::exception_safety::basic, + dice::sparse_map::sparsity::medium, + dice::sparse_map::probing::quadratic>; } // namespace details template void construction() { diff --git a/tests/sparse_map_tests.cpp b/tests/sparse_map_tests.cpp index 776a8df..7443320 100644 --- a/tests/sparse_map_tests.cpp +++ b/tests/sparse_map_tests.cpp @@ -56,41 +56,42 @@ using test_types = boost::mpl::list< dice::sparse_map::sparse_map, std::equal_to, std::allocator>, - dice::sparse_map::sh::power_of_two_growth_policy<4>>, + dice::sparse_map::power_of_two_growth_policy<4>>, dice::sparse_map::sparse_pg_map>, dice::sparse_map::sparse_map, std::equal_to, std::allocator>, - dice::sparse_map::sh::mod_growth_policy<>>, + dice::sparse_map::mod_growth_policy<>>, dice::sparse_map::sparse_map, std::equal_to, std::allocator>, - dice::sparse_map::sh::power_of_two_growth_policy<4>>, + dice::sparse_map::power_of_two_growth_policy<4>>, dice::sparse_map::sparse_pg_map>, dice::sparse_map::sparse_map, std::equal_to, std::allocator>, - dice::sparse_map::sh::mod_growth_policy<>>, + dice::sparse_map::mod_growth_policy<>>, // Strong exception guarantee dice::sparse_map::sparse_map, std::equal_to, std::allocator>, - dice::sparse_map::sh::power_of_two_growth_policy<2>, - dice::sparse_map::sh::exception_safety::strong>, + dice::sparse_map::power_of_two_growth_policy<2>, + dice::sparse_map::exception_safety::strong>, // Others sparsity dice::sparse_map::sparse_map, std::equal_to, std::allocator>, - dice::sparse_map::sh::power_of_two_growth_policy<2>, - dice::sparse_map::sh::exception_safety::basic, dice::sparse_map::sh::sparsity::high>, + dice::sparse_map::power_of_two_growth_policy<2>, + dice::sparse_map::exception_safety::basic, + dice::sparse_map::sparsity::high>, dice::sparse_map::sparse_map, std::equal_to, std::allocator>, - dice::sparse_map::sh::power_of_two_growth_policy<2>, - dice::sparse_map::sh::exception_safety::basic, dice::sparse_map::sh::sparsity::low>>; + dice::sparse_map::power_of_two_growth_policy<2>, + dice::sparse_map::exception_safety::basic, dice::sparse_map::sparsity::low>>; /** * insert @@ -104,12 +105,9 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_insert, HMap, test_types) { HMap map(0); BOOST_CHECK_EQUAL(map.bucket_count(), 0); - typename HMap::iterator it; - bool inserted; for (std::size_t i = 0; i < nb_values; i++) { - std::tie(it, inserted) = - map.insert({utils::get_key(i), utils::get_value(i)}); + auto [it, inserted] = map.insert({utils::get_key(i), utils::get_value(i)}); BOOST_CHECK_EQUAL(it->first, utils::get_key(i)); BOOST_CHECK_EQUAL(it->second, utils::get_value(i)); @@ -118,8 +116,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_insert, HMap, test_types) { BOOST_CHECK_EQUAL(map.size(), nb_values); for (std::size_t i = 0; i < nb_values; i++) { - std::tie(it, inserted) = map.insert( - {utils::get_key(i), utils::get_value(i + 1)}); + auto [it, inserted] = map.insert({utils::get_key(i), utils::get_value(i + 1)}); BOOST_CHECK_EQUAL(it->first, utils::get_key(i)); BOOST_CHECK_EQUAL(it->second, utils::get_value(i)); @@ -127,7 +124,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_insert, HMap, test_types) { } for (std::size_t i = 0; i < nb_values; i++) { - it = map.find(utils::get_key(i)); + auto it = map.find(utils::get_key(i)); BOOST_CHECK_EQUAL(it->first, utils::get_key(i)); BOOST_CHECK_EQUAL(it->second, utils::get_value(i)); @@ -225,10 +222,8 @@ BOOST_AUTO_TEST_CASE(test_emplace_hint) { */ BOOST_AUTO_TEST_CASE(test_emplace) { dice::sparse_map::sparse_map map; - dice::sparse_map::sparse_map::iterator it; - bool inserted; - std::tie(it, inserted) = + auto [it, inserted] = map.emplace(std::piecewise_construct, std::forward_as_tuple(10), std::forward_as_tuple(1)); BOOST_CHECK_EQUAL(it->first, 10); @@ -248,10 +243,8 @@ BOOST_AUTO_TEST_CASE(test_emplace) { */ BOOST_AUTO_TEST_CASE(test_try_emplace) { dice::sparse_map::sparse_map map; - dice::sparse_map::sparse_map::iterator it; - bool inserted; - std::tie(it, inserted) = map.try_emplace(10, 1); + auto [it, inserted] = map.try_emplace(10, 1); BOOST_CHECK_EQUAL(it->first, 10); BOOST_CHECK_EQUAL(it->second, move_only_test(1)); BOOST_CHECK(inserted); @@ -265,12 +258,10 @@ BOOST_AUTO_TEST_CASE(test_try_emplace) { BOOST_AUTO_TEST_CASE(test_try_emplace_2) { // Insert x values with try_emplace, insert them again, check with find. dice::sparse_map::sparse_map map; - dice::sparse_map::sparse_map::iterator it; - bool inserted; const std::size_t nb_values = 1000; for (std::size_t i = 0; i < nb_values; i++) { - std::tie(it, inserted) = map.try_emplace(utils::get_key(i), i); + auto [it, inserted] = map.try_emplace(utils::get_key(i), i); BOOST_CHECK_EQUAL(it->first, utils::get_key(i)); BOOST_CHECK_EQUAL(it->second, move_only_test(i)); @@ -279,8 +270,7 @@ BOOST_AUTO_TEST_CASE(test_try_emplace_2) { BOOST_CHECK_EQUAL(map.size(), nb_values); for (std::size_t i = 0; i < nb_values; i++) { - std::tie(it, inserted) = - map.try_emplace(utils::get_key(i), i + 1); + auto [it, inserted] = map.try_emplace(utils::get_key(i), i + 1); BOOST_CHECK_EQUAL(it->first, utils::get_key(i)); BOOST_CHECK_EQUAL(it->second, move_only_test(i)); @@ -288,7 +278,7 @@ BOOST_AUTO_TEST_CASE(test_try_emplace_2) { } for (std::size_t i = 0; i < nb_values; i++) { - it = map.find(utils::get_key(i)); + auto it = map.find(utils::get_key(i)); BOOST_CHECK_EQUAL(it->first, utils::get_key(i)); BOOST_CHECK_EQUAL(it->second, move_only_test(i)); @@ -319,10 +309,8 @@ BOOST_AUTO_TEST_CASE(test_try_emplace_hint) { */ BOOST_AUTO_TEST_CASE(test_insert_or_assign) { dice::sparse_map::sparse_map map; - dice::sparse_map::sparse_map::iterator it; - bool inserted; - std::tie(it, inserted) = map.insert_or_assign(10, move_only_test(1)); + auto [it, inserted] = map.insert_or_assign(10, move_only_test(1)); BOOST_CHECK_EQUAL(it->first, 10); BOOST_CHECK_EQUAL(it->second, move_only_test(1)); BOOST_CHECK(inserted); @@ -437,13 +425,10 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_insert_erase_insert, HMap, test_types) { const std::size_t nb_values = 2000; HMap map(10); - typename HMap::iterator it; - bool inserted; // Insert nb_values/2 for (std::size_t i = 0; i < nb_values / 2; i++) { - std::tie(it, inserted) = - map.insert({utils::get_key(i), utils::get_value(i)}); + auto [it, inserted] = map.insert({utils::get_key(i), utils::get_value(i)}); BOOST_CHECK_EQUAL(it->first, utils::get_key(i)); BOOST_CHECK_EQUAL(it->second, utils::get_value(i)); @@ -461,8 +446,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_insert_erase_insert, HMap, test_types) { // Insert nb_values/2 for (std::size_t i = nb_values / 2; i < nb_values; i++) { - std::tie(it, inserted) = - map.insert({utils::get_key(i), utils::get_value(i)}); + auto [it, inserted] = map.insert({utils::get_key(i), utils::get_value(i)}); BOOST_CHECK_EQUAL(it->first, utils::get_key(i)); BOOST_CHECK_EQUAL(it->second, utils::get_value(i)); @@ -473,11 +457,11 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_insert_erase_insert, HMap, test_types) { // Find for (std::size_t i = 0; i < nb_values; i++) { if (i % 2 == 0 && i < nb_values / 2) { - it = map.find(utils::get_key(i)); + auto it = map.find(utils::get_key(i)); BOOST_CHECK(it == map.end()); } else { - it = map.find(utils::get_key(i)); + auto it = map.find(utils::get_key(i)); BOOST_REQUIRE(it != map.end()); BOOST_CHECK_EQUAL(it->first, utils::get_key(i)); @@ -638,35 +622,35 @@ BOOST_AUTO_TEST_CASE(test_extreme_bucket_count_value_construction) { BOOST_CHECK_THROW( (dice::sparse_map::sparse_map, std::equal_to, std::allocator>, - dice::sparse_map::sh::power_of_two_growth_policy<2>>( + dice::sparse_map::power_of_two_growth_policy<2>>( std::numeric_limits::max())), std::length_error); BOOST_CHECK_THROW( (dice::sparse_map::sparse_map, std::equal_to, std::allocator>, - dice::sparse_map::sh::power_of_two_growth_policy<2>>( + dice::sparse_map::power_of_two_growth_policy<2>>( std::numeric_limits::max() / 2 + 1)), std::length_error); BOOST_CHECK_THROW( (dice::sparse_map::sparse_map, std::equal_to, std::allocator>, - dice::sparse_map::sh::prime_growth_policy>( + dice::sparse_map::prime_growth_policy>( std::numeric_limits::max())), std::length_error); BOOST_CHECK_THROW( (dice::sparse_map::sparse_map, std::equal_to, std::allocator>, - dice::sparse_map::sh::prime_growth_policy>( + dice::sparse_map::prime_growth_policy>( std::numeric_limits::max() / 2)), std::length_error); BOOST_CHECK_THROW( (dice::sparse_map::sparse_map, std::equal_to, std::allocator>, - dice::sparse_map::sh::mod_growth_policy<>>( + dice::sparse_map::mod_growth_policy<>>( std::numeric_limits::max())), std::length_error); } diff --git a/tests/sparse_set_tests.cpp b/tests/sparse_set_tests.cpp index ca09b19..50ccd14 100644 --- a/tests/sparse_set_tests.cpp +++ b/tests/sparse_set_tests.cpp @@ -46,16 +46,16 @@ using test_types = dice::sparse_map::sparse_set, std::equal_to, std::allocator, - dice::sparse_map::sh::prime_growth_policy>, + dice::sparse_map::prime_growth_policy>, dice::sparse_map::sparse_set, std::equal_to, std::allocator, - dice::sparse_map::sh::mod_growth_policy<>>, + dice::sparse_map::mod_growth_policy<>>, dice::sparse_map::sparse_set, std::equal_to, std::allocator, - dice::sparse_map::sh::mod_growth_policy<>>>; + dice::sparse_map::mod_growth_policy<>>>; BOOST_AUTO_TEST_CASE_TEMPLATE(test_insert, HSet, test_types) { // insert x values, insert them again, check values @@ -63,11 +63,9 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_insert, HSet, test_types) { const std::size_t nb_values = 1000; HSet set; - typename HSet::iterator it; - bool inserted; for (std::size_t i = 0; i < nb_values; i++) { - std::tie(it, inserted) = set.insert(utils::get_key(i)); + auto [it, inserted] = set.insert(utils::get_key(i)); BOOST_CHECK_EQUAL(*it, utils::get_key(i)); BOOST_CHECK(inserted); @@ -75,14 +73,14 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_insert, HSet, test_types) { BOOST_CHECK_EQUAL(set.size(), nb_values); for (std::size_t i = 0; i < nb_values; i++) { - std::tie(it, inserted) = set.insert(utils::get_key(i)); + auto [it, inserted] = set.insert(utils::get_key(i)); BOOST_CHECK_EQUAL(*it, utils::get_key(i)); BOOST_CHECK(!inserted); } for (std::size_t i = 0; i < nb_values; i++) { - it = set.find(utils::get_key(i)); + auto it = set.find(utils::get_key(i)); BOOST_CHECK_EQUAL(*it, utils::get_key(i)); } From ba927ddcbb8e93b23d7d350de274923b6fd9171d Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Wed, 2 Aug 2023 11:56:29 +0200 Subject: [PATCH 07/41] cleanup policies --- include/dice/sparse-map/sparse_array.hpp | 7 - .../dice/sparse-map/sparse_growth_policy.hpp | 206 +++++++----------- include/dice/sparse-map/sparse_props.hpp | 16 +- 3 files changed, 90 insertions(+), 139 deletions(-) diff --git a/include/dice/sparse-map/sparse_array.hpp b/include/dice/sparse-map/sparse_array.hpp index ddf838f..dfe453a 100644 --- a/include/dice/sparse-map/sparse_array.hpp +++ b/include/dice/sparse-map/sparse_array.hpp @@ -5,13 +5,6 @@ namespace dice::sparse_map::detail { - template - constexpr U round_up_to_power_of_2(U value) { - assert(value > 0); - auto const highest_bit_pos = sizeof(U) * 8 - std::countl_zero(value - 1); - return U{1} << highest_bit_pos; - } - /** * WARNING: the sparse_array class doesn't free the ressources allocated through * the allocator passed in parameter in each method. You have to manually call diff --git a/include/dice/sparse-map/sparse_growth_policy.hpp b/include/dice/sparse-map/sparse_growth_policy.hpp index 43a1658..faf10a8 100644 --- a/include/dice/sparse-map/sparse_growth_policy.hpp +++ b/include/dice/sparse-map/sparse_growth_policy.hpp @@ -26,15 +26,15 @@ #include #include -#include #include #include #include -#include #include #include #include +#include "dice/sparse-map/sparse_props.hpp" + namespace dice::sparse_map { template @@ -42,10 +42,11 @@ namespace dice::sparse_map { G{min_bucket_count_in_out}; { cgpol.bucket_for_hash(hash) } -> std::convertible_to; { cgpol.next_bucket_count() } -> std::convertible_to; - { cgpol.max_bucket_count() } -> std::convertible_to; + { G::max_bucket_count() } -> std::convertible_to; gpol.clear(); noexcept(cgpol.bucket_for_hash(hash)); + noexcept(G::max_bucket_count()); noexcept(gpol.clear()); }; @@ -56,8 +57,11 @@ namespace dice::sparse_map { * * GrowthFactor must be a power of two >= 2. */ - template - class power_of_two_growth_policy { + template requires (detail::is_power_of_2(GrowthFactor) && GrowthFactor >= 2) + struct power_of_two_growth_policy { + protected: + std::size_t m_mask; + public: /** * Called on the hash table creation and on rehash. The number of buckets for @@ -67,13 +71,13 @@ namespace dice::sparse_map { * If 0 is given, min_bucket_count_in_out must still be 0 after the policy * creation and bucket_for_hash must always return 0 in this case. */ - explicit power_of_two_growth_policy(std::size_t &min_bucket_count_in_out) { - if (min_bucket_count_in_out > max_bucket_count()) { - throw std::length_error("The hash table exceeds its maximum size."); + explicit constexpr power_of_two_growth_policy(std::size_t &min_bucket_count_in_out) { + if (min_bucket_count_in_out > max_bucket_count()) [[unlikely]] { + throw std::length_error{"The hash table exceeds its maximum size."}; } if (min_bucket_count_in_out > 0) { - min_bucket_count_in_out = round_up_to_power_of_two(min_bucket_count_in_out); + min_bucket_count_in_out = detail::round_up_to_power_of_2(min_bucket_count_in_out); m_mask = min_bucket_count_in_out - 1; } else { m_mask = 0; @@ -84,15 +88,15 @@ namespace dice::sparse_map { * Return the bucket [0, bucket_count()) to which the hash belongs. * If bucket_count() is 0, it must always return 0. */ - std::size_t bucket_for_hash(std::size_t hash) const noexcept { + [[nodiscard]] constexpr std::size_t bucket_for_hash(std::size_t hash) const noexcept { return hash & m_mask; } /** * Return the number of buckets that should be used on next growth. */ - std::size_t next_bucket_count() const { - if ((m_mask + 1) > max_bucket_count() / GrowthFactor) { + [[nodiscard]] constexpr std::size_t next_bucket_count() const { + if ((m_mask + 1) > max_bucket_count() / GrowthFactor) [[unlikely]] { throw std::length_error("The hash table exceeds its maximum size."); } @@ -102,7 +106,7 @@ namespace dice::sparse_map { /** * Return the maximum number of buckets supported by the policy. */ - std::size_t max_bucket_count() const { + [[nodiscard]] static constexpr std::size_t max_bucket_count() noexcept { // Largest power of two. return (std::numeric_limits::max() / 2) + 1; } @@ -112,39 +116,13 @@ namespace dice::sparse_map { * After a clear, the policy must always return 0 when bucket_for_hash is * called. */ - void clear() noexcept { m_mask = 0; } - - std::size_t mask() const noexcept { - return m_mask; - } - - private: - static std::size_t round_up_to_power_of_two(std::size_t value) { - if (is_power_of_two(value)) { - return value; - } - - if (value == 0) { - return 1; - } - - --value; - for (std::size_t i = 1; i < sizeof(std::size_t) * CHAR_BIT; i *= 2) { - value |= value >> i; - } - - return value + 1; + constexpr void clear() noexcept { + m_mask = 0; } - static constexpr bool is_power_of_two(std::size_t value) { - return value != 0 && (value & (value - 1)) == 0; + [[nodiscard]] constexpr std::size_t mask() const noexcept { + return m_mask; } - - protected: - static_assert(is_power_of_two(GrowthFactor) && GrowthFactor >= 2, - "GrowthFactor must be a power of two >= 2."); - - std::size_t m_mask; }; /** @@ -152,12 +130,20 @@ namespace dice::sparse_map { * to map a hash to a bucket. Slower but it can be useful if you want a slower * growth. */ - template> - class mod_growth_policy { + template> + struct mod_growth_policy { + protected: + static constexpr double REHASH_SIZE_MULTIPLICATION_FACTOR = 1.0 * GrowthFactor::num / GrowthFactor::den; + static_assert(REHASH_SIZE_MULTIPLICATION_FACTOR >= 1.1, "Growth factor should be >= 1.1."); + + static constexpr std::size_t MAX_BUCKET_COUNT = static_cast(static_cast(std::numeric_limits::max()) / REHASH_SIZE_MULTIPLICATION_FACTOR); + + std::size_t m_mod; + public: - explicit mod_growth_policy(std::size_t &min_bucket_count_in_out) { - if (min_bucket_count_in_out > max_bucket_count()) { - throw std::length_error("The hash table exceeds its maximum size."); + explicit constexpr mod_growth_policy(std::size_t &min_bucket_count_in_out) { + if (min_bucket_count_in_out > max_bucket_count()) [[unlikely]] { + throw std::length_error{"The hash table exceeds its maximum size."}; } if (min_bucket_count_in_out > 0) { @@ -167,43 +153,34 @@ namespace dice::sparse_map { } } - std::size_t bucket_for_hash(std::size_t hash) const noexcept { + [[nodiscard]] constexpr std::size_t bucket_for_hash(std::size_t hash) const noexcept { return hash % m_mod; } - std::size_t next_bucket_count() const { - if (m_mod == max_bucket_count()) { - throw std::length_error("The hash table exceeds its maximum size."); + [[nodiscard]] constexpr std::size_t next_bucket_count() const { + if (m_mod == max_bucket_count()) [[unlikely]] { + throw std::length_error{"The hash table exceeds its maximum size."}; } - const double next_bucket_count = - std::ceil(double(m_mod) * REHASH_SIZE_MULTIPLICATION_FACTOR); - if (!std::isnormal(next_bucket_count)) { - throw std::length_error("The hash table exceeds its maximum size."); + auto const next_bucket_count = std::ceil(static_cast(m_mod) * REHASH_SIZE_MULTIPLICATION_FACTOR); + if (!std::isnormal(next_bucket_count)) [[unlikely]] { + throw std::length_error{"The hash table exceeds its maximum size."}; } - if (next_bucket_count > double(max_bucket_count())) { + if (next_bucket_count > static_cast(max_bucket_count())) { return max_bucket_count(); - } else { - return std::size_t(next_bucket_count); } - } - std::size_t max_bucket_count() const { return MAX_BUCKET_COUNT; } - - void clear() noexcept { m_mod = 1; } - - private: - static constexpr double REHASH_SIZE_MULTIPLICATION_FACTOR = - 1.0 * GrowthFactor::num / GrowthFactor::den; - static const std::size_t MAX_BUCKET_COUNT = - std::size_t(double(std::numeric_limits::max() / - REHASH_SIZE_MULTIPLICATION_FACTOR)); + return static_cast(next_bucket_count); + } - static_assert(REHASH_SIZE_MULTIPLICATION_FACTOR >= 1.1, - "Growth factor should be >= 1.1."); + [[nodiscard]] static constexpr std::size_t max_bucket_count() noexcept { + return MAX_BUCKET_COUNT; + } - std::size_t m_mod; + void clear() noexcept { + m_mod = 1; + } }; /** @@ -233,17 +210,28 @@ namespace dice::sparse_map { * The 'hash % 5' could become something like 'hash - (hash * 0xCCCCCCCD) >> 34) * * 5' in a 64 bits environment. */ - class prime_growth_policy { + struct prime_growth_policy { + protected: + static constexpr std::array PRIMES{1ul, 5ul, 17ul, 29ul, 37ul, + 53ul, 67ul, 79ul, 97ul, 131ul, + 193ul, 257ul, 389ul, 521ul, 769ul, + 1031ul, 1543ul, 2053ul, 3079ul, 6151ul, + 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, + 393241ul, 786433ul, 1572869ul, 3145739ul, 6291469ul, + 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, + 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul}; + + std::uint8_t m_iprime; + public: - explicit prime_growth_policy(std::size_t &min_bucket_count_in_out) { - auto it_prime = std::lower_bound(primes().begin(), primes().end(), - min_bucket_count_in_out); - if (it_prime == primes().end()) { - throw std::length_error("The hash table exceeds its maximum size."); + explicit constexpr prime_growth_policy(std::size_t &min_bucket_count_in_out) { + auto it_prime = std::lower_bound(PRIMES.begin(), PRIMES.end(), min_bucket_count_in_out); + if (it_prime == PRIMES.end()) [[unlikely]] { + throw std::length_error{"The hash table exceeds its maximum size."}; } - m_iprime = - static_cast(std::distance(primes().begin(), it_prime)); + m_iprime = static_cast(std::distance(PRIMES.begin(), it_prime)); + if (min_bucket_count_in_out > 0) { min_bucket_count_in_out = *it_prime; } else { @@ -251,63 +239,25 @@ namespace dice::sparse_map { } } - std::size_t bucket_for_hash(std::size_t hash) const noexcept { - return mod_prime()[m_iprime](hash); + [[nodiscard]] constexpr std::size_t bucket_for_hash(std::size_t hash) const noexcept { + return hash % PRIMES[m_iprime]; } - std::size_t next_bucket_count() const { - if (m_iprime + 1 >= primes().size()) { + [[nodiscard]] constexpr std::size_t next_bucket_count() const { + if (m_iprime + 1 >= PRIMES.size()) { throw std::length_error("The hash table exceeds its maximum size."); } - return primes()[m_iprime + 1]; - } - - std::size_t max_bucket_count() const { return primes().back(); } - - void clear() noexcept { m_iprime = 0; } - - private: - static const std::array &primes() { - static const std::array PRIMES = { - {1ul, 5ul, 17ul, 29ul, 37ul, - 53ul, 67ul, 79ul, 97ul, 131ul, - 193ul, 257ul, 389ul, 521ul, 769ul, - 1031ul, 1543ul, 2053ul, 3079ul, 6151ul, - 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, - 393241ul, 786433ul, 1572869ul, 3145739ul, 6291469ul, - 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, - 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul}}; - - static_assert( - std::numeric_limits::max() >= PRIMES.size(), - "The type of m_iprime is not big enough."); - - return PRIMES; + return PRIMES[m_iprime + 1]; } - static const std::array &mod_prime() { - // MOD_PRIME[iprime](hash) returns hash % PRIMES[iprime]. This table allows - // for faster modulo as the compiler can optimize the modulo code better - // with a constant known at the compilation. - static const std::array MOD_PRIME = { - {&mod<0>, &mod<1>, &mod<2>, &mod<3>, &mod<4>, &mod<5>, &mod<6>, - &mod<7>, &mod<8>, &mod<9>, &mod<10>, &mod<11>, &mod<12>, &mod<13>, - &mod<14>, &mod<15>, &mod<16>, &mod<17>, &mod<18>, &mod<19>, &mod<20>, - &mod<21>, &mod<22>, &mod<23>, &mod<24>, &mod<25>, &mod<26>, &mod<27>, - &mod<28>, &mod<29>, &mod<30>, &mod<31>, &mod<32>, &mod<33>, &mod<34>, - &mod<35>, &mod<36>, &mod<37>, &mod<38>, &mod<39>}}; - - return MOD_PRIME; + [[nodiscard]] static constexpr std::size_t max_bucket_count() noexcept { + return PRIMES.back(); } - template - static std::size_t mod(std::size_t hash) { - return hash % primes()[IPrime]; + constexpr void clear() noexcept { + m_iprime = 0; } - - private: - unsigned int m_iprime; }; }// namespace dice::sparse_map diff --git a/include/dice/sparse-map/sparse_props.hpp b/include/dice/sparse-map/sparse_props.hpp index 5aa65d9..666cf9c 100644 --- a/include/dice/sparse-map/sparse_props.hpp +++ b/include/dice/sparse-map/sparse_props.hpp @@ -2,27 +2,35 @@ #define DICE_SPARSE_MAP_SPARSE_PROPS_HPP #include +#include #include namespace dice::sparse_map { namespace detail { template - constexpr bool is_power_of_2(U x) { + constexpr bool is_power_of_2(U x) noexcept { return std::popcount(x) == 1; } + + template + constexpr U round_up_to_power_of_2(U value) noexcept { + assert(value > 0); + auto const highest_bit_pos = sizeof(U) * 8 - std::countl_zero(value - 1); + return U{1} << highest_bit_pos; + } } // namespace detail - enum class probing { + enum struct probing : bool { linear, quadratic }; - enum class exception_safety { + enum struct exception_safety : bool { basic, strong }; - enum class sparsity { + enum struct sparsity : uint8_t { high, medium, low From 2cdf1a5680abfa770c5746931c656297487ef09e Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Wed, 2 Aug 2023 12:26:51 +0200 Subject: [PATCH 08/41] revert some things in sparse array --- include/dice/sparse-map/sparse_array.hpp | 67 +++++++++++-------- .../sparse_array_tests.cpp | 20 +++--- .../sparse_hash_set_tests.cpp | 31 ++++++--- 3 files changed, 71 insertions(+), 47 deletions(-) diff --git a/include/dice/sparse-map/sparse_array.hpp b/include/dice/sparse-map/sparse_array.hpp index dfe453a..d6ec529 100644 --- a/include/dice/sparse-map/sparse_array.hpp +++ b/include/dice/sparse-map/sparse_array.hpp @@ -36,18 +36,18 @@ namespace dice::sparse_map::detail { */ template struct sparse_array { + private: + using alloc_traits = std::allocator_traits; + + public: using value_type = T; using size_type = std::uint_least8_t; using allocator_type = Allocator; - using allocator_traits = std::allocator_traits; - using pointer = typename allocator_traits::pointer; - using const_pointer = typename allocator_traits::const_pointer; + using pointer = typename alloc_traits::pointer; + using const_pointer = typename alloc_traits::const_pointer; using iterator = pointer; using const_iterator = const_pointer; - private: - using alloc_traits = std::allocator_traits; - static constexpr size_type CAPACITY_GROWTH_STEP = []() { switch (Sparsity) { case sparsity::high: return 2; @@ -62,7 +62,7 @@ namespace dice::sparse_map::detail { static constexpr std::size_t BUCKET_MASK = BITMAP_NB_BITS - 1; - static_assert(std::popcount(BITMAP_NB_BITS) == 1, + static_assert(is_power_of_2(BITMAP_NB_BITS) == 1, "BITMAP_NB_BITS must be a power of two."); static_assert(std::numeric_limits::digits >= BITMAP_NB_BITS, "bitmap_type must be able to hold at least BITMAP_NB_BITS."); @@ -118,6 +118,15 @@ namespace dice::sparse_map::detail { return std::max(1, sparse_ibucket(round_up_to_power_of_2(bucket_count))); } + template + static void construct_at(allocator_type &alloc, pointer p, Args &&...args) noexcept(std::is_nothrow_constructible_v) { + alloc_traits::construct(alloc, std::to_address(p), std::forward(args)...); + } + + static void destroy_at(allocator_type &alloc, pointer p) noexcept(std::is_nothrow_destructible_v) { + alloc_traits::destroy(alloc, std::to_address(p)); + } + public: constexpr sparse_array() noexcept = default; @@ -171,7 +180,7 @@ namespace dice::sparse_map::detail { try { for (; m_nb_elements < other.m_nb_elements; ++m_nb_elements) { - new (&m_values[m_nb_elements]) value_type{other.m_values[m_nb_elements]}; + construct_at(alloc, &m_values[m_nb_elements], other.m_values[m_nb_elements]); } } catch (...) { clear(alloc); @@ -197,7 +206,7 @@ namespace dice::sparse_map::detail { try { for (size_type i = 0; i < other.m_nb_elements; i++) { - new (&m_values[i]) value_type{std::move(other.m_values[i])}; + construct_at(alloc, &m_values[i], std::move(other.m_values[i])); m_nb_elements++; } } catch (...) { @@ -343,7 +352,7 @@ namespace dice::sparse_map::detail { size_type nb_values, size_type capacity_values) noexcept { for (size_type i = 0; i < nb_values; i++) { - values[i].~value_type(); + destroy_at(alloc, &values[i]); } alloc_traits::deallocate(alloc, values, capacity_values); @@ -406,7 +415,7 @@ namespace dice::sparse_map::detail { template requires (std::is_nothrow_move_constructible_v) void insert_at_offset(allocator_type &alloc, size_type offset, Args &&...value_args) { if (m_nb_elements < m_capacity) { - insert_at_offset_no_realloc(offset, std::forward(value_args)...); + insert_at_offset_no_realloc(alloc, offset, std::forward(value_args)...); } else { insert_at_offset_realloc(alloc, offset, next_capacity(), std::forward(value_args)...); } @@ -418,22 +427,22 @@ namespace dice::sparse_map::detail { } template requires (std::is_nothrow_move_constructible_v) - void insert_at_offset_no_realloc(size_type offset, Args &&...value_args) { + void insert_at_offset_no_realloc(allocator_type &alloc, size_type offset, Args &&...value_args) { assert(offset <= m_nb_elements); assert(m_nb_elements < m_capacity); for (size_type i = m_nb_elements; i > offset; i--) { - new (&m_values[i]) value_type{std::move(m_values[i - 1])}; - m_values[i - 1].~value_type(); + construct_at(alloc, &m_values[i], std::move(m_values[i - 1])); + destroy_at(alloc, &m_values[i - 1]); } try { - new (&m_values[offset]) value_type{std::forward(value_args)...}; + construct_at(alloc, &m_values[offset], std::forward(value_args)...); } catch (...) { // revert for (size_type i = offset; i < m_nb_elements; i++) { - new (&m_values[i]) value_type{std::move(m_values[i + 1])}; - m_values[i + 1].~value_type(); + construct_at(alloc, &m_values[i], std::move(m_values[i + 1])); + destroy_at(alloc, &m_values[i + 1]); } throw; } @@ -448,7 +457,7 @@ namespace dice::sparse_map::detail { assert(new_values != nullptr); // Allocate should throw if there is a failure try { - new (&new_values[offset]) value_type{std::forward(value_args)...}; + construct_at(alloc, &new_values[offset], std::forward(value_args)...); } catch (...) { alloc_traits::deallocate(alloc, new_values, new_capacity); throw; @@ -456,11 +465,11 @@ namespace dice::sparse_map::detail { // Should not throw from here for (size_type i = 0; i < offset; i++) { - new (&new_values[i]) value_type{std::move(m_values[i])}; + construct_at(alloc, &new_values[i], std::move(m_values[i])); } for (size_type i = offset; i < m_nb_elements; i++) { - new (&new_values[i + 1]) value_type{std::move(m_values[i])}; + construct_at(alloc, &new_values[i + 1], std::move(m_values[i])); } destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); @@ -479,15 +488,15 @@ namespace dice::sparse_map::detail { size_type nb_new_values = 0; try { for (size_type i = 0; i < offset; i++) { - new (&new_values[i]) value_type{m_values[i]}; + construct_at(alloc, &new_values[i], m_values[i]); nb_new_values++; } - new (&new_values[offset]) value_type{std::forward(value_args)...}; + construct_at(alloc, &new_values[offset], std::forward(value_args)...); nb_new_values++; for (size_type i = offset; i < m_nb_elements; i++) { - new (&new_values[i + 1]) value_type{m_values[i]}; + construct_at(alloc, &new_values[i + 1], m_values[i]); nb_new_values++; } } catch (...) { @@ -520,11 +529,11 @@ namespace dice::sparse_map::detail { void erase_at_offset([[maybe_unused]] allocator_type &alloc, size_type offset) noexcept { assert(offset < m_nb_elements); - m_values[offset].~value_type(); + destroy_at(alloc, &m_values[offset]); for (size_type i = offset + 1; i < m_nb_elements; ++i) { - new (&m_values[i - 1]) value_type{std::move(m_values[i])}; - m_values[i].~value_type(); + construct_at(alloc, &m_values[i - 1], std::move(m_values[i])); + destroy_at(alloc, &m_values[i]); } } @@ -534,7 +543,7 @@ namespace dice::sparse_map::detail { if (offset + 1 == m_nb_elements) { // Erasing the last element, don't need to reallocate. We keep the capacity. - m_values[offset].~value_type(); + destroy_at(alloc, &m_values[offset]); return; } @@ -547,12 +556,12 @@ namespace dice::sparse_map::detail { size_type nb_new_values = 0; try { for (size_type i = 0; i < offset; ++i) { - new (&new_values[i]) value_type{m_values[i]}; + construct_at(alloc, &new_values[i], m_values[i]); nb_new_values++; } for (size_type i = offset + 1; i < m_nb_elements; ++i) { - new (&new_values[i - 1]) value_type{m_values[i]}; + construct_at(alloc, &new_values[i - 1], m_values[i]); nb_new_values++; } } catch (...) { diff --git a/tests/scoped_allocator_adaptor/sparse_array_tests.cpp b/tests/scoped_allocator_adaptor/sparse_array_tests.cpp index 5bcaf57..57e4e5d 100644 --- a/tests/scoped_allocator_adaptor/sparse_array_tests.cpp +++ b/tests/scoped_allocator_adaptor/sparse_array_tests.cpp @@ -85,16 +85,16 @@ template void is_default_insertable() { template struct NORMAL { - using value_type = T; - using Allocator = std::allocator; - using Array = dice::sparse_map::detail::sparse_array; + using value_type = std::vector; + using Allocator = std::allocator>; + using Array = dice::sparse_map::detail::sparse_array, Allocator, Sparsity>; }; template struct SCOPED { - using value_type = T; - using Allocator = std::scoped_allocator_adaptor>; - using Array = dice::sparse_map::detail::sparse_array; + using value_type = std::vector; + using Allocator = std::scoped_allocator_adaptor>, std::allocator>; + using Array = dice::sparse_map::detail::sparse_array, Allocator, Sparsity>; }; BOOST_AUTO_TEST_SUITE(scoped_allocators) @@ -102,13 +102,13 @@ BOOST_AUTO_TEST_SUITE(sparse_array_tests) BOOST_AUTO_TEST_CASE(normal_compilation) { compilation>(); } BOOST_AUTO_TEST_CASE(normal_construction) { construction>(); } -BOOST_AUTO_TEST_CASE(normal_set) { set>({0, 1, 2, 3, 4}); } +BOOST_AUTO_TEST_CASE(normal_set) { set>({{0, 1, 2, 3, 4}}); } BOOST_AUTO_TEST_CASE(normal_uses_allocator) { uses_allocator>(); } BOOST_AUTO_TEST_CASE(normal_trailing_allocator_convention) { trailing_allocator_convention>(0); } BOOST_AUTO_TEST_CASE(normal_is_move_insertable) { - is_move_insertable>({0, 1, 2, 3, 4, 5}); + is_move_insertable>({{0, 1, 2, 3, 4, 5}}); } BOOST_AUTO_TEST_CASE(normal_is_default_insertable) { is_default_insertable>(); @@ -116,13 +116,13 @@ BOOST_AUTO_TEST_CASE(normal_is_default_insertable) { BOOST_AUTO_TEST_CASE(scoped_compilation) { compilation>(); } BOOST_AUTO_TEST_CASE(scoped_construction) { construction>(); } -BOOST_AUTO_TEST_CASE(scoped_set) { set>({0, 1, 2, 3, 4}); } +BOOST_AUTO_TEST_CASE(scoped_set) { set>({{0, 1, 2, 3, 4}, {1, 2, 3}}); } BOOST_AUTO_TEST_CASE(scoped_uses_allocator) { uses_allocator>(); } BOOST_AUTO_TEST_CASE(scoped_trailing_allocator_convention) { trailing_allocator_convention>(0); } BOOST_AUTO_TEST_CASE(scoped_is_move_insertable) { - is_move_insertable>({0, 1, 2, 3, 4, 5}); + is_move_insertable>({{0, 1, 2, 3, 4, 5}, {1, 2, 3}}); } BOOST_AUTO_TEST_CASE(scoped_is_default_insertable) { is_default_insertable>(); diff --git a/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp b/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp index 2ea487e..216d5f4 100644 --- a/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp +++ b/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp @@ -9,18 +9,33 @@ template struct KeySelect { key_type &operator()(Key &key) noexcept { return key; } }; +template +struct Hash { + std::size_t operator()(std::vector const &vec) const noexcept { + std::hash h; + std::size_t ret; + + for (auto const &e : vec) { + ret ^= h(e); + } + + return ret; + } +}; + template using sparse_set = dice::sparse_map::detail::sparse_hash< - T, details::KeySelect, std::hash, std::equal_to, Alloc, + T, details::KeySelect, Hash, std::equal_to, Alloc, dice::sparse_map::power_of_two_growth_policy<2>, dice::sparse_map::exception_safety::basic, dice::sparse_map::sparsity::medium, dice::sparse_map::probing::quadratic>; + } // namespace details template void construction() { using Type = typename T::value_type; - typename T::Set(T::Set::DEFAULT_INIT_BUCKET_COUNT, std::hash(), + typename T::Set(T::Set::DEFAULT_INIT_BUCKET_COUNT, details::Hash(), std::equal_to(), typename T::Allocator(), T::Set::DEFAULT_MAX_LOAD_FACTOR); } @@ -28,16 +43,16 @@ template void construction() { template struct NORMAL { - using value_type = T; - using Allocator = std::allocator; - using Set = details::sparse_set; + using value_type = std::vector; + using Allocator = std::allocator; + using Set = details::sparse_set; }; template struct SCOPED { - using value_type = T; - using Allocator = std::scoped_allocator_adaptor>; - using Set = details::sparse_set; + using value_type = std::vector; + using Allocator = std::scoped_allocator_adaptor, std::allocator>; + using Set = details::sparse_set; }; BOOST_AUTO_TEST_SUITE(scoped_allocators) From 172991517ed427f5dd20ac2ac17ee21bd6b34ea0 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Wed, 2 Aug 2023 14:55:26 +0200 Subject: [PATCH 09/41] remove last field from array and remove scoped alloc awareness from array --- include/dice/sparse-map/sparse_array.hpp | 63 +--- include/dice/sparse-map/sparse_hash.hpp | 351 ++++++++---------- tests/CMakeLists.txt | 2 - .../sparse_array_tests.cpp | 132 ------- 4 files changed, 167 insertions(+), 381 deletions(-) delete mode 100644 tests/scoped_allocator_adaptor/sparse_array_tests.cpp diff --git a/include/dice/sparse-map/sparse_array.hpp b/include/dice/sparse-map/sparse_array.hpp index d6ec529..b683863 100644 --- a/include/dice/sparse-map/sparse_array.hpp +++ b/include/dice/sparse-map/sparse_array.hpp @@ -82,7 +82,6 @@ namespace dice::sparse_map::detail { size_type m_nb_elements = 0; size_type m_capacity = 0; - bool m_last_array = false; public: /** @@ -130,45 +129,22 @@ namespace dice::sparse_map::detail { public: constexpr sparse_array() noexcept = default; - //needed for "is_constructible" with no parameters - constexpr sparse_array(std::allocator_arg_t, [[maybe_unused]] allocator_type const &alloc) noexcept { - } - - /*explicit sparse_array(bool last_bucket) noexcept - : m_values(nullptr), - m_bitmap_vals(0), - m_bitmap_deleted_vals(0), - m_nb_elements(0), - m_capacity(0), - m_last_array(last_bucket) {}*/ - - sparse_array(size_type capacity, allocator_type const &calloc) : m_capacity{capacity} { + sparse_array(size_type capacity, allocator_type &alloc) : m_capacity{capacity} { if (m_capacity == 0) { return; } - auto alloc = calloc; m_values = alloc_traits::allocate(alloc, m_capacity); assert(m_values != nullptr);// allocate should throw if there is a failure } sparse_array(sparse_array const &other) = delete; - constexpr sparse_array(sparse_array &&other) noexcept : m_values{std::exchange(other.m_values, nullptr)}, - m_bitmap_vals{std::exchange(other.m_bitmap_vals, 0)}, - m_bitmap_deleted_vals{std::exchange(other.m_bitmap_deleted_vals, 0)}, - m_nb_elements{std::exchange(other.m_nb_elements, 0)}, - m_capacity{std::exchange(other.m_capacity, 0)}, - m_last_array{other.m_last_array} { - } - - sparse_array(sparse_array const &other, allocator_type const &calloc) : m_values{nullptr}, - m_bitmap_vals{other.m_bitmap_vals}, - m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, - m_nb_elements{0}, - m_capacity(other.m_capacity), - m_last_array(other.m_last_array) { - auto alloc = calloc; + sparse_array(sparse_array const &other, allocator_type &alloc) : m_values{nullptr}, + m_bitmap_vals{other.m_bitmap_vals}, + m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, + m_nb_elements{0}, + m_capacity{other.m_capacity} { assert(other.m_capacity >= other.m_nb_elements); if (m_capacity == 0) { @@ -188,14 +164,18 @@ namespace dice::sparse_map::detail { } } - sparse_array(sparse_array &&other, Allocator const &calloc) : m_values{nullptr}, - m_bitmap_vals{other.m_bitmap_vals}, - m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, - m_nb_elements{0}, - m_capacity{other.m_capacity}, - m_last_array{other.m_last_array} { - auto alloc = calloc; // the only reason the allocator above is not mutable is because of scoped allocators + constexpr sparse_array(sparse_array &&other) noexcept : m_values{std::exchange(other.m_values, nullptr)}, + m_bitmap_vals{std::exchange(other.m_bitmap_vals, 0)}, + m_bitmap_deleted_vals{std::exchange(other.m_bitmap_deleted_vals, 0)}, + m_nb_elements{std::exchange(other.m_nb_elements, 0)}, + m_capacity{std::exchange(other.m_capacity, 0)} { + } + sparse_array(sparse_array &&other, allocator_type &alloc) : m_values{nullptr}, + m_bitmap_vals{other.m_bitmap_vals}, + m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, + m_nb_elements{0}, + m_capacity{other.m_capacity} { assert(other.m_capacity >= other.m_nb_elements); if (m_capacity == 0) { return; @@ -266,10 +246,6 @@ namespace dice::sparse_map::detail { m_capacity = 0; } - [[nodiscard]] constexpr bool last() const noexcept { return m_last_array; } - - constexpr void set_as_last() noexcept { m_last_array = true; } - [[nodiscard]] constexpr bool has_value(size_type index) const noexcept { assert(index < BITMAP_NB_BITS); return (m_bitmap_vals & (bitmap_type(1) << index)) != 0; @@ -335,7 +311,7 @@ namespace dice::sparse_map::detail { return m_values + offset; } - void swap(sparse_array &other) { + void swap(sparse_array &other) noexcept { using std::swap; swap(m_values, other.m_values); @@ -343,7 +319,6 @@ namespace dice::sparse_map::detail { swap(m_bitmap_deleted_vals, other.m_bitmap_deleted_vals); swap(m_nb_elements, other.m_nb_elements); swap(m_capacity, other.m_capacity); - swap(m_last_array, other.m_last_array); } private: @@ -537,7 +512,7 @@ namespace dice::sparse_map::detail { } } - template requires (!std::is_nothrow_move_constructible_v) + template requires (!std::is_nothrow_move_constructible_v) void erase_at_offset(allocator_type &alloc, size_type offset) { assert(offset < m_nb_elements); diff --git a/include/dice/sparse-map/sparse_hash.hpp b/include/dice/sparse-map/sparse_hash.hpp index 4cfc7fb..63616b3 100644 --- a/include/dice/sparse-map/sparse_hash.hpp +++ b/include/dice/sparse-map/sparse_hash.hpp @@ -134,7 +134,7 @@ namespace dice::sparse_map::detail { private: static constexpr bool has_mapped_type = !std::is_same_v; - using sparse_array = detail::sparse_array; + using sparse_array = sparse_array; using sparse_buckets_allocator = typename std::allocator_traits::template rebind_alloc; using sparse_buckets_container = boost::container::vector; @@ -142,9 +142,9 @@ namespace dice::sparse_map::detail { public: template class sparse_iterator { + private: friend class sparse_hash; - private: using sparse_bucket_iterator = std::conditional_t; @@ -153,14 +153,25 @@ namespace dice::sparse_map::detail { typename sparse_array::const_iterator, typename sparse_array::iterator>; + private: + sparse_bucket_iterator m_sparse_buckets_it; + sparse_bucket_iterator m_sparse_buckets_end; + sparse_array_iterator m_sparse_array_it; + + private: /** * sparse_array_it should be nullptr if sparse_bucket_it == * m_sparse_buckets_data.end(). (TODO better way?) */ sparse_iterator(sparse_bucket_iterator sparse_bucket_it, - sparse_array_iterator sparse_array_it) - : m_sparse_buckets_it(sparse_bucket_it), - m_sparse_array_it(sparse_array_it) {} + sparse_bucket_iterator sparse_bucket_end, + sparse_array_iterator sparse_array_it) noexcept : m_sparse_buckets_it{sparse_bucket_it}, + m_sparse_buckets_end{sparse_bucket_end}, + m_sparse_array_it{sparse_array_it} { + + assert((m_sparse_buckets_it == m_sparse_buckets_end && m_sparse_array_it == nullptr) + || (m_sparse_buckets_it != m_sparse_buckets_end && m_sparse_array_it != nullptr)); + } public: using iterator_category = std::forward_iterator_tag; @@ -169,68 +180,58 @@ namespace dice::sparse_map::detail { using reference = std::conditional_t; - using pointer = std::conditional_t::template rebind_traits::const_pointer, typename std::allocator_traits::template rebind_traits::pointer>; // Copy constructor from iterator to const_iterator. - sparse_iterator(sparse_iterator const &other) noexcept requires (IsConst) : m_sparse_buckets_it(other.m_sparse_buckets_it), - m_sparse_array_it(other.m_sparse_array_it) { + sparse_iterator(sparse_iterator const &other) noexcept requires (IsConst) : m_sparse_buckets_it{other.m_sparse_buckets_it}, + m_sparse_buckets_end{other.m_sparse_buckets_end}, + m_sparse_array_it{other.m_sparse_array_it} { } - sparse_iterator(const sparse_iterator &other) = default; - sparse_iterator(sparse_iterator &&other) = default; - sparse_iterator &operator=(const sparse_iterator &other) = default; - sparse_iterator &operator=(sparse_iterator &&other) = default; + sparse_iterator(sparse_iterator const &other) noexcept = default; + sparse_iterator(sparse_iterator &&other) noexcept = default; + sparse_iterator &operator=(sparse_iterator const &other) noexcept = default; + sparse_iterator &operator=(sparse_iterator &&other) noexcept = default; - reference operator*() const { return KeyValueSelect::both(*m_sparse_array_it); } + reference operator*() const noexcept { return KeyValueSelect::both(*m_sparse_array_it); } + pointer operator->() const noexcept { return &KeyValueSelect::both(*m_sparse_array_it); } - //with fancy pointers addressof might be problematic. - pointer operator->() const { return &KeyValueSelect::both(*m_sparse_array_it); } - - sparse_iterator &operator++() { + sparse_iterator &operator++() noexcept { assert(m_sparse_array_it != nullptr); ++m_sparse_array_it; - //vector iterator with fancy pointers have a problem with -> - if (m_sparse_array_it == (*m_sparse_buckets_it).end()) { - do { - if ((*m_sparse_buckets_it).last()) { - ++m_sparse_buckets_it; - m_sparse_array_it = nullptr; - return *this; - } - - ++m_sparse_buckets_it; - } while ((*m_sparse_buckets_it).empty()); - - m_sparse_array_it = (*m_sparse_buckets_it).begin(); + if (m_sparse_array_it != (*m_sparse_buckets_it).end()) { + return *this; } + do { + if (++m_sparse_buckets_it == m_sparse_buckets_end) { + m_sparse_array_it = nullptr; + return *this; + } + } while ((*m_sparse_buckets_it).empty()); + + m_sparse_array_it = (*m_sparse_buckets_it).begin(); return *this; } - sparse_iterator operator++(int) { - sparse_iterator tmp(*this); + sparse_iterator operator++(int) noexcept { + auto tmp = *this; ++*this; - return tmp; } template - bool operator==(const sparse_iterator &other) const noexcept { + bool operator==(sparse_iterator const &other) const noexcept { return m_sparse_buckets_it == other.m_sparse_buckets_it && m_sparse_array_it == other.m_sparse_array_it; } template - bool operator!=(const sparse_iterator &other) const noexcept { + bool operator!=(sparse_iterator const &other) const noexcept { return m_sparse_buckets_it != other.m_sparse_buckets_it || m_sparse_array_it != other.m_sparse_array_it; } - - private: - sparse_bucket_iterator m_sparse_buckets_it; - sparse_array_iterator m_sparse_array_it; }; iterator mutable_iterator(const_iterator pos) noexcept { @@ -241,43 +242,26 @@ namespace dice::sparse_map::detail { // SAFETY: this is non-const therefore the underlying sparse array is also non-const auto it_array = sparse_array::unsafe_mutable_iterator(pos.m_sparse_array_it); - return iterator(it_sparse_buckets, it_array); + return iterator{it_sparse_buckets, m_sparse_buckets_data.end(), it_array}; } public: - sparse_hash(size_type bucket_count, const Hash &hash, const KeyEqual &equal, - const Allocator &alloc, float max_load_factor) - : m_sparse_buckets_data(alloc), - // m_sparse_buckets_data(std::allocator_traits::rebind_alloc(m_alloc)), - m_sparse_buckets(static_empty_sparse_bucket_ptr()), - m_bucket_count(bucket_count), - m_nb_elements(0), - m_nb_deleted_buckets(0), - m_alloc{alloc}, - m_h{hash}, - m_keq{equal}, - m_gpol{bucket_count} { - + sparse_hash(size_type bucket_count, Hash const &hash, KeyEqual const &equal, + allocator_type const &alloc, float max_load_factor) : m_sparse_buckets_data{alloc}, + m_bucket_count{bucket_count}, + m_nb_elements{0}, + m_nb_deleted_buckets{0}, + m_alloc{alloc}, + m_h{hash}, + m_keq{equal}, + m_gpol{bucket_count} { if (m_bucket_count > max_bucket_count()) { throw std::length_error("The map exceeds its maximum size."); } if (m_bucket_count > 0) { - /* - * We can't use the `vector(size_type count, const Allocator& m_alloc)` - * constructor as it's only available in C++14 and we need to support - * C++11. We thus must resize after using the `vector(const Allocator& - * m_alloc)` constructor. - * - * We can't use `vector(size_type count, const T& value, const Allocator& - * m_alloc)` as it requires the value T to be copyable. - */ - m_sparse_buckets_data.resize( - sparse_array::nb_sparse_buckets(bucket_count)); - m_sparse_buckets = m_sparse_buckets_data.data(); - + m_sparse_buckets_data.resize(sparse_array::nb_sparse_buckets(bucket_count)); assert(!m_sparse_buckets_data.empty()); - m_sparse_buckets_data.back().set_as_last(); } this->max_load_factor(max_load_factor); @@ -304,10 +288,7 @@ namespace dice::sparse_map::detail { m_h{other.m_h}, m_keq{other.m_keq}, m_gpol{other.m_gpol} { - copy_buckets_from(other), - m_sparse_buckets = m_sparse_buckets_data.empty() - ? static_empty_sparse_bucket_ptr() - : m_sparse_buckets_data.data(); + copy_buckets_from(other); } sparse_hash(sparse_hash &&other) noexcept(std::is_nothrow_move_constructible::value @@ -316,9 +297,6 @@ namespace dice::sparse_map::detail { && std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value) : m_sparse_buckets_data(std::move(other.m_sparse_buckets_data)), - m_sparse_buckets(m_sparse_buckets_data.empty() - ? static_empty_sparse_bucket_ptr() - : m_sparse_buckets_data.data()), m_bucket_count(other.m_bucket_count), m_nb_elements(other.m_nb_elements), m_nb_deleted_buckets(other.m_nb_deleted_buckets), @@ -331,7 +309,6 @@ namespace dice::sparse_map::detail { m_gpol{std::move(other.m_gpol)} { other.m_gpol.clear(); other.m_sparse_buckets_data.clear(); - other.m_sparse_buckets = static_empty_sparse_bucket_ptr(); other.m_bucket_count = 0; other.m_nb_elements = 0; other.m_nb_deleted_buckets = 0; @@ -363,9 +340,6 @@ namespace dice::sparse_map::detail { } copy_buckets_from(other); - m_sparse_buckets = m_sparse_buckets_data.empty() - ? static_empty_sparse_bucket_ptr() - : m_sparse_buckets_data.data(); m_bucket_count = other.m_bucket_count; m_nb_elements = other.m_nb_elements; @@ -388,10 +362,6 @@ namespace dice::sparse_map::detail { m_sparse_buckets_data = std::move(other.m_sparse_buckets_data); } - m_sparse_buckets = m_sparse_buckets_data.empty() - ? static_empty_sparse_bucket_ptr() - : m_sparse_buckets_data.data(); - m_h = std::move(other.m_h); m_keq = std::move(other.m_keq); m_gpol = std::move(other.m_gpol); @@ -404,7 +374,6 @@ namespace dice::sparse_map::detail { other.m_gpol.clear(); other.m_sparse_buckets_data.clear(); - other.m_sparse_buckets = static_empty_sparse_bucket_ptr(); other.m_bucket_count = 0; other.m_nb_elements = 0; other.m_nb_deleted_buckets = 0; @@ -426,12 +395,14 @@ namespace dice::sparse_map::detail { } //vector iterator with fancy pointers have a problem with -> - return iterator(begin, (begin != m_sparse_buckets_data.end()) - ? (*begin).begin() - : nullptr); + return iterator{begin, + m_sparse_buckets_data.end(), + begin != m_sparse_buckets_data.end() ? (*begin).begin() : nullptr}; } - const_iterator begin() const noexcept { return cbegin(); } + const_iterator begin() const noexcept { + return cbegin(); + } const_iterator cbegin() const noexcept { auto begin = m_sparse_buckets_data.cbegin(); @@ -440,19 +411,25 @@ namespace dice::sparse_map::detail { ++begin; } - return const_iterator(begin, (begin != m_sparse_buckets_data.cend()) - ? (*begin).cbegin() - : nullptr); + return const_iterator{begin, + m_sparse_buckets_data.cend(), + begin != m_sparse_buckets_data.cend() ? (*begin).cbegin() : nullptr}; } iterator end() noexcept { - return iterator(m_sparse_buckets_data.end(), nullptr); + return iterator{m_sparse_buckets_data.end(), + m_sparse_buckets_data.end(), + nullptr}; } - const_iterator end() const noexcept { return cend(); } + const_iterator end() const noexcept { + return cend(); + } const_iterator cend() const noexcept { - return const_iterator(m_sparse_buckets_data.cend(), nullptr); + return const_iterator{m_sparse_buckets_data.cend(), + m_sparse_buckets_data.cend(), + nullptr}; } bool empty() const noexcept { return m_nb_elements == 0; } @@ -563,8 +540,7 @@ namespace dice::sparse_map::detail { iterator erase(iterator pos) { assert(pos != end() && m_nb_elements > 0); //vector iterator with fancy pointers have a problem with -> - auto it_sparse_array_next = - (*pos.m_sparse_buckets_it).erase(m_alloc, pos.m_sparse_array_it); + auto it_sparse_array_next = (*pos.m_sparse_buckets_it).erase(m_alloc, pos.m_sparse_array_it); m_nb_elements--; m_nb_deleted_buckets++; @@ -572,30 +548,38 @@ namespace dice::sparse_map::detail { auto it_sparse_buckets_next = pos.m_sparse_buckets_it; do { ++it_sparse_buckets_next; - } while (it_sparse_buckets_next != m_sparse_buckets_data.end() && - (*it_sparse_buckets_next).empty()); + } while (it_sparse_buckets_next != m_sparse_buckets_data.end() + && (*it_sparse_buckets_next).empty()); if (it_sparse_buckets_next == m_sparse_buckets_data.end()) { return end(); } else { - return iterator(it_sparse_buckets_next, - (*it_sparse_buckets_next).begin()); + return iterator{it_sparse_buckets_next, + m_sparse_buckets_data.end(), + (*it_sparse_buckets_next).begin()}; } } else { - return iterator(pos.m_sparse_buckets_it, it_sparse_array_next); + return iterator{pos.m_sparse_buckets_it, + m_sparse_buckets_data.end(), + it_sparse_array_next}; } } - iterator erase(const_iterator pos) { return erase(mutable_iterator(pos)); } + iterator erase(const_iterator pos) { + return erase(mutable_iterator(pos)); + } iterator erase(const_iterator first, const_iterator last) { - if (first == last) { - return mutable_iterator(first); + //TODO why doesn't this work + /*auto it = mutable_iterator(first); + while (it != last) { + it = erase(it); } + return it;*/ + // TODO Optimize, could avoid the call to std::distance. - const size_type nb_elements_to_erase = - static_cast(std::distance(first, last)); + auto const nb_elements_to_erase = static_cast(std::distance(first, last)); auto to_delete = mutable_iterator(first); for (size_type i = 0; i < nb_elements_to_erase; i++) { to_delete = erase(to_delete); @@ -627,7 +611,6 @@ namespace dice::sparse_map::detail { swap(m_keq, other.m_keq); swap(m_gpol, other.m_gpol); swap(m_sparse_buckets_data, other.m_sparse_buckets_data); - swap(m_sparse_buckets, other.m_sparse_buckets); swap(m_bucket_count, other.m_bucket_count); swap(m_nb_elements, other.m_nb_elements); swap(m_nb_deleted_buckets, other.m_nb_deleted_buckets); @@ -758,8 +741,7 @@ namespace dice::sparse_map::detail { } void rehash(size_type count) { - count = std::max(count, - size_type(std::ceil(float(size()) / max_load_factor()))); + count = std::max(count, size_type(std::ceil(float(size()) / max_load_factor()))); rehash_impl(count); } @@ -811,9 +793,6 @@ namespace dice::sparse_map::detail { clear(); throw; } - - assert(m_sparse_buckets_data.empty() || - m_sparse_buckets_data.back().last()); } void move_buckets_from(sparse_hash &&other) { @@ -827,9 +806,6 @@ namespace dice::sparse_map::detail { clear(); throw; } - - assert(m_sparse_buckets_data.empty() || - m_sparse_buckets_data.back().last()); } template @@ -864,19 +840,16 @@ namespace dice::sparse_map::detail { auto index_in_sparse_bucket = sparse_array::index_in_sparse_bucket(ibucket); - if (m_sparse_buckets != static_empty_sparse_bucket_ptr()) { - if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { - auto value_it = - m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); + if (!m_sparse_buckets_data.empty()) { + if (m_sparse_buckets_data[sparse_ibucket].has_value(index_in_sparse_bucket)) { + auto value_it = m_sparse_buckets_data[sparse_ibucket].value(index_in_sparse_bucket); if (m_keq(key, KeyValueSelect::key(*value_it))) { - return std::make_pair( - iterator(m_sparse_buckets_data.begin() + sparse_ibucket, - value_it), - false); + return std::make_pair(iterator{std::next(m_sparse_buckets_data.begin(), sparse_ibucket), + m_sparse_buckets_data.end(), + value_it}, + false); } - } else if (m_sparse_buckets[sparse_ibucket].has_deleted_value( - index_in_sparse_bucket) && - probe < m_bucket_count) { + } else if (m_sparse_buckets_data[sparse_ibucket].has_deleted_value(index_in_sparse_bucket) && probe < m_bucket_count) { if (!found_first_deleted_bucket) { found_first_deleted_bucket = true; sparse_ibucket_first_deleted = sparse_ibucket; @@ -904,47 +877,45 @@ namespace dice::sparse_map::detail { } template - std::pair insert_in_bucket( - std::size_t sparse_ibucket, - typename sparse_array::size_type index_in_sparse_bucket, - Args &&...value_type_args) { + std::pair insert_in_bucket(std::size_t sparse_ibucket, + typename sparse_array::size_type index_in_sparse_bucket, + Args &&...value_type_args) { // is not called when empty - auto value_it = m_sparse_buckets[sparse_ibucket].set( - m_alloc, index_in_sparse_bucket, std::forward(value_type_args)...); + auto value_it = m_sparse_buckets_data[sparse_ibucket].set(m_alloc, index_in_sparse_bucket, std::forward(value_type_args)...); m_nb_elements++; - return std::make_pair( - iterator(m_sparse_buckets_data.begin() + sparse_ibucket, value_it), + return std::make_pair(iterator{std::next(m_sparse_buckets_data.begin(), sparse_ibucket), + m_sparse_buckets_data.end(), + value_it}, true); } template - size_type erase_impl(const K &key, std::size_t hash) { - std::size_t ibucket = bucket_for_hash(hash); + size_type erase_impl(K const &key, std::size_t hash) { + if (m_sparse_buckets_data.empty()) { + return 0; + } + std::size_t ibucket = bucket_for_hash(hash); std::size_t probe = 0; - if (m_sparse_buckets == static_empty_sparse_bucket_ptr()) - return 0; while (true) { - const std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); - const auto index_in_sparse_bucket = - sparse_array::index_in_sparse_bucket(ibucket); + auto const sparse_ibucket = sparse_array::sparse_ibucket(ibucket); + auto const index_in_sparse_bucket = sparse_array::index_in_sparse_bucket(ibucket); + + auto &bucket = m_sparse_buckets_data[sparse_ibucket]; + + if (bucket.has_value(index_in_sparse_bucket)) { + auto value_it = bucket.value(index_in_sparse_bucket); - if (m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { - auto value_it = - m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); if (m_keq(key, KeyValueSelect::key(*value_it))) { - m_sparse_buckets[sparse_ibucket].erase(m_alloc, value_it, - index_in_sparse_bucket); + bucket.erase(m_alloc, value_it, index_in_sparse_bucket); m_nb_elements--; m_nb_deleted_buckets++; return 1; } - } else if (!m_sparse_buckets[sparse_ibucket].has_deleted_value( - index_in_sparse_bucket) || - probe >= m_bucket_count) { + } else if (!bucket.has_deleted_value(index_in_sparse_bucket) || probe >= m_bucket_count) { return 0; } @@ -954,27 +925,30 @@ namespace dice::sparse_map::detail { } template - static auto find_impl(Self &&self, const K &key, std::size_t hash) { - static constexpr bool is_const = std::is_const_v>; - std::size_t ibucket = self.bucket_for_hash(hash); + static auto find_impl(Self &&self, K const &key, std::size_t hash) { + if (self.m_sparse_buckets_data.empty()) { + return self.end(); + } + std::size_t ibucket = self.bucket_for_hash(hash); std::size_t probe = 0; + while (true) { - const std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); - const auto index_in_sparse_bucket = - sparse_array::index_in_sparse_bucket(ibucket); + auto const sparse_ibucket = sparse_array::sparse_ibucket(ibucket); + auto const index_in_sparse_bucket = sparse_array::index_in_sparse_bucket(ibucket); - if (self.m_sparse_buckets == static_empty_sparse_bucket_ptr()) { - return self.end(); - } - if (self.m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { - auto value_it = self.m_sparse_buckets[sparse_ibucket].value(index_in_sparse_bucket); + auto &bucket = self.m_sparse_buckets_data[sparse_ibucket]; + + if (bucket.has_value(index_in_sparse_bucket)) { + auto value_it = bucket.value(index_in_sparse_bucket); if (self.m_keq(key, KeyValueSelect::key(*value_it))) { - return sparse_iterator{self.m_sparse_buckets_data.begin() + sparse_ibucket, value_it}; + static constexpr bool is_const = std::is_const_v>; + + return sparse_iterator{std::next(self.m_sparse_buckets_data.begin(), sparse_ibucket), + self.m_sparse_buckets_data.end(), + value_it}; } - } else if (!self.m_sparse_buckets[sparse_ibucket].has_deleted_value( - index_in_sparse_bucket) || - probe >= self.m_bucket_count) { + } else if (!bucket.has_deleted_value(index_in_sparse_bucket) || probe >= self.m_bucket_count) { return self.end(); } @@ -999,10 +973,7 @@ namespace dice::sparse_map::detail { assert(m_nb_deleted_buckets == 0); } - template::type - * = nullptr> - void rehash_impl(size_type count) { + void rehash_impl(size_type count) requires (ExceptionSafety == exception_safety::basic) { sparse_hash new_table(count, m_h, m_keq, m_alloc, m_max_load_factor); for (auto &bucket : m_sparse_buckets_data) { @@ -1022,10 +993,7 @@ namespace dice::sparse_map::detail { * them if they are nothrow_move_constructible without triggering * any exception if we reserve enough space in the sparse arrays beforehand. */ - template::type * = nullptr> - void rehash_impl(size_type count) { + void rehash_impl(size_type count) requires (ExceptionSafety == exception_safety::strong) { sparse_hash new_table(count, m_h, m_keq, m_alloc, m_max_load_factor); for (const auto &bucket : m_sparse_buckets_data) { @@ -1041,25 +1009,23 @@ namespace dice::sparse_map::detail { void insert_on_rehash(K &&key_value) { const key_type &key = KeyValueSelect::key(key_value); - const std::size_t hash = m_h(key); + std::size_t const hash = m_h(key); std::size_t ibucket = bucket_for_hash(hash); - std::size_t probe = 0; + while (true) { - std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); - auto index_in_sparse_bucket = - sparse_array::index_in_sparse_bucket(ibucket); + auto const sparse_ibucket = sparse_array::sparse_ibucket(ibucket); + auto const index_in_sparse_bucket = sparse_array::index_in_sparse_bucket(ibucket); - if (!m_sparse_buckets[sparse_ibucket].has_value(index_in_sparse_bucket)) { - m_sparse_buckets[sparse_ibucket].set(m_alloc, index_in_sparse_bucket, - std::forward(key_value)); + auto &bucket = m_sparse_buckets_data[sparse_ibucket]; + + if (!bucket.has_value(index_in_sparse_bucket)) { + bucket.set(m_alloc, index_in_sparse_bucket, std::forward(key_value)); m_nb_elements++; return; } else { - assert(!m_keq( - key, KeyValueSelect::key(*m_sparse_buckets[sparse_ibucket].value( - index_in_sparse_bucket)))); + assert(!m_keq(key, KeyValueSelect::key(*bucket.value(index_in_sparse_bucket)))); } probe++; @@ -1071,30 +1037,9 @@ namespace dice::sparse_map::detail { static constexpr size_type DEFAULT_INIT_BUCKET_COUNT = 0; static constexpr float DEFAULT_MAX_LOAD_FACTOR = 0.5f; - using sparse_array_ptr = typename std::allocator_traits::template rebind_traits::pointer; - - /** - * Return an nullptr to indicate an empty bucket - */ - static sparse_array_ptr static_empty_sparse_bucket_ptr() { - return {}; - } - private: sparse_buckets_container m_sparse_buckets_data; - - /** - * Points to m_sparse_buckets_data.data() if !m_sparse_buckets_data.empty() - * otherwise points to static_empty_sparse_bucket_ptr. This variable is useful - * to avoid the cost of checking if m_sparse_buckets_data is empty when trying - * to find an element. - * - * TODO Remove m_sparse_buckets_data and only use a pointer instead of a - * pointer+vector to save some space in the sparse_hash object. - */ - sparse_array_ptr m_sparse_buckets; - size_type m_bucket_count; size_type m_nb_elements; size_type m_nb_deleted_buckets; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 71a3f87..67e57b0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,10 +7,8 @@ add_executable(tsl_sparse_map_tests "main.cpp" "policy_tests.cpp" "sparse_map_tests.cpp" "sparse_set_tests.cpp" - "fancy_pointer/sparse_array_tests.cpp" "fancy_pointer/sparse_hash_map_tests.cpp" "fancy_pointer/sparse_hash_set_tests.cpp" - "scoped_allocator_adaptor/sparse_array_tests.cpp" "scoped_allocator_adaptor/sparse_hash_set_tests.cpp" ) diff --git a/tests/scoped_allocator_adaptor/sparse_array_tests.cpp b/tests/scoped_allocator_adaptor/sparse_array_tests.cpp deleted file mode 100644 index 57e4e5d..0000000 --- a/tests/scoped_allocator_adaptor/sparse_array_tests.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -// Globals -constexpr auto MAX_INDEX = 32; // BITMAP_NB_BITS - -template void compilation() { - typename T::Array test; - (void) test; -} - -template void construction() { - typename T::Allocator a; - typename T::Array test(MAX_INDEX, a); - test.clear(a); -} - -template -void set(std::initializer_list l) { - typename T::Allocator a; - typename T::Array array(MAX_INDEX, a); - std::vector check; - check.reserve(l.size()); - std::size_t counter = 0; - for (auto const &value : l) { - array.set(a, counter++, value); - check.emplace_back(value); - } - //'set' did not create the correct order of items - BOOST_REQUIRE(std::equal(array.begin(), array.end(), check.begin())); - array.clear(a); -} - -template void uses_allocator() { - //uses_allocator returns false - BOOST_REQUIRE((std::uses_allocator::value)); -} - -template -void trailing_allocator_convention(Args...) { - using Alloc = typename T::Allocator; - //trailing_allocator thinks construction is not possible - BOOST_REQUIRE((std::is_constructible::value)); -} - -template void trailing_allocator_convention_without_parameters() { - using Alloc = typename std::allocator_traits< - typename T::Allocator>::template rebind_alloc; - //trailing_allocator thinks construction is not possible - BOOST_REQUIRE((std::is_constructible::value)); -} - -template -void is_move_insertable(std::initializer_list l) { - using A = typename std::allocator_traits< - typename T::Allocator>::template rebind_alloc; - A m; - auto p = std::allocator_traits::allocate(m, 1); - typename T::Allocator ArrayAlloc; - typename T::Array rv(MAX_INDEX, ArrayAlloc); - std::size_t counter = 0; - for (auto const &value : l) { - rv.set(ArrayAlloc, counter++, value); - } - std::allocator_traits::construct(m, p, std::move(rv)); - rv.clear(ArrayAlloc); - p->clear(ArrayAlloc); - std::allocator_traits::destroy(m, p); - std::allocator_traits::deallocate(m, p, 1); -} - -template void is_default_insertable() { - using A = typename std::allocator_traits< - typename T::Allocator>::template rebind_alloc; - A m; - typename T::Array *p = std::allocator_traits::allocate(m, 1); - std::allocator_traits::construct(m, p); - std::allocator_traits::deallocate(m, p, 1); -} - -template -struct NORMAL { - using value_type = std::vector; - using Allocator = std::allocator>; - using Array = dice::sparse_map::detail::sparse_array, Allocator, Sparsity>; -}; - -template -struct SCOPED { - using value_type = std::vector; - using Allocator = std::scoped_allocator_adaptor>, std::allocator>; - using Array = dice::sparse_map::detail::sparse_array, Allocator, Sparsity>; -}; - -BOOST_AUTO_TEST_SUITE(scoped_allocators) -BOOST_AUTO_TEST_SUITE(sparse_array_tests) - -BOOST_AUTO_TEST_CASE(normal_compilation) { compilation>(); } -BOOST_AUTO_TEST_CASE(normal_construction) { construction>(); } -BOOST_AUTO_TEST_CASE(normal_set) { set>({{0, 1, 2, 3, 4}}); } -BOOST_AUTO_TEST_CASE(normal_uses_allocator) { uses_allocator>(); } -BOOST_AUTO_TEST_CASE(normal_trailing_allocator_convention) { - trailing_allocator_convention>(0); -} -BOOST_AUTO_TEST_CASE(normal_is_move_insertable) { - is_move_insertable>({{0, 1, 2, 3, 4, 5}}); -} -BOOST_AUTO_TEST_CASE(normal_is_default_insertable) { - is_default_insertable>(); -} - -BOOST_AUTO_TEST_CASE(scoped_compilation) { compilation>(); } -BOOST_AUTO_TEST_CASE(scoped_construction) { construction>(); } -BOOST_AUTO_TEST_CASE(scoped_set) { set>({{0, 1, 2, 3, 4}, {1, 2, 3}}); } -BOOST_AUTO_TEST_CASE(scoped_uses_allocator) { uses_allocator>(); } -BOOST_AUTO_TEST_CASE(scoped_trailing_allocator_convention) { - trailing_allocator_convention>(0); -} -BOOST_AUTO_TEST_CASE(scoped_is_move_insertable) { - is_move_insertable>({{0, 1, 2, 3, 4, 5}, {1, 2, 3}}); -} -BOOST_AUTO_TEST_CASE(scoped_is_default_insertable) { - is_default_insertable>(); -} - -BOOST_AUTO_TEST_SUITE_END() -BOOST_AUTO_TEST_SUITE_END() From fa4efa2b1cc2223964d22d6c9a9f7ca6c86779b3 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Wed, 2 Aug 2023 16:05:28 +0200 Subject: [PATCH 10/41] utilize memmove --- include/dice/sparse-map/sparse_array.hpp | 60 ++++++++++++++---------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/include/dice/sparse-map/sparse_array.hpp b/include/dice/sparse-map/sparse_array.hpp index b683863..a112cd7 100644 --- a/include/dice/sparse-map/sparse_array.hpp +++ b/include/dice/sparse-map/sparse_array.hpp @@ -6,7 +6,7 @@ namespace dice::sparse_map::detail { /** - * WARNING: the sparse_array class doesn't free the ressources allocated through + * WARNING: the sparse_array class doesn't free the resources allocated through * the allocator passed in parameter in each method. You have to manually call * `clear(Allocator&)` when you don't need a sparse_array object anymore. * @@ -31,8 +31,6 @@ namespace dice::sparse_map::detail { * * See https://smerity.com/articles/2015/google_sparsehash.html for details on * the idea behinds the implementation. - * - * TODO Check to use std::realloc and std::memmove when possible */ template struct sparse_array { @@ -182,7 +180,7 @@ namespace dice::sparse_map::detail { } m_values = alloc_traits::allocate(alloc, m_capacity); - assert(m_values != nullptr);// allocate should throw if there is a failure + assert(m_values != nullptr); // allocate should throw if there is a failure try { for (size_type i = 0; i < other.m_nb_elements; i++) { @@ -333,13 +331,9 @@ namespace dice::sparse_map::detail { alloc_traits::deallocate(alloc, values, capacity_values); } - [[nodiscard]] static constexpr size_type popcount(bitmap_type val) noexcept { - return std::popcount(val); - } - [[nodiscard]] constexpr size_type index_to_offset(size_type index) const noexcept { assert(index < BITMAP_NB_BITS); - return popcount(m_bitmap_vals & ((bitmap_type(1) << index) - bitmap_type(1))); + return std::popcount(m_bitmap_vals & ((bitmap_type(1) << index) - bitmap_type(1))); } // TODO optimize @@ -406,19 +400,28 @@ namespace dice::sparse_map::detail { assert(offset <= m_nb_elements); assert(m_nb_elements < m_capacity); - for (size_type i = m_nb_elements; i > offset; i--) { - construct_at(alloc, &m_values[i], std::move(m_values[i - 1])); - destroy_at(alloc, &m_values[i - 1]); + if constexpr (std::is_trivially_copyable_v) { + std::memmove(&m_values[offset + 1], &m_values[offset], (m_nb_elements - offset) * sizeof(value_type)); + } else { + for (size_type i = m_nb_elements; i > offset; i--) { + construct_at(alloc, &m_values[i], std::move(m_values[i - 1])); + destroy_at(alloc, &m_values[i - 1]); + } } try { construct_at(alloc, &m_values[offset], std::forward(value_args)...); } catch (...) { // revert - for (size_type i = offset; i < m_nb_elements; i++) { - construct_at(alloc, &m_values[i], std::move(m_values[i + 1])); - destroy_at(alloc, &m_values[i + 1]); + if constexpr (std::is_trivially_copyable_v) { + std::memmove(&m_values[offset], &m_values[offset + 1], (m_nb_elements - offset) * sizeof(value_type)); + } else { + for (size_type i = offset; i < m_nb_elements; i++) { + construct_at(alloc, &m_values[i], std::move(m_values[i + 1])); + destroy_at(alloc, &m_values[i + 1]); + } } + throw; } } @@ -438,13 +441,18 @@ namespace dice::sparse_map::detail { throw; } - // Should not throw from here - for (size_type i = 0; i < offset; i++) { - construct_at(alloc, &new_values[i], std::move(m_values[i])); - } + if constexpr (std::is_trivially_copyable_v) { + std::memcpy(&new_values[0], &m_values[0], offset * sizeof(value_type)); + std::memcpy(&new_values[offset + 1], &m_values[offset], (m_nb_elements - offset) * sizeof(value_type)); + } else { + // Cannot throw here as per requires clause + for (size_type i = 0; i < offset; i++) { + construct_at(alloc, &new_values[i], std::move(m_values[i])); + } - for (size_type i = offset; i < m_nb_elements; i++) { - construct_at(alloc, &new_values[i + 1], std::move(m_values[i])); + for (size_type i = offset; i < m_nb_elements; i++) { + construct_at(alloc, &new_values[i + 1], std::move(m_values[i])); + } } destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); @@ -506,9 +514,13 @@ namespace dice::sparse_map::detail { destroy_at(alloc, &m_values[offset]); - for (size_type i = offset + 1; i < m_nb_elements; ++i) { - construct_at(alloc, &m_values[i - 1], std::move(m_values[i])); - destroy_at(alloc, &m_values[i]); + if constexpr (std::is_trivially_copyable_v) { + std::memmove(&m_values[offset], &m_values[offset + 1], (m_nb_elements - offset - 1) * sizeof(value_type)); + } else { + for (size_type i = offset + 1; i < m_nb_elements; ++i) { + construct_at(alloc, &m_values[i - 1], std::move(m_values[i])); + destroy_at(alloc, &m_values[i]); + } } } From bdb2ac92b59013abdbce823e397fc799561f897d Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Wed, 2 Aug 2023 16:22:14 +0200 Subject: [PATCH 11/41] more memmove --- include/dice/sparse-map/sparse_array.hpp | 63 +++++++++++++++--------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/include/dice/sparse-map/sparse_array.hpp b/include/dice/sparse-map/sparse_array.hpp index a112cd7..26e7276 100644 --- a/include/dice/sparse-map/sparse_array.hpp +++ b/include/dice/sparse-map/sparse_array.hpp @@ -154,7 +154,7 @@ namespace dice::sparse_map::detail { try { for (; m_nb_elements < other.m_nb_elements; ++m_nb_elements) { - construct_at(alloc, &m_values[m_nb_elements], other.m_values[m_nb_elements]); + construct_at(alloc, m_values + m_nb_elements, other.m_values[m_nb_elements]); } } catch (...) { clear(alloc); @@ -169,11 +169,16 @@ namespace dice::sparse_map::detail { m_capacity{std::exchange(other.m_capacity, 0)} { } - sparse_array(sparse_array &&other, allocator_type &alloc) : m_values{nullptr}, - m_bitmap_vals{other.m_bitmap_vals}, - m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, - m_nb_elements{0}, - m_capacity{other.m_capacity} { + sparse_array(sparse_array &&other, [[maybe_unused]] allocator_type &alloc) noexcept requires (alloc_traits::is_always_equal::value) + : sparse_array{std::move(other)} { + } + + sparse_array(sparse_array &&other, allocator_type &alloc) requires (!alloc_traits::is_always_equal::value) + : m_bitmap_vals{other.m_bitmap_vals}, + m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, + m_nb_elements{0}, + m_capacity{other.m_capacity} { + assert(other.m_capacity >= other.m_nb_elements); if (m_capacity == 0) { return; @@ -182,14 +187,24 @@ namespace dice::sparse_map::detail { m_values = alloc_traits::allocate(alloc, m_capacity); assert(m_values != nullptr); // allocate should throw if there is a failure - try { + if constexpr (std::is_trivially_copyable_v) { + std::memcpy(&m_values[0], &other.m_values[0], other.m_nb_elements * sizeof(value_type)); + m_nb_elements = other.m_nb_elements; + } else if constexpr (std::is_nothrow_move_constructible_v) { for (size_type i = 0; i < other.m_nb_elements; i++) { construct_at(alloc, &m_values[i], std::move(other.m_values[i])); - m_nb_elements++; } - } catch (...) { - clear(alloc); - throw; + + m_nb_elements = other.m_nb_elements; + } else { + try { + for (; m_nb_elements < other.m_nb_elements; ++m_nb_elements) { + construct_at(alloc, &m_values[m_nb_elements], std::move(other.m_values[m_nb_elements])); + } + } catch (...) { + clear(alloc); + throw; + } } } @@ -246,12 +261,12 @@ namespace dice::sparse_map::detail { [[nodiscard]] constexpr bool has_value(size_type index) const noexcept { assert(index < BITMAP_NB_BITS); - return (m_bitmap_vals & (bitmap_type(1) << index)) != 0; + return (m_bitmap_vals & (bitmap_type{1} << index)) != 0; } [[nodiscard]] constexpr bool has_deleted_value(size_type index) const noexcept { assert(index < BITMAP_NB_BITS); - return (m_bitmap_deleted_vals & (bitmap_type(1) << index)) != 0; + return (m_bitmap_deleted_vals & (bitmap_type{1} << index)) != 0; } iterator value(size_type index) noexcept { @@ -274,10 +289,10 @@ namespace dice::sparse_map::detail { const size_type offset = index_to_offset(index); insert_at_offset(alloc, offset, std::forward(value_args)...); - m_bitmap_vals = (m_bitmap_vals | (bitmap_type(1) << index)); - m_bitmap_deleted_vals = (m_bitmap_deleted_vals & ~(bitmap_type(1) << index)); + m_bitmap_vals |= bitmap_type{1} << index; + m_bitmap_deleted_vals &= ~(bitmap_type{1} << index); - m_nb_elements++; + m_nb_elements += 1; assert(has_value(index)); assert(!has_deleted_value(index)); @@ -298,10 +313,10 @@ namespace dice::sparse_map::detail { auto const offset = static_cast(std::distance(begin(), position)); erase_at_offset(alloc, offset); - m_bitmap_vals = (m_bitmap_vals & ~(bitmap_type(1) << index)); - m_bitmap_deleted_vals = (m_bitmap_deleted_vals | (bitmap_type(1) << index)); + m_bitmap_vals &= ~(bitmap_type{1} << index); + m_bitmap_deleted_vals |= bitmap_type{1} << index; - m_nb_elements--; + m_nb_elements -= 1; assert(!has_value(index)); assert(has_deleted_value(index)); @@ -324,8 +339,10 @@ namespace dice::sparse_map::detail { pointer values, size_type nb_values, size_type capacity_values) noexcept { - for (size_type i = 0; i < nb_values; i++) { - destroy_at(alloc, &values[i]); + if constexpr (!std::is_trivially_destructible_v) { + for (size_type i = 0; i < nb_values; i++) { + destroy_at(alloc, &values[i]); + } } alloc_traits::deallocate(alloc, values, capacity_values); @@ -350,10 +367,10 @@ namespace dice::sparse_map::detail { break; } - nb_ones++; + nb_ones += 1; } - index++; + index += 1; bitmap_vals = bitmap_vals >> 1; } From 43c26e4bdc633f40e2e5c2b2e76c62419a65bb6c Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Wed, 2 Aug 2023 17:43:40 +0200 Subject: [PATCH 12/41] attempt to optimize offset_to_index --- include/dice/sparse-map/sparse_array.hpp | 42 +++++++++---------- .../dice/sparse-map/sparse_growth_policy.hpp | 6 +-- include/dice/sparse-map/sparse_props.hpp | 14 ------- 3 files changed, 23 insertions(+), 39 deletions(-) diff --git a/include/dice/sparse-map/sparse_array.hpp b/include/dice/sparse-map/sparse_array.hpp index 26e7276..4e0bdca 100644 --- a/include/dice/sparse-map/sparse_array.hpp +++ b/include/dice/sparse-map/sparse_array.hpp @@ -60,7 +60,7 @@ namespace dice::sparse_map::detail { static constexpr std::size_t BUCKET_MASK = BITMAP_NB_BITS - 1; - static_assert(is_power_of_2(BITMAP_NB_BITS) == 1, + static_assert(std::has_single_bit(BITMAP_NB_BITS) == 1, "BITMAP_NB_BITS must be a power of two."); static_assert(std::numeric_limits::digits >= BITMAP_NB_BITS, "bitmap_type must be able to hold at least BITMAP_NB_BITS."); @@ -112,7 +112,7 @@ namespace dice::sparse_map::detail { return 0; } - return std::max(1, sparse_ibucket(round_up_to_power_of_2(bucket_count))); + return std::max(1, sparse_ibucket(std::bit_ceil(bucket_count))); } template @@ -280,9 +280,9 @@ namespace dice::sparse_map::detail { } /** - * Return iterator to set value. - */ - template + * Return iterator to set value. + */ + template iterator set(allocator_type &alloc, size_type index, Args &&...value_args) { assert(!has_value(index)); @@ -350,31 +350,31 @@ namespace dice::sparse_map::detail { [[nodiscard]] constexpr size_type index_to_offset(size_type index) const noexcept { assert(index < BITMAP_NB_BITS); - return std::popcount(m_bitmap_vals & ((bitmap_type(1) << index) - bitmap_type(1))); + return std::popcount(m_bitmap_vals & ((bitmap_type{1} << index) - bitmap_type{1})); } - // TODO optimize - [[nodiscard]] constexpr size_type offset_to_index(size_type offset) const noexcept { - assert(offset < m_nb_elements); + [[nodiscard]] constexpr size_t offset_to_index(size_t offset) const noexcept { + assert(offset < static_cast(std::popcount(m_bitmap_vals))); - bitmap_type bitmap_vals = m_bitmap_vals; size_type index = 0; - size_type nb_ones = 0; + bitmap_type acc = m_bitmap_vals; - while (bitmap_vals != 0) { - if ((bitmap_vals & 0x1) == 1) { - if (nb_ones == offset) { - break; - } - - nb_ones += 1; + while (true) { + size_t const ones = std::countr_one(acc); + if (ones > offset) { + break; } - index += 1; - bitmap_vals = bitmap_vals >> 1; + acc >>= ones; + index += ones; + offset -= ones; + + size_t const skip = std::countr_zero(acc); + acc >>= skip; + index += skip; } - return index; + return index + offset; } [[nodiscard]] constexpr size_type next_capacity() const noexcept { diff --git a/include/dice/sparse-map/sparse_growth_policy.hpp b/include/dice/sparse-map/sparse_growth_policy.hpp index faf10a8..f1ccfdd 100644 --- a/include/dice/sparse-map/sparse_growth_policy.hpp +++ b/include/dice/sparse-map/sparse_growth_policy.hpp @@ -33,8 +33,6 @@ #include #include -#include "dice/sparse-map/sparse_props.hpp" - namespace dice::sparse_map { template @@ -57,7 +55,7 @@ namespace dice::sparse_map { * * GrowthFactor must be a power of two >= 2. */ - template requires (detail::is_power_of_2(GrowthFactor) && GrowthFactor >= 2) + template requires (std::has_single_bit(GrowthFactor) && GrowthFactor >= 2) struct power_of_two_growth_policy { protected: std::size_t m_mask; @@ -77,7 +75,7 @@ namespace dice::sparse_map { } if (min_bucket_count_in_out > 0) { - min_bucket_count_in_out = detail::round_up_to_power_of_2(min_bucket_count_in_out); + min_bucket_count_in_out = std::bit_ceil(min_bucket_count_in_out); m_mask = min_bucket_count_in_out - 1; } else { m_mask = 0; diff --git a/include/dice/sparse-map/sparse_props.hpp b/include/dice/sparse-map/sparse_props.hpp index 666cf9c..d4c0c74 100644 --- a/include/dice/sparse-map/sparse_props.hpp +++ b/include/dice/sparse-map/sparse_props.hpp @@ -6,20 +6,6 @@ #include namespace dice::sparse_map { - namespace detail { - template - constexpr bool is_power_of_2(U x) noexcept { - return std::popcount(x) == 1; - } - - template - constexpr U round_up_to_power_of_2(U value) noexcept { - assert(value > 0); - auto const highest_bit_pos = sizeof(U) * 8 - std::countl_zero(value - 1); - return U{1} << highest_bit_pos; - } - } // namespace detail - enum struct probing : bool { linear, quadratic From f4ec70bd8f35e2e4b76df4b29f94d5ada020dbe2 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Thu, 3 Aug 2023 13:18:08 +0200 Subject: [PATCH 13/41] port to doctest --- CMakeLists.txt | 7 +- doxygen.conf | 2 +- include/dice/sparse-map/sparse_array.hpp | 24 +- .../dice/sparse-map/sparse_growth_policy.hpp | 2 +- include/dice/sparse-map/sparse_hash.hpp | 109 +- tests/CMakeLists.txt | 60 +- tests/custom_allocator_tests.cpp | 129 +- tests/fancy_pointer/CustomAllocator.hpp | 6 +- tests/fancy_pointer/sparse_array_tests.cpp | 151 +- tests/fancy_pointer/sparse_hash_map_tests.cpp | 173 +- tests/fancy_pointer/sparse_hash_set_tests.cpp | 268 +- tests/main.cpp | 26 - tests/policy_tests.cpp | 98 +- .../sparse_hash_set_tests.cpp | 16 +- tests/sparse_map_tests.cpp | 2371 ++++++++--------- tests/sparse_set_tests.cpp | 192 +- 16 files changed, 1770 insertions(+), 1864 deletions(-) delete mode 100644 tests/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 151bc84..3d3b4a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,4 +30,9 @@ if (IS_TOP_LEVEL) install_interface_library("${PROJECT_NAME}" "${PROJECT_NAME}" "${PROJECT_NAME}" "include") endif () - +if (BUILD_TESTING AND IS_TOP_LEVEL) + message("Building testing") + include(CTest) + enable_testing() + add_subdirectory(tests) +endif () diff --git a/doxygen.conf b/doxygen.conf index 115e07c..9e206f0 100644 --- a/doxygen.conf +++ b/doxygen.conf @@ -895,7 +895,7 @@ tsl::detail_popcount::* \ tsl::detail_sparse_hash::has_is_transparent* \ tsl::detail_sparse_hash::make_void* \ tsl::detail_sparse_hash::is_power_of_two_policy* \ -tsl::detail_sparse_hash::sparse_array* +tsl::detail_sparse_hash::sparse_array_type* # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include diff --git a/include/dice/sparse-map/sparse_array.hpp b/include/dice/sparse-map/sparse_array.hpp index 4e0bdca..66ee85a 100644 --- a/include/dice/sparse-map/sparse_array.hpp +++ b/include/dice/sparse-map/sparse_array.hpp @@ -6,12 +6,12 @@ namespace dice::sparse_map::detail { /** - * WARNING: the sparse_array class doesn't free the resources allocated through + * WARNING: the sparse_array_type class doesn't free the resources allocated through * the allocator passed in parameter in each method. You have to manually call - * `clear(Allocator&)` when you don't need a sparse_array object anymore. + * `clear(Allocator&)` when you don't need a sparse_array_type object anymore. * - * The reason is that the sparse_array doesn't store the allocator to avoid - * wasting space in each sparse_array when the allocator has a size > 0. It only + * The reason is that the sparse_array_type doesn't store the allocator to avoid + * wasting space in each sparse_array_type when the allocator has a size > 0. It only * allocates/deallocates objects with the allocator that is passed in parameter. * * @@ -22,7 +22,7 @@ namespace dice::sparse_map::detail { * * We are using raw pointers instead of std::vector to avoid loosing * 2*sizeof(size_t) bytes to store the capacity and size of the vector in each - * sparse_array. We know we can only store up to BITMAP_NB_BITS elements in the + * sparse_array_type. We know we can only store up to BITMAP_NB_BITS elements in the * array, we don't need such big types. * * @@ -84,7 +84,7 @@ namespace dice::sparse_map::detail { public: /** * Map an ibucket [0, bucket_count) in the hash table to a sparse_ibucket - * (a sparse_array holds multiple buckets, so there is less sparse_array than + * (a sparse_array_type holds multiple buckets, so there is less sparse_array_type than * bucket_count). * * The bucket ibucket is in @@ -97,7 +97,7 @@ namespace dice::sparse_map::detail { /** * Map an ibucket [0, bucket_count) in the hash table to an index in the - * sparse_array which corresponds to the bucket. + * sparse_array_type which corresponds to the bucket. * * The bucket ibucket is in * m_sparse_buckets[sparse_ibucket(ibucket)][index_in_sparse_bucket(ibucket)] @@ -223,8 +223,8 @@ namespace dice::sparse_map::detail { } - // The code that manages the sparse_array must have called clear before - // destruction. See documentation of sparse_array for more details. + // The code that manages the sparse_array_type must have called clear before + // destruction. See documentation of sparse_array_type for more details. ~sparse_array() noexcept = default; /** @@ -459,8 +459,10 @@ namespace dice::sparse_map::detail { } if constexpr (std::is_trivially_copyable_v) { - std::memcpy(&new_values[0], &m_values[0], offset * sizeof(value_type)); - std::memcpy(&new_values[offset + 1], &m_values[offset], (m_nb_elements - offset) * sizeof(value_type)); + if (m_values != nullptr) { + std::memcpy(&new_values[0], &m_values[0], offset * sizeof(value_type)); + std::memcpy(&new_values[offset + 1], &m_values[offset], (m_nb_elements - offset) * sizeof(value_type)); + } } else { // Cannot throw here as per requires clause for (size_type i = 0; i < offset; i++) { diff --git a/include/dice/sparse-map/sparse_growth_policy.hpp b/include/dice/sparse-map/sparse_growth_policy.hpp index f1ccfdd..74dcb78 100644 --- a/include/dice/sparse-map/sparse_growth_policy.hpp +++ b/include/dice/sparse-map/sparse_growth_policy.hpp @@ -242,7 +242,7 @@ namespace dice::sparse_map { } [[nodiscard]] constexpr std::size_t next_bucket_count() const { - if (m_iprime + 1 >= PRIMES.size()) { + if (static_cast(m_iprime) + 1 >= PRIMES.size()) { throw std::length_error("The hash table exceeds its maximum size."); } diff --git a/include/dice/sparse-map/sparse_hash.hpp b/include/dice/sparse-map/sparse_hash.hpp index 63616b3..129f376 100644 --- a/include/dice/sparse-map/sparse_hash.hpp +++ b/include/dice/sparse-map/sparse_hash.hpp @@ -40,7 +40,7 @@ #include #include -#include +#include #include "dice/sparse-map/sparse_growth_policy.hpp" #include "dice/sparse-map/sparse_array.hpp" @@ -76,13 +76,13 @@ namespace dice::sparse_map::detail { * * The class holds its buckets in a 2-dimensional fashion. Instead of having a * linear `std::vector` for [0, bucket_count) where each bucket stores - * one value, we have a `std::vector` (m_sparse_buckets_data) - * where each `sparse_array` stores multiple values (up to - * `sparse_array::BITMAP_NB_BITS`). To convert a one dimensional `ibucket` - * position to a position in `std::vector` and a position in - * `sparse_array`, use respectively the methods - * `sparse_array::sparse_ibucket(ibucket)` and - * `sparse_array::index_in_sparse_bucket(ibucket)`. + * one value, we have a `std::vector` (m_sparse_buckets_data) + * where each `sparse_array_type` stores multiple values (up to + * `sparse_array_type::BITMAP_NB_BITS`). To convert a one dimensional `ibucket` + * position to a position in `std::vector` and a position in + * `sparse_array_type`, use respectively the methods + * `sparse_array_type::sparse_ibucket(ibucket)` and + * `sparse_array_type::index_in_sparse_bucket(ibucket)`. */ template; using const_iterator = sparse_iterator; + static constexpr size_type DEFAULT_INIT_BUCKET_COUNT = 0; + static constexpr float DEFAULT_MAX_LOAD_FACTOR = 0.5f; + private: static constexpr bool has_mapped_type = !std::is_same_v; - using sparse_array = sparse_array; + using sparse_array_type = sparse_array; + + using sparse_buckets_allocator = typename std::allocator_traits::template rebind_alloc; + using sparse_buckets_container = boost::interprocess::vector; - using sparse_buckets_allocator = typename std::allocator_traits::template rebind_alloc; - using sparse_buckets_container = boost::container::vector; + private: + sparse_buckets_container m_sparse_buckets_data; + + size_type m_bucket_count; + size_type m_nb_elements; + size_type m_nb_deleted_buckets; + + /** + * Maximum that m_nb_elements can reach before a rehash occurs automatically + * to grow the hash table. + */ + size_type m_load_threshold_rehash; + + /** + * Maximum that m_nb_elements + m_nb_deleted_buckets can reach before cleaning + * up the buckets marked as deleted. + */ + size_type m_load_threshold_clear_deleted; + float m_max_load_factor; + + [[no_unique_address]] allocator_type m_alloc; + [[no_unique_address]] hasher m_h; + [[no_unique_address]] key_equal m_keq; + [[no_unique_address]] growth_policy m_gpol; public: template @@ -150,8 +178,8 @@ namespace dice::sparse_map::detail { typename sparse_buckets_container::iterator>; using sparse_array_iterator = std::conditional_t; + typename sparse_array_type::const_iterator, + typename sparse_array_type::iterator>; private: sparse_bucket_iterator m_sparse_buckets_it; @@ -240,7 +268,7 @@ namespace dice::sparse_map::detail { auto it_sparse_buckets = m_sparse_buckets_data.begin() + std::distance(m_sparse_buckets_data.cbegin(), pos.m_sparse_buckets_it); // SAFETY: this is non-const therefore the underlying sparse array is also non-const - auto it_array = sparse_array::unsafe_mutable_iterator(pos.m_sparse_array_it); + auto it_array = sparse_array_type::unsafe_mutable_iterator(pos.m_sparse_array_it); return iterator{it_sparse_buckets, m_sparse_buckets_data.end(), it_array}; } @@ -260,7 +288,7 @@ namespace dice::sparse_map::detail { } if (m_bucket_count > 0) { - m_sparse_buckets_data.resize(sparse_array::nb_sparse_buckets(bucket_count)); + m_sparse_buckets_data.resize(sparse_array_type::nb_sparse_buckets(bucket_count)); assert(!m_sparse_buckets_data.empty()); } @@ -755,7 +783,7 @@ namespace dice::sparse_map::detail { private: size_type bucket_for_hash(std::size_t hash) const { auto const bucket = m_gpol.bucket_for_hash(hash); - assert(sparse_array::sparse_ibucket(bucket) < m_sparse_buckets_data.size() + assert(sparse_array_type::sparse_ibucket(bucket) < m_sparse_buckets_data.size() || (bucket == 0 && m_sparse_buckets_data.empty())); return bucket; @@ -829,16 +857,16 @@ namespace dice::sparse_map::detail { */ bool found_first_deleted_bucket = false; std::size_t sparse_ibucket_first_deleted = 0; - typename sparse_array::size_type index_in_sparse_bucket_first_deleted = 0; + typename sparse_array_type::size_type index_in_sparse_bucket_first_deleted = 0; const std::size_t hash = m_h(key); std::size_t ibucket = bucket_for_hash(hash); std::size_t probe = 0; while (true) { - std::size_t sparse_ibucket = sparse_array::sparse_ibucket(ibucket); + std::size_t sparse_ibucket = sparse_array_type::sparse_ibucket(ibucket); auto index_in_sparse_bucket = - sparse_array::index_in_sparse_bucket(ibucket); + sparse_array_type::index_in_sparse_bucket(ibucket); if (!m_sparse_buckets_data.empty()) { if (m_sparse_buckets_data[sparse_ibucket].has_value(index_in_sparse_bucket)) { @@ -878,7 +906,7 @@ namespace dice::sparse_map::detail { template std::pair insert_in_bucket(std::size_t sparse_ibucket, - typename sparse_array::size_type index_in_sparse_bucket, + typename sparse_array_type::size_type index_in_sparse_bucket, Args &&...value_type_args) { // is not called when empty auto value_it = m_sparse_buckets_data[sparse_ibucket].set(m_alloc, index_in_sparse_bucket, std::forward(value_type_args)...); @@ -900,8 +928,8 @@ namespace dice::sparse_map::detail { std::size_t probe = 0; while (true) { - auto const sparse_ibucket = sparse_array::sparse_ibucket(ibucket); - auto const index_in_sparse_bucket = sparse_array::index_in_sparse_bucket(ibucket); + auto const sparse_ibucket = sparse_array_type::sparse_ibucket(ibucket); + auto const index_in_sparse_bucket = sparse_array_type::index_in_sparse_bucket(ibucket); auto &bucket = m_sparse_buckets_data[sparse_ibucket]; @@ -934,8 +962,8 @@ namespace dice::sparse_map::detail { std::size_t probe = 0; while (true) { - auto const sparse_ibucket = sparse_array::sparse_ibucket(ibucket); - auto const index_in_sparse_bucket = sparse_array::index_in_sparse_bucket(ibucket); + auto const sparse_ibucket = sparse_array_type::sparse_ibucket(ibucket); + auto const index_in_sparse_bucket = sparse_array_type::index_in_sparse_bucket(ibucket); auto &bucket = self.m_sparse_buckets_data[sparse_ibucket]; @@ -1014,8 +1042,8 @@ namespace dice::sparse_map::detail { std::size_t probe = 0; while (true) { - auto const sparse_ibucket = sparse_array::sparse_ibucket(ibucket); - auto const index_in_sparse_bucket = sparse_array::index_in_sparse_bucket(ibucket); + auto const sparse_ibucket = sparse_array_type::sparse_ibucket(ibucket); + auto const index_in_sparse_bucket = sparse_array_type::index_in_sparse_bucket(ibucket); auto &bucket = m_sparse_buckets_data[sparse_ibucket]; @@ -1032,35 +1060,6 @@ namespace dice::sparse_map::detail { ibucket = next_bucket(ibucket, probe); } } - - public: - static constexpr size_type DEFAULT_INIT_BUCKET_COUNT = 0; - static constexpr float DEFAULT_MAX_LOAD_FACTOR = 0.5f; - - private: - sparse_buckets_container m_sparse_buckets_data; - - size_type m_bucket_count; - size_type m_nb_elements; - size_type m_nb_deleted_buckets; - - /** - * Maximum that m_nb_elements can reach before a rehash occurs automatically - * to grow the hash table. - */ - size_type m_load_threshold_rehash; - - /** - * Maximum that m_nb_elements + m_nb_deleted_buckets can reach before cleaning - * up the buckets marked as deleted. - */ - size_type m_load_threshold_clear_deleted; - float m_max_load_factor; - - [[no_unique_address]] allocator_type m_alloc; - [[no_unique_address]] hasher m_h; - [[no_unique_address]] key_equal m_keq; - [[no_unique_address]] growth_policy m_gpol; }; }// namespace dice::sparse_map diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 67e57b0..21f9963 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,30 +1,38 @@ -cmake_minimum_required(VERSION 3.18) +include(FetchContent) +FetchContent_Declare( + DocTest + GIT_REPOSITORY "https://github.com/doctest/doctest.git" + GIT_TAG "v2.4.11" + GIT_SHALLOW TRUE) +FetchContent_MakeAvailable(DocTest) -project(tsl_sparse_map_tests) +macro(make_test DIR NAME) + if ("${DIR}" STREQUAL ".") + set(TARGET ${NAME}) + else () + set(TARGET ${DIR}-${NAME}) + endif () -add_executable(tsl_sparse_map_tests "main.cpp" - "custom_allocator_tests.cpp" - "policy_tests.cpp" - "sparse_map_tests.cpp" - "sparse_set_tests.cpp" - "fancy_pointer/sparse_hash_map_tests.cpp" - "fancy_pointer/sparse_hash_set_tests.cpp" - "scoped_allocator_adaptor/sparse_hash_set_tests.cpp" - ) + add_executable(${TARGET} ${DIR}/${NAME}.cpp) + target_link_libraries(${TARGET} + doctest::doctest + dice-sparse-map::dice-sparse-map + ) + set_property(TARGET ${TARGET} PROPERTY CXX_STANDARD 20) + add_test(NAME ${TARGET} COMMAND ${TARGET}) -target_compile_features(tsl_sparse_map_tests PRIVATE cxx_std_20) + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") + target_compile_options(${TARGET} PRIVATE -Werror -Wall -Wextra -Wold-style-cast -DTSL_DEBUG -UNDEBUG) + elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + target_compile_options(${TARGET} PRIVATE /bigobj /WX /W3 /DTSL_DEBUG /UNDEBUG) + endif() +endmacro () -if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") - target_compile_options(tsl_sparse_map_tests PRIVATE -Werror -Wall -Wextra -Wold-style-cast -DTSL_DEBUG -UNDEBUG) -elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - target_compile_options(tsl_sparse_map_tests PRIVATE /bigobj /WX /W3 /DTSL_DEBUG /UNDEBUG) -endif() - -# Boost::unit_test_framework -set(Boost_USE_STATIC_LIBS ON) -find_package(Boost 1.54.0 REQUIRED COMPONENTS unit_test_framework) -target_link_libraries(tsl_sparse_map_tests PRIVATE Boost::unit_test_framework) - -# dice-sparse-map::dice-sparse-map -add_subdirectory(../ ${CMAKE_CURRENT_BINARY_DIR}/Dice/sparse-map) -target_link_libraries(tsl_sparse_map_tests PRIVATE dice-sparse-map::dice-sparse-map) +make_test(. custom_allocator_tests) +make_test(. policy_tests) +make_test(. sparse_map_tests) +make_test(. sparse_set_tests) +make_test(fancy_pointer sparse_array_tests) +make_test(fancy_pointer sparse_hash_map_tests) +make_test(fancy_pointer sparse_hash_set_tests) +make_test(scoped_allocator_adaptor sparse_hash_set_tests) diff --git a/tests/custom_allocator_tests.cpp b/tests/custom_allocator_tests.cpp index 2fc7395..ec89b66 100644 --- a/tests/custom_allocator_tests.cpp +++ b/tests/custom_allocator_tests.cpp @@ -21,85 +21,80 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -#define BOOST_TEST_DYN_LINK +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include #include - -#include -#include #include #include #include -#include #include #include -#include "utils.h" - static std::size_t nb_custom_allocs = 0; -template +template class custom_allocator { - public: - using value_type = T; - using pointer = T*; - using const_pointer = const T*; - using reference = T&; - using const_reference = const T&; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - using propagate_on_container_move_assignment = std::true_type; +public: + using value_type = T; + using pointer = T *; + using const_pointer = const T *; + using reference = T &; + using const_reference = const T &; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using propagate_on_container_move_assignment = std::true_type; - template - struct rebind { - using other = custom_allocator; - }; + template + struct rebind { + using other = custom_allocator; + }; - custom_allocator() = default; + custom_allocator() = default; - template - custom_allocator(const custom_allocator&) {} + template + custom_allocator(const custom_allocator &) {} - pointer address(reference x) const noexcept { return &x; } + pointer address(reference x) const noexcept { return &x; } - const_pointer address(const_reference x) const noexcept { return &x; } + const_pointer address(const_reference x) const noexcept { return &x; } - pointer allocate(size_type n, const void* /*hint*/ = 0) { - nb_custom_allocs++; + pointer allocate(size_type n, const void * /*hint*/ = 0) { + nb_custom_allocs++; - pointer ptr = static_cast(std::malloc(n * sizeof(T))); - if (ptr == nullptr) { - throw std::bad_alloc(); - } + pointer ptr = static_cast(std::malloc(n * sizeof(T))); + if (ptr == nullptr) { + throw std::bad_alloc(); + } - return ptr; - } + return ptr; + } - void deallocate(T* p, size_type /*n*/) { std::free(p); } + void deallocate(T *p, size_type /*n*/) { std::free(p); } - size_type max_size() const noexcept { - return std::numeric_limits::max() / sizeof(value_type); - } + size_type max_size() const noexcept { + return std::numeric_limits::max() / sizeof(value_type); + } - template - void construct(U* p, Args&&... args) { - ::new (static_cast(p)) U(std::forward(args)...); - } + template + void construct(U *p, Args &&...args) { + ::new (static_cast(p)) U(std::forward(args)...); + } - template - void destroy(U* p) { - p->~U(); - } + template + void destroy(U *p) { + p->~U(); + } }; -template -bool operator==(const custom_allocator&, const custom_allocator&) { - return true; +template +bool operator==(const custom_allocator &, const custom_allocator &) { + return true; } -template -bool operator!=(const custom_allocator&, const custom_allocator&) { - return false; +template +bool operator!=(const custom_allocator &, const custom_allocator &) { + return false; } // TODO Avoid overloading new to check number of global new @@ -113,23 +108,21 @@ bool operator!=(const custom_allocator&, const custom_allocator&) { // std::free(ptr); // } -BOOST_AUTO_TEST_SUITE(test_custom_allocator) +TEST_SUITE("custom allocator") { + TEST_CASE("test_1") { + // nb_global_new = 0; + nb_custom_allocs = 0; -BOOST_AUTO_TEST_CASE(test_custom_allocator_1) { - // nb_global_new = 0; - nb_custom_allocs = 0; + dice::sparse_map::sparse_map, std::equal_to, + custom_allocator>> + map; - dice::sparse_map::sparse_map, std::equal_to, - custom_allocator>> - map; + const int nb_elements = 1000; + for (int i = 0; i < nb_elements; i++) { + map.insert({i, i * 2}); + } - const int nb_elements = 1000; - for (int i = 0; i < nb_elements; i++) { - map.insert({i, i * 2}); - } - - BOOST_CHECK_NE(nb_custom_allocs, 0); - // BOOST_CHECK_EQUAL(nb_global_new, 0); + CHECK_NE(nb_custom_allocs, 0); + // BOOST_CHECK_EQUAL(nb_global_new, 0); + } } - -BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/fancy_pointer/CustomAllocator.hpp b/tests/fancy_pointer/CustomAllocator.hpp index 733adb5..4fc18e8 100644 --- a/tests/fancy_pointer/CustomAllocator.hpp +++ b/tests/fancy_pointer/CustomAllocator.hpp @@ -2,8 +2,8 @@ * @bief Home of a custom allocator for testing with fancy pointers. */ -#ifndef TSL_SPARSE_MAP_TESTS_CUSTOMALLOCATOR_HPP -#define TSL_SPARSE_MAP_TESTS_CUSTOMALLOCATOR_HPP +#ifndef DICE_SPARSE_MAP_TESTS_CUSTOMALLOCATOR_HPP +#define DICE_SPARSE_MAP_TESTS_CUSTOMALLOCATOR_HPP #include @@ -43,4 +43,4 @@ struct OffsetAllocator { } }; -#endif //TSL_SPARSE_MAP_TESTS_CUSTOMALLOCATOR_HPP \ No newline at end of file +#endif //DICE_SPARSE_MAP_TESTS_CUSTOMALLOCATOR_HPP \ No newline at end of file diff --git a/tests/fancy_pointer/sparse_array_tests.cpp b/tests/fancy_pointer/sparse_array_tests.cpp index 29d6329..4697d55 100644 --- a/tests/fancy_pointer/sparse_array_tests.cpp +++ b/tests/fancy_pointer/sparse_array_tests.cpp @@ -1,32 +1,16 @@ /** @file - * @brief Checks for fancy pointer support in the sparse_array implementation. + * @brief Checks for fancy pointer support in the sparse_array_type implementation. */ -#include +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include + #include #include "CustomAllocator.hpp" // Globals constexpr auto MAX_INDEX = 32; //BITMAP_NB_BITS -/* Tests are formulated via templates to reduce code duplication. - * The template parameter contains the Allocator type and the shorthand "Array" for the sparse_array - * (with all template parameter already inserted). - */ - -template -void compilation() { - typename T::Array test; - (void) test; -} - -template -void construction() { - typename T::Allocator a; - typename T::Array test(MAX_INDEX, a); - test.clear(a); //needed because destructor asserts -} - namespace details { template typename T::Array generate_test_array(typename T::Allocator &a) { @@ -47,55 +31,6 @@ namespace details { } } -template -void set() { - typename T::Allocator a; - auto test = details::generate_test_array(a); - auto check = details::generate_check_for_test_array(); - //'set' did not create the correct order of items - BOOST_REQUIRE(std::equal(test.begin(), test.end(), check.begin())); - test.clear(a); //needed because destructor asserts -} - -template -void copy_construction() { - typename T::Allocator a; - //needs to be its own line, otherwise the move-construction would take place - auto test = details::generate_test_array(a); - typename T::Array copy(test, a); - auto check = details::generate_check_for_test_array(); - //'copy' changed the order of the items - BOOST_REQUIRE(std::equal(copy.begin(), copy.end(), check.begin())); - test.clear(a); - copy.clear(a); -} - -template -void move_construction() { - typename T::Allocator a; - //two lines needed. Otherwise move/copy elision - auto moved_from = details::generate_test_array(a); - typename T::Array moved_to(std::move(moved_from)); - auto check = details::generate_check_for_test_array(); - //'move' changed the order of the items - BOOST_REQUIRE(std::equal(moved_to.begin(), moved_to.end(), check.begin())); - moved_to.clear(a); -} - -template -void const_iterator() { - typename T::Allocator a; - auto test = details::generate_test_array(a); - auto const_iter = test.cbegin(); - //const iterator has the wrong type - BOOST_REQUIRE((std::is_same::value)); - test.clear(a); -} - - -/* - * This are the types you can give the tests as template parameters. - */ template struct STD { using Allocator = std::allocator; @@ -113,26 +48,58 @@ struct CUSTOM { }; - -/* The instantiation of the tests. - * I don't use the boost template test cases because with this I can set the title of every test case myself. - */ -BOOST_AUTO_TEST_SUITE(fancy_pointers) -BOOST_AUTO_TEST_SUITE(sparse_array_tests) - -BOOST_AUTO_TEST_CASE(std_alloc_compile) {compilation>();} -BOOST_AUTO_TEST_CASE(std_alloc_construction) {construction>();} -BOOST_AUTO_TEST_CASE(std_alloc_set) {set>();} -BOOST_AUTO_TEST_CASE(std_alloc_copy_construction) {copy_construction>();} -BOOST_AUTO_TEST_CASE(std_alloc_move_construction) {move_construction>();} -BOOST_AUTO_TEST_CASE(std_const_iterator) {const_iterator>();} - -BOOST_AUTO_TEST_CASE(custom_alloc_compile) {compilation>();} -BOOST_AUTO_TEST_CASE(custom_alloc_construction) {construction>();} -BOOST_AUTO_TEST_CASE(custom_alloc_set) {set>();} -BOOST_AUTO_TEST_CASE(custom_alloc_copy_construction) {copy_construction>();} -BOOST_AUTO_TEST_CASE(custom_alloc_move_construction) {move_construction>();} -BOOST_AUTO_TEST_CASE(custom_const_iterator) {const_iterator>();} - -BOOST_AUTO_TEST_SUITE_END() -BOOST_AUTO_TEST_SUITE_END() +#define TEST_ARRAYS STD, CUSTOM + +TEST_SUITE("sparse array with fancy pointers") { + TEST_CASE_TEMPLATE("compile", T, TEST_ARRAYS) { + typename T::Array test; + (void) test; + } + + TEST_CASE_TEMPLATE("construction", T, TEST_ARRAYS) { + typename T::Allocator a; + typename T::Array test(MAX_INDEX, a); + test.clear(a); //needed because destructor asserts + } + + TEST_CASE_TEMPLATE("set", T, TEST_ARRAYS) { + typename T::Allocator a; + auto test = details::generate_test_array(a); + auto check = details::generate_check_for_test_array(); + //'set' did not create the correct order of items + REQUIRE(std::equal(test.begin(), test.end(), check.begin())); + test.clear(a); //needed because destructor asserts + } + + TEST_CASE_TEMPLATE("copy ctor", T, TEST_ARRAYS) { + typename T::Allocator a; + //needs to be its own line, otherwise the move-construction would take place + auto test = details::generate_test_array(a); + typename T::Array copy(test, a); + auto check = details::generate_check_for_test_array(); + //'copy' changed the order of the items + REQUIRE(std::equal(copy.begin(), copy.end(), check.begin())); + test.clear(a); + copy.clear(a); + } + + TEST_CASE_TEMPLATE("move ctor", T, TEST_ARRAYS) { + typename T::Allocator a; + //two lines needed. Otherwise move/copy elision + auto moved_from = details::generate_test_array(a); + typename T::Array moved_to(std::move(moved_from)); + auto check = details::generate_check_for_test_array(); + //'move' changed the order of the items + REQUIRE(std::equal(moved_to.begin(), moved_to.end(), check.begin())); + moved_to.clear(a); + } + + TEST_CASE_TEMPLATE("const iterator", T, TEST_ARRAYS) { + typename T::Allocator a; + auto test = details::generate_test_array(a); + auto const_iter = test.cbegin(); + //const iterator has the wrong type + REQUIRE((std::is_same::value)); + test.clear(a); + } +} diff --git a/tests/fancy_pointer/sparse_hash_map_tests.cpp b/tests/fancy_pointer/sparse_hash_map_tests.cpp index ab161c7..18ffb74 100644 --- a/tests/fancy_pointer/sparse_hash_map_tests.cpp +++ b/tests/fancy_pointer/sparse_hash_map_tests.cpp @@ -2,8 +2,10 @@ * @brief Checks for fancy pointer support in the sparse_hash implementation for pair values (maps). */ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include + #include -#include #include #include #include "CustomAllocator.hpp" @@ -83,62 +85,6 @@ namespace details { } } -template -void construction() { - auto map = details::default_construct_map(); -} - -template -void insert(std::initializer_list l) { - auto map = details::default_construct_map(); - for (auto dataPair : l) map.insert(dataPair); - //'insert' did not create exactly the values needed - BOOST_REQUIRE(details::is_equal(map, l)); -} - -template -void iterator_insert(std::initializer_list l) { - auto map = details::default_construct_map(); - map.insert(l.begin(), l.end()); - //'insert' with iterators did not create exactly the values needed - BOOST_REQUIRE(details::is_equal(map, l)); -} - -template -void iterator_access(typename T::value_type single_value) { - auto map = details::default_construct_map(); - map.insert(single_value); - //iterator cannot access single value - BOOST_REQUIRE((*map.begin()).first == single_value.first && (*map.begin()).second == single_value.second); -} - -template -void iterator_access_multi(std::initializer_list l) { - auto map = details::default_construct_map(); - map.insert(l.begin(), l.end()); - std::vector l_sorted = l; - std::vector map_sorted(map.begin(), map.end()); - std::sort(l_sorted.begin(), l_sorted.end()); - std::sort(map_sorted.begin(), map_sorted.end()); - //iterating over the map didn't work - BOOST_REQUIRE(std::equal(l_sorted.begin(), l_sorted.end(), - map_sorted.begin())); -} - -template -void value(std::initializer_list l, typename T::value_type to_change) { - auto map = details::default_construct_map(); - map.insert(l.begin(), l.end()); - map[to_change.first] = to_change.second; - - std::unordered_map check(l.begin(), l.end()); - check[to_change.first] = to_change.second; - - //changing a single value didn't work - BOOST_REQUIRE(details::is_equal(map, check)); -} - - template struct STD { using key_type = Key; @@ -156,38 +102,83 @@ struct CUSTOM { }; -BOOST_AUTO_TEST_SUITE(fancy_pointers) -BOOST_AUTO_TEST_SUITE(sparse_hash_map_tests) - -BOOST_AUTO_TEST_CASE(std_alloc_compiles) {construction>();} -BOOST_AUTO_TEST_CASE(std_alloc_insert) {insert>({{1,2},{3,4},{5,6}});} -BOOST_AUTO_TEST_CASE(std_alloc_iterator_insert) {insert>({{1,2},{3,4},{5,6}});} -BOOST_AUTO_TEST_CASE(std_alloc_iterator_access) {iterator_access>({1,42});} -BOOST_AUTO_TEST_CASE(std_alloc_iterator_access_multi) {iterator_access_multi>({{1,2},{3,4},{5,6}});} -BOOST_AUTO_TEST_CASE(std_alloc_value) {value>({{1,2},{3,4},{5,6}}, {1, 42});} - -BOOST_AUTO_TEST_CASE(custom_alloc_compiles) {construction>();} -BOOST_AUTO_TEST_CASE(custom_alloc_insert) {insert>({{1,2},{3,4},{5,6}});} -BOOST_AUTO_TEST_CASE(custom_alloc_iterator_insert) {insert>({{1,2},{3,4},{5,6}});} -BOOST_AUTO_TEST_CASE(custom_alloc_iterator_access) {iterator_access>({1,42});} -BOOST_AUTO_TEST_CASE(custom_alloc_iterator_access_multi) {iterator_access_multi>({{1,2},{3,4},{5,6}});} -BOOST_AUTO_TEST_CASE(custom_alloc_value) {value>({{1,2},{3,4},{5,6}}, {1, 42});} - -BOOST_AUTO_TEST_CASE(full_map) { - dice::sparse_map::sparse_map, std::equal_to, OffsetAllocator>> map; - std::vector> data = { - {0,1},{2,3},{4,5},{6,7},{8,9} - }; - map.insert(data.begin(), data.end()); - auto check = [&map](std::pair p) { - if (!map.contains(p.first)) return false; - return map.at(p.first) == p.second; - }; - //size did not match - BOOST_REQUIRE(data.size() == map.size()); - //map did not contain all values - BOOST_REQUIRE(std::all_of(data.begin(), data.end(), check)); +#define TEST_MAPS STD, CUSTOM + +TEST_SUITE("sparse map with fancy pointers") { + TEST_CASE_TEMPLATE("construction", T, TEST_MAPS) { + auto map = details::default_construct_map(); + } + + TEST_CASE_TEMPLATE("insert", T, TEST_MAPS) { + std::initializer_list l{{1,2},{3,4},{5,6}}; + + auto map = details::default_construct_map(); + for (auto dataPair : l) map.insert(dataPair); + //'insert' did not create exactly the values needed + REQUIRE(details::is_equal(map, l)); + } + + TEST_CASE_TEMPLATE("iter insert", T, TEST_MAPS) { + std::initializer_list l{{1,2},{3,4},{5,6}}; + + auto map = details::default_construct_map(); + map.insert(l.begin(), l.end()); + //'insert' with iterators did not create exactly the values needed + REQUIRE(details::is_equal(map, l)); + } + + TEST_CASE_TEMPLATE("iter access", T, TEST_MAPS) { + typename T::value_type single_value{1,42}; + + auto map = details::default_construct_map(); + map.insert(single_value); + //iterator cannot access single value + REQUIRE((*map.begin()).first == single_value.first); + REQUIRE((*map.begin()).second == single_value.second); + } + + TEST_CASE_TEMPLATE("iter access multi", T, TEST_MAPS) { + std::initializer_list l{{1,2},{3,4},{5,6}}; + + auto map = details::default_construct_map(); + map.insert(l.begin(), l.end()); + std::vector l_sorted = l; + std::vector map_sorted(map.begin(), map.end()); + std::sort(l_sorted.begin(), l_sorted.end()); + std::sort(map_sorted.begin(), map_sorted.end()); + //iterating over the map didn't work + REQUIRE(std::equal(l_sorted.begin(), l_sorted.end(), + map_sorted.begin())); + } + + TEST_CASE_TEMPLATE("value", T, TEST_MAPS) { + typename T::value_type to_change{1, 42}; + std::initializer_list l{{1,2},{3,4},{5,6}}; + + auto map = details::default_construct_map(); + map.insert(l.begin(), l.end()); + map[to_change.first] = to_change.second; + + std::unordered_map check(l.begin(), l.end()); + check[to_change.first] = to_change.second; + + //changing a single value didn't work + REQUIRE(details::is_equal(map, check)); + } + + TEST_CASE("full map") { + dice::sparse_map::sparse_map, std::equal_to, OffsetAllocator>> map; + std::vector> data = { + {0,1},{2,3},{4,5},{6,7},{8,9} + }; + map.insert(data.begin(), data.end()); + auto check = [&map](std::pair p) { + if (!map.contains(p.first)) return false; + return map.at(p.first) == p.second; + }; + //size did not match + REQUIRE(data.size() == map.size()); + //map did not contain all values + REQUIRE(std::all_of(data.begin(), data.end(), check)); + } } - -BOOST_AUTO_TEST_SUITE_END() -BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/fancy_pointer/sparse_hash_set_tests.cpp b/tests/fancy_pointer/sparse_hash_set_tests.cpp index 46ffbb8..0e135c6 100644 --- a/tests/fancy_pointer/sparse_hash_set_tests.cpp +++ b/tests/fancy_pointer/sparse_hash_set_tests.cpp @@ -2,7 +2,9 @@ * @brief Checks for fancy pointer support in the sparse_hash implementation for single values (sets). */ -#include +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include + #include #include #include "CustomAllocator.hpp" @@ -28,11 +30,9 @@ namespace details { dice::sparse_map::sparsity::medium, dice::sparse_map::probing::quadratic>; - template - typename T::Set default_construct_set() { - using Type = typename T::value_type; - return typename T::Set(T::Set::DEFAULT_INIT_BUCKET_COUNT, std::hash(), std::equal_to(), - typename T::Allocator(), T::Set::DEFAULT_MAX_LOAD_FACTOR); + template + Set default_construct_set() { + return Set{Set::DEFAULT_INIT_BUCKET_COUNT, {}, {}, {}, Set::DEFAULT_MAX_LOAD_FACTOR}; } /** checks if all values of the set are in the initializer_list and than if the lengths are equal. @@ -46,99 +46,6 @@ namespace details { } } -template -void construction() { - auto set = details::default_construct_set(); -} - -template -void insert(std::initializer_list l) { - auto set = details::default_construct_set(); - for (auto const& i: l) set.insert(i); - //'insert' did not create exactly the values needed - BOOST_REQUIRE(details::is_equal(set, l)); -} - -template -void iterator_insert(std::initializer_list l) { - auto set = details::default_construct_set(); - set.insert(l.begin(), l.end()); - //'insert' with iterators did not create exactly the values needed - BOOST_REQUIRE(details::is_equal(set, l)); -} - -template -void iterator_access(typename T::value_type single_value) { - auto set = details::default_construct_set(); - set.insert(single_value); - //iterator cannot access single value - BOOST_REQUIRE(*(set.begin()) == single_value); -} - -template -void iterator_access_multi(std::initializer_list l) { - auto set = details::default_construct_set(); - set.insert(l.begin(), l.end()); - std::vector l_sorted = l; - std::vector set_sorted(set.begin(), set.end()); - std::sort(l_sorted.begin(), l_sorted.end()); - std::sort(set_sorted.begin(), set_sorted.end()); - //iterating over the set didn't work - BOOST_REQUIRE(std::equal(l_sorted.begin(), l_sorted.end(), - set_sorted.begin())); -} - - -template -void const_iterator_access_multi(std::initializer_list l) { - auto set = details::default_construct_set(); - set.insert(l.begin(), l.end()); - std::vector l_sorted = l; - std::vector set_sorted(set.cbegin(), set.cend()); - std::sort(l_sorted.begin(), l_sorted.end()); - std::sort(set_sorted.begin(), set_sorted.end()); - //const iterating over the set didn't work - BOOST_REQUIRE(std::equal(l_sorted.begin(), l_sorted.end(), - set_sorted.begin())); -} - -template -void find(std::initializer_list l, typename T::value_type search_value, bool is_in_list) { - auto set = details::default_construct_set(); - set.insert(l.begin(), l.end()); - auto iter = set.find(search_value); - bool found = iter != set.end(); - //find did not work as expected - BOOST_REQUIRE((found == is_in_list)); -} - -template -void erase(std::initializer_list l, typename T::value_type extra_value) { - auto set = details::default_construct_set(); - set.insert(extra_value); - set.insert(l.begin(), l.end()); - // force non-const iterator - auto iter = set.begin(); - for(; *iter != extra_value; ++iter); - set.erase(iter); - //erase did not work as expected - BOOST_REQUIRE(details::is_equal(set, l)); -} - -template -void erase_with_const_iter(std::initializer_list l, typename T::value_type extra_value) { - auto set = details::default_construct_set(); - set.insert(extra_value); - set.insert(l.begin(), l.end()); - //force const iterator - auto iter = set.cbegin(); - for(; *iter != extra_value; ++iter); - set.erase(iter); - //erase did not work as expected - BOOST_REQUIRE(details::is_equal(set, l)); -} - - template struct STD { using value_type = T; @@ -146,49 +53,124 @@ struct STD { using Set = details::sparse_set; }; -template -struct CUSTOM { - using value_type = T; - using Allocator = OffsetAllocator; - using Set = details::sparse_set; -}; - -BOOST_AUTO_TEST_SUITE(fancy_pointers) -BOOST_AUTO_TEST_SUITE(sparse_hash_set_tests) - -BOOST_AUTO_TEST_CASE(std_alloc_compiles) {construction>();} -BOOST_AUTO_TEST_CASE(std_alloc_insert) {insert>({1,2,3,4});} -BOOST_AUTO_TEST_CASE(std_alloc_iterator_insert) {iterator_insert>({1,2,3,4});} -BOOST_AUTO_TEST_CASE(std_alloc_iterator_access) {iterator_access>(42);} -BOOST_AUTO_TEST_CASE(std_alloc_iterator_access_multi) {iterator_access_multi>({1,2,3,4});} -BOOST_AUTO_TEST_CASE(std_alloc_const_iterator_access_multi) {const_iterator_access_multi>({1,2,3,4});} -BOOST_AUTO_TEST_CASE(std_find_true) {find>({1,2,3,4}, 4, true);} -BOOST_AUTO_TEST_CASE(std_find_false) {find>({1,2,3,4}, 5, false);} -BOOST_AUTO_TEST_CASE(std_erase) {erase>({1,2,3,4}, 5);} -BOOST_AUTO_TEST_CASE(std_erase_with_const_iter) {erase_with_const_iter>({1,2,3,4}, 5);} - -BOOST_AUTO_TEST_CASE(custom_alloc_compiles) {construction>();} -BOOST_AUTO_TEST_CASE(custom_alloc_insert) {insert>({1,2,3,4});} -BOOST_AUTO_TEST_CASE(custom_alloc_iterator_insert) {iterator_insert>({1,2,3,4});} -BOOST_AUTO_TEST_CASE(custom_alloc_iterator_access) {iterator_access>(42);} -BOOST_AUTO_TEST_CASE(custom_alloc_iterator_access_multi) {iterator_access_multi>({1,2,3,4});} -BOOST_AUTO_TEST_CASE(custom_alloc_const_iterator_access_multi) {const_iterator_access_multi>({1,2,3,4});} -BOOST_AUTO_TEST_CASE(custom_find_true) {find>({1,2,3,4}, 4, true);} -BOOST_AUTO_TEST_CASE(custom_find_false) {find>({1,2,3,4}, 5, false);} -BOOST_AUTO_TEST_CASE(custom_erase) {erase>({1,2,3,4}, 5);} -BOOST_AUTO_TEST_CASE(custom_erase_with_const_iter) {erase_with_const_iter>({1,2,3,4}, 5);} - -BOOST_AUTO_TEST_CASE(full_set) { - dice::sparse_map::sparse_set, std::equal_to, OffsetAllocator> set; - std::vector data = {1,2,3,4,5,6,7,8,9}; - set.insert(data.begin(), data.end()); - auto check = [&set](int d) {return set.contains(d);}; - //size did not match - BOOST_REQUIRE(data.size() == set.size()); - //Set did not contain all values - BOOST_REQUIRE(std::all_of(data.begin(), data.end(), check)); +#define TEST_TYPES details::sparse_set>, \ + details::sparse_set> + +TEST_SUITE("sparse set with fancy pointers") { + TEST_CASE_TEMPLATE("construction", T, TEST_TYPES) { + auto set = details::default_construct_set(); + } + + TEST_CASE_TEMPLATE("insert", T, TEST_TYPES) { + std::initializer_list l{1,2,3,4}; + + auto set = details::default_construct_set(); + for (auto const& i: l) set.insert(i); + //'insert' did not create exactly the values needed + REQUIRE(details::is_equal(set, l)); + } + + TEST_CASE_TEMPLATE("iter insert", T, TEST_TYPES) { + std::initializer_list l{1,2,3,4}; + + auto set = details::default_construct_set(); + set.insert(l.begin(), l.end()); + //'insert' with iterators did not create exactly the values needed + REQUIRE(details::is_equal(set, l)); + } + + TEST_CASE_TEMPLATE("iter access", T, TEST_TYPES) { + typename T::value_type single_value = 42; + + auto set = details::default_construct_set(); + set.insert(single_value); + //iterator cannot access single value + REQUIRE(*(set.begin()) == single_value); + } + + TEST_CASE_TEMPLATE("iter access multi", T, TEST_TYPES) { + std::initializer_list l{1,2,3,4}; + + auto set = details::default_construct_set(); + set.insert(l.begin(), l.end()); + std::vector l_sorted = l; + std::vector set_sorted(set.begin(), set.end()); + std::sort(l_sorted.begin(), l_sorted.end()); + std::sort(set_sorted.begin(), set_sorted.end()); + //iterating over the set didn't work + REQUIRE(std::equal(l_sorted.begin(), l_sorted.end(), + set_sorted.begin())); + } + + TEST_CASE_TEMPLATE("const iter access multi", T, TEST_TYPES) { + std::initializer_list l{1,2,3,4}; + + auto set = details::default_construct_set(); + set.insert(l.begin(), l.end()); + std::vector l_sorted = l; + std::vector set_sorted(set.cbegin(), set.cend()); + std::sort(l_sorted.begin(), l_sorted.end()); + std::sort(set_sorted.begin(), set_sorted.end()); + //const iterating over the set didn't work + REQUIRE(std::equal(l_sorted.begin(), l_sorted.end(), + set_sorted.begin())); + } + + TEST_CASE_TEMPLATE("find", T, TEST_TYPES) { + std::initializer_list l{1,2,3,4}; + + auto set = details::default_construct_set(); + set.insert(l.begin(), l.end()); + + SUBCASE("exists") { + auto iter = set.find(4); + REQUIRE(iter != set.end()); + } + + SUBCASE("not exists") { + auto iter = set.find(5); + REQUIRE(iter == set.end()); + } + } + + TEST_CASE_TEMPLATE("erase", T, TEST_TYPES) { + std::initializer_list l{1,2,3,4}; + typename T::value_type extra_value = 5; + + SUBCASE("iter") { + auto set = details::default_construct_set(); + set.insert(extra_value); + set.insert(l.begin(), l.end()); + // force non-const iterator + auto iter = set.begin(); + for(; *iter != extra_value; ++iter); + set.erase(iter); + //erase did not work as expected + REQUIRE(details::is_equal(set, l)); + } + + SUBCASE("const iter") { + auto set = details::default_construct_set(); + set.insert(extra_value); + set.insert(l.begin(), l.end()); + //force const iterator + auto iter = set.cbegin(); + for(; *iter != extra_value; ++iter); + set.erase(iter); + //erase did not work as expected + REQUIRE(details::is_equal(set, l)); + } + } + + TEST_CASE("full set") { + dice::sparse_map::sparse_set, std::equal_to, OffsetAllocator> set; + std::vector data = {1,2,3,4,5,6,7,8,9}; + set.insert(data.begin(), data.end()); + auto check = [&set](int d) {return set.contains(d);}; + //size did not match + REQUIRE(data.size() == set.size()); + //Set did not contain all values + REQUIRE(std::all_of(data.begin(), data.end(), check)); + } } - -BOOST_AUTO_TEST_SUITE_END() -BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/main.cpp b/tests/main.cpp deleted file mode 100644 index fa1e24c..0000000 --- a/tests/main.cpp +++ /dev/null @@ -1,26 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2017 Thibaut Goetghebuer-Planchon - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#define BOOST_TEST_MODULE sparse_map_tests - -#include diff --git a/tests/policy_tests.cpp b/tests/policy_tests.cpp index 7866b2e..74947b0 100644 --- a/tests/policy_tests.cpp +++ b/tests/policy_tests.cpp @@ -21,75 +21,71 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include + #include -#include -#include -#include #include #include #include -BOOST_AUTO_TEST_SUITE(test_policy) +#define TEST_POLICIES dice::sparse_map::power_of_two_growth_policy<2>, \ + dice::sparse_map::power_of_two_growth_policy<4>, \ + dice::sparse_map::prime_growth_policy, \ + dice::sparse_map::mod_growth_policy<>, \ + dice::sparse_map::mod_growth_policy> -using test_types = - boost::mpl::list, - dice::sparse_map::power_of_two_growth_policy<4>, - dice::sparse_map::prime_growth_policy, - dice::sparse_map::mod_growth_policy<>, - dice::sparse_map::mod_growth_policy>>; +TEST_SUITE("policies") { + TEST_CASE_TEMPLATE("test policy", Policy, TEST_POLICIES) { -BOOST_AUTO_TEST_CASE_TEMPLATE(test_policy, Policy, test_types) { - // Call next_bucket_count() on the policy until we reach its - // max_bucket_count() - bool exception_thrown = false; + // Call next_bucket_count() on the policy until we reach its + // max_bucket_count() + bool exception_thrown = false; - std::size_t bucket_count = 0; - Policy policy(bucket_count); + std::size_t bucket_count = 0; + Policy policy(bucket_count); - BOOST_CHECK_EQUAL(policy.bucket_for_hash(0), 0); - BOOST_CHECK_EQUAL(bucket_count, 0); + CHECK_EQ(policy.bucket_for_hash(0), 0); + CHECK_EQ(bucket_count, 0); - try { - while (true) { - const std::size_t previous_bucket_count = bucket_count; + try { + while (true) { + const std::size_t previous_bucket_count = bucket_count; - bucket_count = policy.next_bucket_count(); - policy = Policy(bucket_count); + bucket_count = policy.next_bucket_count(); + policy = Policy(bucket_count); - BOOST_CHECK_EQUAL(policy.bucket_for_hash(0), 0); - BOOST_CHECK(bucket_count > previous_bucket_count); - } - } catch (const std::length_error&) { - exception_thrown = true; - } + CHECK_EQ(policy.bucket_for_hash(0), 0); + CHECK(bucket_count > previous_bucket_count); + } + } catch (const std::length_error&) { + exception_thrown = true; + } - BOOST_CHECK(exception_thrown); -} + CHECK(exception_thrown); + } -BOOST_AUTO_TEST_CASE_TEMPLATE(test_policy_min_bucket_count, Policy, - test_types) { - // Check policy when a bucket_count of 0 is asked. - std::size_t bucket_count = 0; - Policy policy(bucket_count); + TEST_CASE_TEMPLATE("min bucket count", Policy, TEST_POLICIES) { + // Check policy when a bucket_count of 0 is asked. + std::size_t bucket_count = 0; + Policy policy(bucket_count); - BOOST_CHECK_EQUAL(policy.bucket_for_hash(0), 0); -} + CHECK_EQ(policy.bucket_for_hash(0), 0); + } -BOOST_AUTO_TEST_CASE_TEMPLATE(test_policy_max_bucket_count, Policy, - test_types) { - // Test a bucket_count equals to the max_bucket_count limit and above - std::size_t bucket_count = 0; - Policy policy(bucket_count); + TEST_CASE_TEMPLATE("max bucket count", Policy, TEST_POLICIES) { + // Test a bucket_count equals to the max_bucket_count limit and above + std::size_t bucket_count = 0; + Policy policy(bucket_count); - bucket_count = policy.max_bucket_count(); - Policy policy2(bucket_count); + bucket_count = policy.max_bucket_count(); + Policy policy2(bucket_count); - bucket_count = std::numeric_limits::max(); - BOOST_CHECK_THROW((Policy(bucket_count)), std::length_error); + bucket_count = std::numeric_limits::max(); + CHECK_THROWS_AS((Policy(bucket_count)), std::length_error); - bucket_count = policy.max_bucket_count() + 1; - BOOST_CHECK_THROW((Policy(bucket_count)), std::length_error); + bucket_count = policy.max_bucket_count() + 1; + CHECK_THROWS_AS((Policy(bucket_count)), std::length_error); + } } - -BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp b/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp index 216d5f4..d99b4de 100644 --- a/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp +++ b/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp @@ -1,4 +1,6 @@ -#include +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include + #include #include @@ -55,12 +57,8 @@ struct SCOPED { using Set = details::sparse_set; }; -BOOST_AUTO_TEST_SUITE(scoped_allocators) -BOOST_AUTO_TEST_SUITE(sparse_hash_set_tests) - -BOOST_AUTO_TEST_CASE(normal_construction){construction>();} +TEST_SUITE("sparse set with scoped allocator") { + TEST_CASE("normal construction"){construction>();} -BOOST_AUTO_TEST_CASE(scoped_construction){construction>();} - -BOOST_AUTO_TEST_SUITE_END() -BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file + TEST_CASE("scoped construction"){construction>();} +} diff --git a/tests/sparse_map_tests.cpp b/tests/sparse_map_tests.cpp index 7443320..41e2cda 100644 --- a/tests/sparse_map_tests.cpp +++ b/tests/sparse_map_tests.cpp @@ -21,14 +21,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include + #include -#include -#include -#include -#include #include -#include #include #include #include @@ -39,1209 +37,1208 @@ #include "utils.h" -BOOST_AUTO_TEST_SUITE(test_sparse_map) - -using test_types = boost::mpl::list< - dice::sparse_map::sparse_map, - dice::sparse_map::sparse_map, - // Test with hash having a lot of collisions - dice::sparse_map::sparse_map>, - dice::sparse_map::sparse_map>, - dice::sparse_map::sparse_map>, - dice::sparse_map::sparse_map>, - dice::sparse_map::sparse_map>, - - // Others GrowthPolicy - dice::sparse_map::sparse_map, - std::equal_to, - std::allocator>, - dice::sparse_map::power_of_two_growth_policy<4>>, - dice::sparse_map::sparse_pg_map>, - dice::sparse_map::sparse_map, - std::equal_to, - std::allocator>, - dice::sparse_map::mod_growth_policy<>>, - - dice::sparse_map::sparse_map, - std::equal_to, - std::allocator>, - dice::sparse_map::power_of_two_growth_policy<4>>, - dice::sparse_map::sparse_pg_map>, - dice::sparse_map::sparse_map, - std::equal_to, - std::allocator>, - dice::sparse_map::mod_growth_policy<>>, - - // Strong exception guarantee - dice::sparse_map::sparse_map, - std::equal_to, - std::allocator>, - dice::sparse_map::power_of_two_growth_policy<2>, - dice::sparse_map::exception_safety::strong>, - - // Others sparsity - dice::sparse_map::sparse_map, - std::equal_to, - std::allocator>, - dice::sparse_map::power_of_two_growth_policy<2>, - dice::sparse_map::exception_safety::basic, - dice::sparse_map::sparsity::high>, - dice::sparse_map::sparse_map, - std::equal_to, - std::allocator>, - dice::sparse_map::power_of_two_growth_policy<2>, - dice::sparse_map::exception_safety::basic, dice::sparse_map::sparsity::low>>; - -/** - * insert - */ -BOOST_AUTO_TEST_CASE_TEMPLATE(test_insert, HMap, test_types) { - // insert x values, insert them again, check values - using key_t = typename HMap::key_type; - using value_t = typename HMap::mapped_type; - - const std::size_t nb_values = 1000; - HMap map(0); - BOOST_CHECK_EQUAL(map.bucket_count(), 0); - - - for (std::size_t i = 0; i < nb_values; i++) { - auto [it, inserted] = map.insert({utils::get_key(i), utils::get_value(i)}); - - BOOST_CHECK_EQUAL(it->first, utils::get_key(i)); - BOOST_CHECK_EQUAL(it->second, utils::get_value(i)); - BOOST_CHECK(inserted); - } - BOOST_CHECK_EQUAL(map.size(), nb_values); - - for (std::size_t i = 0; i < nb_values; i++) { - auto [it, inserted] = map.insert({utils::get_key(i), utils::get_value(i + 1)}); - - BOOST_CHECK_EQUAL(it->first, utils::get_key(i)); - BOOST_CHECK_EQUAL(it->second, utils::get_value(i)); - BOOST_CHECK(!inserted); - } - - for (std::size_t i = 0; i < nb_values; i++) { - auto it = map.find(utils::get_key(i)); - - BOOST_CHECK_EQUAL(it->first, utils::get_key(i)); - BOOST_CHECK_EQUAL(it->second, utils::get_value(i)); - } -} - -BOOST_AUTO_TEST_CASE(test_range_insert) { - // create a vector of values to insert, insert part of them in the - // map, check values - const int nb_values = 1000; - std::vector> values_to_insert(nb_values); - for (int i = 0; i < nb_values; i++) { - values_to_insert[i] = std::make_pair(i, i + 1); - } - - dice::sparse_map::sparse_map map = {{-1, 1}, {-2, 2}}; - map.insert(std::next(values_to_insert.begin(), 10), - values_to_insert.end() - 5); - - BOOST_CHECK_EQUAL(map.size(), 987); - - BOOST_CHECK_EQUAL(map[-1], 1); - BOOST_CHECK_EQUAL(map[-2], 2); - - for (int i = 10; i < nb_values - 5; i++) { - BOOST_CHECK_EQUAL(map[i], i + 1); - } -} - -BOOST_AUTO_TEST_CASE(test_insert_with_hint) { - dice::sparse_map::sparse_map map{{1, 0}, {2, 1}, {3, 2}}; - - // Wrong hint - BOOST_CHECK(map.insert(map.find(2), std::make_pair(3, 4)) == map.find(3)); - - // Good hint - BOOST_CHECK(map.insert(map.find(2), std::make_pair(2, 4)) == map.find(2)); - - // end() hint - BOOST_CHECK(map.insert(map.find(10), std::make_pair(2, 4)) == map.find(2)); - - BOOST_CHECK_EQUAL(map.size(), 3); - - // end() hint, new value - BOOST_CHECK_EQUAL(map.insert(map.find(10), std::make_pair(4, 3))->first, 4); - - // Wrong hint, new value - BOOST_CHECK_EQUAL(map.insert(map.find(2), std::make_pair(5, 4))->first, 5); - - BOOST_CHECK_EQUAL(map.size(), 5); -} - -/** +#define TEST_MAPS dice::sparse_map::sparse_map, \ + dice::sparse_map::sparse_map, \ + dice::sparse_map::sparse_map>, \ + dice::sparse_map::sparse_map>, \ + dice::sparse_map::sparse_map>, \ + dice::sparse_map::sparse_map>, \ + dice::sparse_map::sparse_map>, \ + \ + dice::sparse_map::sparse_map, \ + std::equal_to, \ + std::allocator>, \ + dice::sparse_map::power_of_two_growth_policy<4>>, \ + dice::sparse_map::sparse_pg_map>, \ + dice::sparse_map::sparse_map, \ + std::equal_to, \ + std::allocator>, \ + dice::sparse_map::mod_growth_policy<>>, \ + \ + dice::sparse_map::sparse_map, \ + std::equal_to, \ + std::allocator>, \ + dice::sparse_map::power_of_two_growth_policy<4>>, \ + dice::sparse_map::sparse_pg_map>, \ + dice::sparse_map::sparse_map, \ + std::equal_to, \ + std::allocator>, \ + dice::sparse_map::mod_growth_policy<>>, \ + \ + \ + dice::sparse_map::sparse_map, \ + std::equal_to, \ + std::allocator>, \ + dice::sparse_map::power_of_two_growth_policy<2>, \ + dice::sparse_map::exception_safety::strong>, \ + \ + \ + dice::sparse_map::sparse_map, \ + std::equal_to, \ + std::allocator>, \ + dice::sparse_map::power_of_two_growth_policy<2>, \ + dice::sparse_map::exception_safety::basic, \ + dice::sparse_map::sparsity::high>, \ + dice::sparse_map::sparse_map, \ + std::equal_to, \ + std::allocator>, \ + dice::sparse_map::power_of_two_growth_policy<2>, \ + dice::sparse_map::exception_safety::basic, dice::sparse_map::sparsity::low> + +TEST_SUITE("sparse map") { + + /** + * insert + */ + TEST_CASE_TEMPLATE("insert", HMap, TEST_MAPS) { + // insert x values, insert them again, check values + using key_t = typename HMap::key_type; + using value_t = typename HMap::mapped_type; + + const std::size_t nb_values = 1000; + HMap map(0); + CHECK_EQ(map.bucket_count(), 0); + + + for (std::size_t i = 0; i < nb_values; i++) { + auto [it, inserted] = map.insert({utils::get_key(i), utils::get_value(i)}); + + CHECK_EQ(it->first, utils::get_key(i)); + CHECK_EQ(it->second, utils::get_value(i)); + CHECK(inserted); + } + CHECK_EQ(map.size(), nb_values); + + for (std::size_t i = 0; i < nb_values; i++) { + auto [it, inserted] = map.insert({utils::get_key(i), utils::get_value(i + 1)}); + + CHECK_EQ(it->first, utils::get_key(i)); + CHECK_EQ(it->second, utils::get_value(i)); + CHECK(!inserted); + } + + for (std::size_t i = 0; i < nb_values; i++) { + auto it = map.find(utils::get_key(i)); + + CHECK_EQ(it->first, utils::get_key(i)); + CHECK_EQ(it->second, utils::get_value(i)); + } + } + + TEST_CASE("range insert") { + // create a vector of values to insert, insert part of them in the + // map, check values + const int nb_values = 1000; + std::vector> values_to_insert(nb_values); + for (int i = 0; i < nb_values; i++) { + values_to_insert[i] = std::make_pair(i, i + 1); + } + + dice::sparse_map::sparse_map map = {{-1, 1}, {-2, 2}}; + map.insert(std::next(values_to_insert.begin(), 10), + values_to_insert.end() - 5); + + CHECK_EQ(map.size(), 987); + + CHECK_EQ(map[-1], 1); + CHECK_EQ(map[-2], 2); + + for (int i = 10; i < nb_values - 5; i++) { + CHECK_EQ(map[i], i + 1); + } + } + + TEST_CASE("insert with hint") { + dice::sparse_map::sparse_map map{{1, 0}, {2, 1}, {3, 2}}; + + // Wrong hint + CHECK(map.insert(map.find(2), std::make_pair(3, 4)) == map.find(3)); + + // Good hint + CHECK(map.insert(map.find(2), std::make_pair(2, 4)) == map.find(2)); + + // end() hint + CHECK(map.insert(map.find(10), std::make_pair(2, 4)) == map.find(2)); + + CHECK_EQ(map.size(), 3); + + // end() hint, new value + CHECK_EQ(map.insert(map.find(10), std::make_pair(4, 3))->first, 4); + + // Wrong hint, new value + CHECK_EQ(map.insert(map.find(2), std::make_pair(5, 4))->first, 5); + + CHECK_EQ(map.size(), 5); + } + + /** * emplace_hint */ -BOOST_AUTO_TEST_CASE(test_emplace_hint) { - dice::sparse_map::sparse_map map{{1, 0}, {2, 1}, {3, 2}}; - - // Wrong hint - BOOST_CHECK(map.emplace_hint(map.find(2), std::piecewise_construct, - std::forward_as_tuple(3), - std::forward_as_tuple(4)) == map.find(3)); - - // Good hint - BOOST_CHECK(map.emplace_hint(map.find(2), std::piecewise_construct, - std::forward_as_tuple(2), - std::forward_as_tuple(4)) == map.find(2)); - - // end() hint - BOOST_CHECK(map.emplace_hint(map.find(10), std::piecewise_construct, - std::forward_as_tuple(2), - std::forward_as_tuple(4)) == map.find(2)); - - BOOST_CHECK_EQUAL(map.size(), 3); - - // end() hint, new value - BOOST_CHECK_EQUAL( - map.emplace_hint(map.find(10), std::piecewise_construct, - std::forward_as_tuple(4), std::forward_as_tuple(3)) - ->first, - 4); - - // Wrong hint, new value - BOOST_CHECK_EQUAL( - map.emplace_hint(map.find(2), std::piecewise_construct, - std::forward_as_tuple(5), std::forward_as_tuple(4)) - ->first, - 5); - - BOOST_CHECK_EQUAL(map.size(), 5); -} - -/** - * emplace - */ -BOOST_AUTO_TEST_CASE(test_emplace) { - dice::sparse_map::sparse_map map; - - auto [it, inserted] = - map.emplace(std::piecewise_construct, std::forward_as_tuple(10), - std::forward_as_tuple(1)); - BOOST_CHECK_EQUAL(it->first, 10); - BOOST_CHECK_EQUAL(it->second, move_only_test(1)); - BOOST_CHECK(inserted); - - std::tie(it, inserted) = - map.emplace(std::piecewise_construct, std::forward_as_tuple(10), - std::forward_as_tuple(3)); - BOOST_CHECK_EQUAL(it->first, 10); - BOOST_CHECK_EQUAL(it->second, move_only_test(1)); - BOOST_CHECK(!inserted); -} - -/** - * try_emplace - */ -BOOST_AUTO_TEST_CASE(test_try_emplace) { - dice::sparse_map::sparse_map map; - - auto [it, inserted] = map.try_emplace(10, 1); - BOOST_CHECK_EQUAL(it->first, 10); - BOOST_CHECK_EQUAL(it->second, move_only_test(1)); - BOOST_CHECK(inserted); - - std::tie(it, inserted) = map.try_emplace(10, 3); - BOOST_CHECK_EQUAL(it->first, 10); - BOOST_CHECK_EQUAL(it->second, move_only_test(1)); - BOOST_CHECK(!inserted); -} - -BOOST_AUTO_TEST_CASE(test_try_emplace_2) { - // Insert x values with try_emplace, insert them again, check with find. - dice::sparse_map::sparse_map map; - - const std::size_t nb_values = 1000; - for (std::size_t i = 0; i < nb_values; i++) { - auto [it, inserted] = map.try_emplace(utils::get_key(i), i); - - BOOST_CHECK_EQUAL(it->first, utils::get_key(i)); - BOOST_CHECK_EQUAL(it->second, move_only_test(i)); - BOOST_CHECK(inserted); - } - BOOST_CHECK_EQUAL(map.size(), nb_values); - - for (std::size_t i = 0; i < nb_values; i++) { - auto [it, inserted] = map.try_emplace(utils::get_key(i), i + 1); - - BOOST_CHECK_EQUAL(it->first, utils::get_key(i)); - BOOST_CHECK_EQUAL(it->second, move_only_test(i)); - BOOST_CHECK(!inserted); - } - - for (std::size_t i = 0; i < nb_values; i++) { - auto it = map.find(utils::get_key(i)); - - BOOST_CHECK_EQUAL(it->first, utils::get_key(i)); - BOOST_CHECK_EQUAL(it->second, move_only_test(i)); - } -} - -BOOST_AUTO_TEST_CASE(test_try_emplace_hint) { - dice::sparse_map::sparse_map map(0); - - // end() hint, new value - auto it = map.try_emplace(map.find(10), 10, 1); - BOOST_CHECK_EQUAL(it->first, 10); - BOOST_CHECK_EQUAL(it->second, move_only_test(1)); - - // Good hint - it = map.try_emplace(map.find(10), 10, 3); - BOOST_CHECK_EQUAL(it->first, 10); - BOOST_CHECK_EQUAL(it->second, move_only_test(1)); - - // Wrong hint, new value - it = map.try_emplace(map.find(10), 1, 3); - BOOST_CHECK_EQUAL(it->first, 1); - BOOST_CHECK_EQUAL(it->second, move_only_test(3)); -} - -/** - * insert_or_assign - */ -BOOST_AUTO_TEST_CASE(test_insert_or_assign) { - dice::sparse_map::sparse_map map; - - auto [it, inserted] = map.insert_or_assign(10, move_only_test(1)); - BOOST_CHECK_EQUAL(it->first, 10); - BOOST_CHECK_EQUAL(it->second, move_only_test(1)); - BOOST_CHECK(inserted); - - std::tie(it, inserted) = map.insert_or_assign(10, move_only_test(3)); - BOOST_CHECK_EQUAL(it->first, 10); - BOOST_CHECK_EQUAL(it->second, move_only_test(3)); - BOOST_CHECK(!inserted); -} - -BOOST_AUTO_TEST_CASE(test_insert_or_assign_hint) { - dice::sparse_map::sparse_map map(0); - - // end() hint, new value - auto it = map.insert_or_assign(map.find(10), 10, move_only_test(1)); - BOOST_CHECK_EQUAL(it->first, 10); - BOOST_CHECK_EQUAL(it->second, move_only_test(1)); - - // Good hint - it = map.insert_or_assign(map.find(10), 10, move_only_test(3)); - BOOST_CHECK_EQUAL(it->first, 10); - BOOST_CHECK_EQUAL(it->second, move_only_test(3)); - - // Bad hint, new value - it = map.insert_or_assign(map.find(10), 1, move_only_test(3)); - BOOST_CHECK_EQUAL(it->first, 1); - BOOST_CHECK_EQUAL(it->second, move_only_test(3)); -} - -/** - * erase - */ -BOOST_AUTO_TEST_CASE(test_range_erase_all) { - // insert x values, delete all - using HMap = dice::sparse_map::sparse_map; - - const std::size_t nb_values = 1000; - HMap map = utils::get_filled_hash_map(nb_values); - - auto it = map.erase(map.begin(), map.end()); - BOOST_CHECK(it == map.end()); - BOOST_CHECK(map.empty()); -} - -BOOST_AUTO_TEST_CASE(test_range_erase) { - // insert x values, delete all except 10 first and 780 last values - using HMap = dice::sparse_map::sparse_map; - - const std::size_t nb_values = 1000; - HMap map = utils::get_filled_hash_map(nb_values); - - auto it_first = std::next(map.begin(), 10); - auto it_last = std::next(map.begin(), 220); - - auto it = map.erase(it_first, it_last); - BOOST_CHECK_EQUAL(std::distance(it, map.end()), 780); - BOOST_CHECK_EQUAL(map.size(), 790); - BOOST_CHECK_EQUAL(std::distance(map.begin(), map.end()), 790); - - for (auto& val : map) { - BOOST_CHECK_EQUAL(map.count(val.first), 1); - } -} - -BOOST_AUTO_TEST_CASE_TEMPLATE(test_erase_loop, HMap, test_types) { - // insert x values, delete all one by one with iterator - std::size_t nb_values = 1000; - - HMap map = utils::get_filled_hash_map(nb_values); - HMap map2 = utils::get_filled_hash_map(nb_values); - - auto it = map.begin(); - // Use second map to check for key after delete as we may not copy the key - // with move-only types. - auto it2 = map2.begin(); - while (it != map.end()) { - it = map.erase(it); - --nb_values; - - BOOST_CHECK_EQUAL(map.count(it2->first), 0); - BOOST_CHECK_EQUAL(map.size(), nb_values); - ++it2; - } - - BOOST_CHECK(map.empty()); -} - -BOOST_AUTO_TEST_CASE_TEMPLATE(test_erase_loop_range, HMap, test_types) { - // insert x values, delete all five by five with iterators - const std::size_t hop = 5; - std::size_t nb_values = 1000; - - BOOST_REQUIRE_EQUAL(nb_values % hop, 0); - - HMap map = utils::get_filled_hash_map(nb_values); - - auto it = map.begin(); - while (it != map.end()) { - it = map.erase(it, std::next(it, hop)); - nb_values -= hop; - - BOOST_CHECK_EQUAL(map.size(), nb_values); - } - - BOOST_CHECK(map.empty()); -} - -BOOST_AUTO_TEST_CASE_TEMPLATE(test_insert_erase_insert, HMap, test_types) { - // insert x/2 values, delete x/4 values, insert x/2 values, find each value - using key_t = typename HMap::key_type; - using value_t = typename HMap::mapped_type; - - const std::size_t nb_values = 2000; - HMap map(10); - - // Insert nb_values/2 - for (std::size_t i = 0; i < nb_values / 2; i++) { - auto [it, inserted] = map.insert({utils::get_key(i), utils::get_value(i)}); - - BOOST_CHECK_EQUAL(it->first, utils::get_key(i)); - BOOST_CHECK_EQUAL(it->second, utils::get_value(i)); - BOOST_CHECK(inserted); - } - BOOST_CHECK_EQUAL(map.size(), nb_values / 2); - - // Delete nb_values/4 - for (std::size_t i = 0; i < nb_values / 2; i++) { - if (i % 2 == 0) { - BOOST_CHECK_EQUAL(map.erase(utils::get_key(i)), 1); - } - } - BOOST_CHECK_EQUAL(map.size(), nb_values / 4); - - // Insert nb_values/2 - for (std::size_t i = nb_values / 2; i < nb_values; i++) { - auto [it, inserted] = map.insert({utils::get_key(i), utils::get_value(i)}); - - BOOST_CHECK_EQUAL(it->first, utils::get_key(i)); - BOOST_CHECK_EQUAL(it->second, utils::get_value(i)); - BOOST_CHECK(inserted); - } - BOOST_CHECK_EQUAL(map.size(), nb_values - nb_values / 4); - - // Find - for (std::size_t i = 0; i < nb_values; i++) { - if (i % 2 == 0 && i < nb_values / 2) { - auto it = map.find(utils::get_key(i)); - - BOOST_CHECK(it == map.end()); - } else { - auto it = map.find(utils::get_key(i)); - - BOOST_REQUIRE(it != map.end()); - BOOST_CHECK_EQUAL(it->first, utils::get_key(i)); - BOOST_CHECK_EQUAL(it->second, utils::get_value(i)); - } - } -} - -BOOST_AUTO_TEST_CASE(test_range_erase_same_iterators) { - // insert x values, test erase with same iterator as each parameter, check if - // returned mutable iterator is valid. - const std::size_t nb_values = 100; - auto map = - utils::get_filled_hash_map>( - nb_values); - - dice::sparse_map::sparse_map::const_iterator it_const = - map.cbegin(); - std::advance(it_const, 10); - - dice::sparse_map::sparse_map::iterator it_mutable = - map.erase(it_const, it_const); - BOOST_CHECK(it_const == it_mutable); - //BOOST_CHECK(map.mutable_iterator(it_const) == it_mutable); - BOOST_CHECK_EQUAL(map.size(), 100); - - it_mutable->second = -100; - BOOST_CHECK_EQUAL(it_const->second, -100); -} - -/** + TEST_CASE("emplace hint") { + dice::sparse_map::sparse_map map{{1, 0}, {2, 1}, {3, 2}}; + + // Wrong hint + CHECK(map.emplace_hint(map.find(2), std::piecewise_construct, + std::forward_as_tuple(3), + std::forward_as_tuple(4)) == map.find(3)); + + // Good hint + CHECK(map.emplace_hint(map.find(2), std::piecewise_construct, + std::forward_as_tuple(2), + std::forward_as_tuple(4)) == map.find(2)); + + // end() hint + CHECK(map.emplace_hint(map.find(10), std::piecewise_construct, + std::forward_as_tuple(2), + std::forward_as_tuple(4)) == map.find(2)); + + CHECK_EQ(map.size(), 3); + + // end() hint, new value + CHECK_EQ( + map.emplace_hint(map.find(10), std::piecewise_construct, + std::forward_as_tuple(4), std::forward_as_tuple(3)) + ->first, + 4); + + // Wrong hint, new value + CHECK_EQ( + map.emplace_hint(map.find(2), std::piecewise_construct, + std::forward_as_tuple(5), std::forward_as_tuple(4)) + ->first, + 5); + + CHECK_EQ(map.size(), 5); + } + + TEST_CASE("emplace") { + dice::sparse_map::sparse_map map; + + auto [it, inserted] = + map.emplace(std::piecewise_construct, std::forward_as_tuple(10), + std::forward_as_tuple(1)); + CHECK_EQ(it->first, 10); + CHECK_EQ(it->second, move_only_test(1)); + CHECK(inserted); + + std::tie(it, inserted) = + map.emplace(std::piecewise_construct, std::forward_as_tuple(10), + std::forward_as_tuple(3)); + CHECK_EQ(it->first, 10); + CHECK_EQ(it->second, move_only_test(1)); + CHECK(!inserted); + } + + TEST_CASE("try emplace") { + dice::sparse_map::sparse_map map; + + auto [it, inserted] = map.try_emplace(10, 1); + CHECK_EQ(it->first, 10); + CHECK_EQ(it->second, move_only_test(1)); + CHECK(inserted); + + std::tie(it, inserted) = map.try_emplace(10, 3); + CHECK_EQ(it->first, 10); + CHECK_EQ(it->second, move_only_test(1)); + CHECK(!inserted); + } + + TEST_CASE("try emplace 2") { + // Insert x values with try_emplace, insert them again, check with find. + dice::sparse_map::sparse_map map; + + const std::size_t nb_values = 1000; + for (std::size_t i = 0; i < nb_values; i++) { + auto [it, inserted] = map.try_emplace(utils::get_key(i), i); + + CHECK_EQ(it->first, utils::get_key(i)); + CHECK_EQ(it->second, move_only_test(i)); + CHECK(inserted); + } + CHECK_EQ(map.size(), nb_values); + + for (std::size_t i = 0; i < nb_values; i++) { + auto [it, inserted] = map.try_emplace(utils::get_key(i), i + 1); + + CHECK_EQ(it->first, utils::get_key(i)); + CHECK_EQ(it->second, move_only_test(i)); + CHECK(!inserted); + } + + for (std::size_t i = 0; i < nb_values; i++) { + auto it = map.find(utils::get_key(i)); + + CHECK_EQ(it->first, utils::get_key(i)); + CHECK_EQ(it->second, move_only_test(i)); + } + } + + TEST_CASE("emplace hint") { + dice::sparse_map::sparse_map map(0); + + // end() hint, new value + auto it = map.try_emplace(map.find(10), 10, 1); + CHECK_EQ(it->first, 10); + CHECK_EQ(it->second, move_only_test(1)); + + // Good hint + it = map.try_emplace(map.find(10), 10, 3); + CHECK_EQ(it->first, 10); + CHECK_EQ(it->second, move_only_test(1)); + + // Wrong hint, new value + it = map.try_emplace(map.find(10), 1, 3); + CHECK_EQ(it->first, 1); + CHECK_EQ(it->second, move_only_test(3)); + } + + TEST_CASE("insert or assign") { + dice::sparse_map::sparse_map map; + + auto [it, inserted] = map.insert_or_assign(10, move_only_test(1)); + CHECK_EQ(it->first, 10); + CHECK_EQ(it->second, move_only_test(1)); + CHECK(inserted); + + std::tie(it, inserted) = map.insert_or_assign(10, move_only_test(3)); + CHECK_EQ(it->first, 10); + CHECK_EQ(it->second, move_only_test(3)); + CHECK(!inserted); + } + + TEST_CASE("insert or assign hint") { + dice::sparse_map::sparse_map map(0); + + // end() hint, new value + auto it = map.insert_or_assign(map.find(10), 10, move_only_test(1)); + CHECK_EQ(it->first, 10); + CHECK_EQ(it->second, move_only_test(1)); + + // Good hint + it = map.insert_or_assign(map.find(10), 10, move_only_test(3)); + CHECK_EQ(it->first, 10); + CHECK_EQ(it->second, move_only_test(3)); + + // Bad hint, new value + it = map.insert_or_assign(map.find(10), 1, move_only_test(3)); + CHECK_EQ(it->first, 1); + CHECK_EQ(it->second, move_only_test(3)); + } + + TEST_CASE("range erase all") { + // insert x values, delete all + using HMap = dice::sparse_map::sparse_map; + + const std::size_t nb_values = 1000; + HMap map = utils::get_filled_hash_map(nb_values); + + auto it = map.erase(map.begin(), map.end()); + CHECK(it == map.end()); + CHECK(map.empty()); + } + + TEST_CASE("range erase") { + // insert x values, delete all except 10 first and 780 last values + using HMap = dice::sparse_map::sparse_map; + + const std::size_t nb_values = 1000; + HMap map = utils::get_filled_hash_map(nb_values); + + auto it_first = std::next(map.begin(), 10); + auto it_last = std::next(map.begin(), 220); + + auto it = map.erase(it_first, it_last); + CHECK_EQ(std::distance(it, map.end()), 780); + CHECK_EQ(map.size(), 790); + CHECK_EQ(std::distance(map.begin(), map.end()), 790); + + for (auto &val : map) { + CHECK_EQ(map.count(val.first), 1); + } + } + + TEST_CASE_TEMPLATE("erase loop", HMap, TEST_MAPS) { + // insert x values, delete all one by one with iterator + std::size_t nb_values = 1000; + + HMap map = utils::get_filled_hash_map(nb_values); + HMap map2 = utils::get_filled_hash_map(nb_values); + + auto it = map.begin(); + // Use second map to check for key after delete as we may not copy the key + // with move-only types. + auto it2 = map2.begin(); + while (it != map.end()) { + it = map.erase(it); + --nb_values; + + CHECK_EQ(map.count(it2->first), 0); + CHECK_EQ(map.size(), nb_values); + ++it2; + } + + CHECK(map.empty()); + } + + TEST_CASE_TEMPLATE("erase loop range", HMap, TEST_MAPS) { + // insert x values, delete all five by five with iterators + const std::size_t hop = 5; + std::size_t nb_values = 1000; + + REQUIRE_EQ(nb_values % hop, 0); + + HMap map = utils::get_filled_hash_map(nb_values); + + auto it = map.begin(); + while (it != map.end()) { + it = map.erase(it, std::next(it, hop)); + nb_values -= hop; + + CHECK_EQ(map.size(), nb_values); + } + + CHECK(map.empty()); + } + + TEST_CASE_TEMPLATE("insert erase insert", HMap, TEST_MAPS) { + // insert x/2 values, delete x/4 values, insert x/2 values, find each value + using key_t = typename HMap::key_type; + using value_t = typename HMap::mapped_type; + + const std::size_t nb_values = 2000; + HMap map(10); + + // Insert nb_values/2 + for (std::size_t i = 0; i < nb_values / 2; i++) { + auto [it, inserted] = map.insert({utils::get_key(i), utils::get_value(i)}); + + CHECK_EQ(it->first, utils::get_key(i)); + CHECK_EQ(it->second, utils::get_value(i)); + CHECK(inserted); + } + CHECK_EQ(map.size(), nb_values / 2); + + // Delete nb_values/4 + for (std::size_t i = 0; i < nb_values / 2; i++) { + if (i % 2 == 0) { + CHECK_EQ(map.erase(utils::get_key(i)), 1); + } + } + CHECK_EQ(map.size(), nb_values / 4); + + // Insert nb_values/2 + for (std::size_t i = nb_values / 2; i < nb_values; i++) { + auto [it, inserted] = map.insert({utils::get_key(i), utils::get_value(i)}); + + CHECK_EQ(it->first, utils::get_key(i)); + CHECK_EQ(it->second, utils::get_value(i)); + CHECK(inserted); + } + CHECK_EQ(map.size(), nb_values - nb_values / 4); + + // Find + for (std::size_t i = 0; i < nb_values; i++) { + if (i % 2 == 0 && i < nb_values / 2) { + auto it = map.find(utils::get_key(i)); + + CHECK(it == map.end()); + } else { + auto it = map.find(utils::get_key(i)); + + REQUIRE(it != map.end()); + CHECK_EQ(it->first, utils::get_key(i)); + CHECK_EQ(it->second, utils::get_value(i)); + } + } + } + + TEST_CASE("range erase same iter") { + // insert x values, test erase with same iterator as each parameter, check if + // returned mutable iterator is valid. + const std::size_t nb_values = 100; + auto map = + utils::get_filled_hash_map>( + nb_values); + + dice::sparse_map::sparse_map::const_iterator it_const = + map.cbegin(); + std::advance(it_const, 10); + + dice::sparse_map::sparse_map::iterator it_mutable = + map.erase(it_const, it_const); + CHECK(it_const == it_mutable); + //CHECK(map.mutable_iterator(it_const) == it_mutable); + CHECK_EQ(map.size(), 100); + + it_mutable->second = -100; + CHECK_EQ(it_const->second, -100); + } + + /** * rehash */ -BOOST_AUTO_TEST_CASE(test_rehash_empty) { - // test rehash(0), test find/erase/insert on map. - const std::size_t nb_values = 100; - auto map = - utils::get_filled_hash_map>( - nb_values); - - const std::size_t bucket_count = map.bucket_count(); - BOOST_CHECK(bucket_count >= nb_values); - - map.clear(); - BOOST_CHECK_EQUAL(map.bucket_count(), bucket_count); - BOOST_CHECK(map.empty()); - - map.rehash(0); - BOOST_CHECK_EQUAL(map.bucket_count(), 0); - BOOST_CHECK(map.empty()); - - BOOST_CHECK(map.find(1) == map.end()); - BOOST_CHECK_EQUAL(map.erase(1), 0); - BOOST_CHECK(map.insert({1, 10}).second); - BOOST_CHECK_EQUAL(map.at(1), 10); -} - -/** + TEST_CASE("rehash empty") { + // test rehash(0), test find/erase/insert on map. + const std::size_t nb_values = 100; + auto map = + utils::get_filled_hash_map>( + nb_values); + + const std::size_t bucket_count = map.bucket_count(); + CHECK(bucket_count >= nb_values); + + map.clear(); + CHECK_EQ(map.bucket_count(), bucket_count); + CHECK(map.empty()); + + map.rehash(0); + CHECK_EQ(map.bucket_count(), 0); + CHECK(map.empty()); + + CHECK(map.find(1) == map.end()); + CHECK_EQ(map.erase(1), 0); + CHECK(map.insert({1, 10}).second); + CHECK_EQ(map.at(1), 10); + } + + /** * operator== and operator!= */ -BOOST_AUTO_TEST_CASE_TEMPLATE(test_compare, HMap, test_types) { - const dice::sparse_map::sparse_map map1 = { - {"a", 1}, {"e", 5}, {"d", 4}, {"c", 3}, {"b", 2}}; - const dice::sparse_map::sparse_map map1_copy = { - {"e", 5}, {"c", 3}, {"b", 2}, {"a", 1}, {"d", 4}}; - const dice::sparse_map::sparse_map map2 = { - {"e", 5}, {"c", 3}, {"b", 2}, {"a", 1}, {"d", 4}, {"f", 6}}; - const dice::sparse_map::sparse_map map3 = { - {"e", 5}, {"c", 3}, {"b", 2}, {"a", 1}}; - const dice::sparse_map::sparse_map map4 = { - {"a", 1}, {"e", 5}, {"d", 4}, {"c", 3}, {"b", 26}}; - const dice::sparse_map::sparse_map map5 = { - {"a", 1}, {"e", 5}, {"d", 4}, {"c", 3}, {"z", 2}}; - - BOOST_CHECK(map1 == map1_copy); - BOOST_CHECK(map1_copy == map1); - - BOOST_CHECK(map1 != map2); - BOOST_CHECK(map2 != map1); - - BOOST_CHECK(map1 != map3); - BOOST_CHECK(map3 != map1); - - BOOST_CHECK(map1 != map4); - BOOST_CHECK(map4 != map1); - - BOOST_CHECK(map1 != map5); - BOOST_CHECK(map5 != map1); - - BOOST_CHECK(map2 != map3); - BOOST_CHECK(map3 != map2); - - BOOST_CHECK(map2 != map4); - BOOST_CHECK(map4 != map2); - - BOOST_CHECK(map2 != map5); - BOOST_CHECK(map5 != map2); - - BOOST_CHECK(map3 != map4); - BOOST_CHECK(map4 != map3); - - BOOST_CHECK(map3 != map5); - BOOST_CHECK(map5 != map3); - - BOOST_CHECK(map4 != map5); - BOOST_CHECK(map5 != map4); -} - -/** - * clear - */ -BOOST_AUTO_TEST_CASE(test_clear) { - // insert x values, clear map - using HMap = dice::sparse_map::sparse_map; - - const std::size_t nb_values = 1000; - auto map = utils::get_filled_hash_map(nb_values); - - map.clear(); - BOOST_CHECK_EQUAL(map.size(), 0); - BOOST_CHECK_EQUAL(std::distance(map.begin(), map.end()), 0); - - map.insert({5, -5}); - map.insert({{1, -1}, {2, -1}, {4, -4}, {3, -3}}); - - BOOST_CHECK(map == (HMap({{5, -5}, {1, -1}, {2, -1}, {4, -4}, {3, -3}}))); -} - -/** - * iterator.value() - */ -BOOST_AUTO_TEST_CASE(test_modify_value_through_iterator) { - // insert x values, modify value of even keys, check values - const std::size_t nb_values = 100; - auto map = - utils::get_filled_hash_map>( - nb_values); - - for (auto it = map.begin(); it != map.end(); it++) { - if (it->first % 2 == 0) { - it->second = -1; - } - } - - for (auto& val : map) { - if (val.first % 2 == 0) { - BOOST_CHECK_EQUAL(val.second, -1); - } else { - BOOST_CHECK_NE(val.second, -1); - } - } -} - -/** + TEST_CASE_TEMPLATE("compare", HMap, TEST_MAPS) { + const dice::sparse_map::sparse_map map1 = { + {"a", 1}, + {"e", 5}, + {"d", 4}, + {"c", 3}, + {"b", 2}}; + const dice::sparse_map::sparse_map map1_copy = { + {"e", 5}, + {"c", 3}, + {"b", 2}, + {"a", 1}, + {"d", 4}}; + const dice::sparse_map::sparse_map map2 = { + {"e", 5}, + {"c", 3}, + {"b", 2}, + {"a", 1}, + {"d", 4}, + {"f", 6}}; + const dice::sparse_map::sparse_map map3 = { + {"e", 5}, + {"c", 3}, + {"b", 2}, + {"a", 1}}; + const dice::sparse_map::sparse_map map4 = { + {"a", 1}, + {"e", 5}, + {"d", 4}, + {"c", 3}, + {"b", 26}}; + const dice::sparse_map::sparse_map map5 = { + {"a", 1}, + {"e", 5}, + {"d", 4}, + {"c", 3}, + {"z", 2}}; + + CHECK(map1 == map1_copy); + CHECK(map1_copy == map1); + + CHECK(map1 != map2); + CHECK(map2 != map1); + + CHECK(map1 != map3); + CHECK(map3 != map1); + + CHECK(map1 != map4); + CHECK(map4 != map1); + + CHECK(map1 != map5); + CHECK(map5 != map1); + + CHECK(map2 != map3); + CHECK(map3 != map2); + + CHECK(map2 != map4); + CHECK(map4 != map2); + + CHECK(map2 != map5); + CHECK(map5 != map2); + + CHECK(map3 != map4); + CHECK(map4 != map3); + + CHECK(map3 != map5); + CHECK(map5 != map3); + + CHECK(map4 != map5); + CHECK(map5 != map4); + } + + TEST_CASE("clear") { + // insert x values, clear map + using HMap = dice::sparse_map::sparse_map; + + const std::size_t nb_values = 1000; + auto map = utils::get_filled_hash_map(nb_values); + + map.clear(); + CHECK_EQ(map.size(), 0); + CHECK_EQ(std::distance(map.begin(), map.end()), 0); + + map.insert({5, -5}); + map.insert({{1, -1}, {2, -1}, {4, -4}, {3, -3}}); + + CHECK(map == (HMap({{5, -5}, {1, -1}, {2, -1}, {4, -4}, {3, -3}}))); + } + + TEST_CASE("modify value through iterator") { + // insert x values, modify value of even keys, check values + const std::size_t nb_values = 100; + auto map = + utils::get_filled_hash_map>( + nb_values); + + for (auto it = map.begin(); it != map.end(); it++) { + if (it->first % 2 == 0) { + it->second = -1; + } + } + + for (auto &val : map) { + if (val.first % 2 == 0) { + CHECK_EQ(val.second, -1); + } else { + CHECK_NE(val.second, -1); + } + } + } + + /** * constructor */ -BOOST_AUTO_TEST_CASE(test_extreme_bucket_count_value_construction) { - BOOST_CHECK_THROW( - (dice::sparse_map::sparse_map, std::equal_to, - std::allocator>, - dice::sparse_map::power_of_two_growth_policy<2>>( - std::numeric_limits::max())), - std::length_error); - - BOOST_CHECK_THROW( - (dice::sparse_map::sparse_map, std::equal_to, - std::allocator>, - dice::sparse_map::power_of_two_growth_policy<2>>( - std::numeric_limits::max() / 2 + 1)), - std::length_error); - - BOOST_CHECK_THROW( - (dice::sparse_map::sparse_map, std::equal_to, - std::allocator>, - dice::sparse_map::prime_growth_policy>( - std::numeric_limits::max())), - std::length_error); - - BOOST_CHECK_THROW( - (dice::sparse_map::sparse_map, std::equal_to, - std::allocator>, - dice::sparse_map::prime_growth_policy>( - std::numeric_limits::max() / 2)), - std::length_error); - - BOOST_CHECK_THROW( - (dice::sparse_map::sparse_map, std::equal_to, - std::allocator>, - dice::sparse_map::mod_growth_policy<>>( - std::numeric_limits::max())), - std::length_error); -} - -BOOST_AUTO_TEST_CASE(test_range_construct) { - dice::sparse_map::sparse_map map = {{2, 1}, {1, 0}, {3, 2}}; - - dice::sparse_map::sparse_map map2(map.begin(), map.end()); - dice::sparse_map::sparse_map map3(map.cbegin(), map.cend()); -} - -/** - * operator=(std::initializer_list) - */ -BOOST_AUTO_TEST_CASE(test_assign_operator) { - dice::sparse_map::sparse_map map = {{0, 10}, {-2, 20}}; - BOOST_CHECK_EQUAL(map.size(), 2); - - map = {{1, 3}, {2, 4}}; - BOOST_CHECK_EQUAL(map.size(), 2); - BOOST_CHECK_EQUAL(map.at(1), 3); - BOOST_CHECK_EQUAL(map.at(2), 4); - BOOST_CHECK(map.find(0) == map.end()); - - map = {}; - BOOST_CHECK(map.empty()); -} - -/** - * move/copy constructor/operator - */ -BOOST_AUTO_TEST_CASE(test_move_constructor) { - // insert x values in map, move map into map_move with move constructor, check - // map and map_move, insert additional values in map_move, check map_move - using HMap = dice::sparse_map::sparse_map; - - const std::size_t nb_values = 100; - HMap map = utils::get_filled_hash_map(nb_values); - HMap map_move(std::move(map)); - - BOOST_CHECK(map_move == utils::get_filled_hash_map(nb_values)); - BOOST_CHECK(map == (HMap())); - - for (std::size_t i = nb_values; i < nb_values * 2; i++) { - map_move.insert( - {utils::get_key(i), utils::get_value(i)}); - } - - BOOST_CHECK_EQUAL(map_move.size(), nb_values * 2); - BOOST_CHECK(map_move == utils::get_filled_hash_map(nb_values * 2)); -} - -BOOST_AUTO_TEST_CASE(test_move_constructor_empty) { - dice::sparse_map::sparse_map map(0); - dice::sparse_map::sparse_map map_move(std::move(map)); - - BOOST_CHECK(map.empty()); - BOOST_CHECK(map_move.empty()); - - BOOST_CHECK(map.find("") == map.end()); - BOOST_CHECK(map_move.find("") == map_move.end()); -} - -BOOST_AUTO_TEST_CASE(test_move_operator) { - // insert x values in map, move map into map_move, check map and map_move, - // insert additional values in map_move, check map_move - using HMap = dice::sparse_map::sparse_map; - - const std::size_t nb_values = 100; - HMap map = utils::get_filled_hash_map(nb_values); - HMap map_move = utils::get_filled_hash_map(1); - map_move = std::move(map); - - BOOST_CHECK(map_move == utils::get_filled_hash_map(nb_values)); - BOOST_CHECK(map == (HMap())); - - for (std::size_t i = nb_values; i < nb_values * 2; i++) { - map_move.insert( - {utils::get_key(i), utils::get_value(i)}); - } - - BOOST_CHECK_EQUAL(map_move.size(), nb_values * 2); - BOOST_CHECK(map_move == utils::get_filled_hash_map(nb_values * 2)); -} - -BOOST_AUTO_TEST_CASE(test_move_operator_empty) { - dice::sparse_map::sparse_map map(0); - dice::sparse_map::sparse_map map_move; - map_move = (std::move(map)); - - BOOST_CHECK(map.empty()); - BOOST_CHECK(map_move.empty()); - - BOOST_CHECK(map.find("") == map.end()); - BOOST_CHECK(map_move.find("") == map_move.end()); -} - -BOOST_AUTO_TEST_CASE(test_reassign_moved_object_move_constructor) { - using HMap = dice::sparse_map::sparse_map; - - HMap map = {{"Key1", "Value1"}, {"Key2", "Value2"}, {"Key3", "Value3"}}; - HMap map_move(std::move(map)); - - BOOST_CHECK_EQUAL(map_move.size(), 3); - BOOST_CHECK_EQUAL(map.size(), 0); - - map = {{"Key4", "Value4"}, {"Key5", "Value5"}}; - BOOST_CHECK(map == (HMap({{"Key4", "Value4"}, {"Key5", "Value5"}}))); -} - -BOOST_AUTO_TEST_CASE(test_reassign_moved_object_move_operator) { - using HMap = dice::sparse_map::sparse_map; - - HMap map = {{"Key1", "Value1"}, {"Key2", "Value2"}, {"Key3", "Value3"}}; - HMap map_move = std::move(map); - - BOOST_CHECK_EQUAL(map_move.size(), 3); - BOOST_CHECK_EQUAL(map.size(), 0); - - map = {{"Key4", "Value4"}, {"Key5", "Value5"}}; - BOOST_CHECK(map == (HMap({{"Key4", "Value4"}, {"Key5", "Value5"}}))); -} - -BOOST_AUTO_TEST_CASE(test_use_after_move_constructor) { - using HMap = dice::sparse_map::sparse_map; - - const std::size_t nb_values = 100; - HMap map = utils::get_filled_hash_map(nb_values); - HMap map_move(std::move(map)); - - BOOST_CHECK(map == (HMap())); - BOOST_CHECK_EQUAL(map.size(), 0); - BOOST_CHECK_EQUAL(map.bucket_count(), 0); - BOOST_CHECK_EQUAL(map.erase("a"), 0); - BOOST_CHECK(map.find("a") == map.end()); - - for (std::size_t i = 0; i < nb_values; i++) { - map.insert( - {utils::get_key(i), utils::get_value(i)}); - } - - BOOST_CHECK_EQUAL(map.size(), nb_values); - BOOST_CHECK(map == map_move); -} - -BOOST_AUTO_TEST_CASE(test_use_after_move_operator) { - using HMap = dice::sparse_map::sparse_map; - - const std::size_t nb_values = 100; - HMap map = utils::get_filled_hash_map(nb_values); - HMap map_move(0); - map_move = std::move(map); - - BOOST_CHECK(map == (HMap())); - BOOST_CHECK_EQUAL(map.size(), 0); - BOOST_CHECK_EQUAL(map.bucket_count(), 0); - BOOST_CHECK_EQUAL(map.erase("a"), 0); - BOOST_CHECK(map.find("a") == map.end()); - - for (std::size_t i = 0; i < nb_values; i++) { - map.insert( - {utils::get_key(i), utils::get_value(i)}); - } - - BOOST_CHECK_EQUAL(map.size(), nb_values); - BOOST_CHECK(map == map_move); -} - -BOOST_AUTO_TEST_CASE(test_copy_constructor_and_operator) { - using HMap = dice::sparse_map::sparse_map>; - - const std::size_t nb_values = 100; - HMap map = utils::get_filled_hash_map(nb_values); - - HMap map_copy = map; - HMap map_copy2(map); - HMap map_copy3 = utils::get_filled_hash_map(1); - map_copy3 = map; - - BOOST_CHECK(map == map_copy); - map.clear(); - - BOOST_CHECK(map_copy == map_copy2); - BOOST_CHECK(map_copy == map_copy3); -} - -BOOST_AUTO_TEST_CASE(test_copy_constructor_empty) { - dice::sparse_map::sparse_map map(0); - dice::sparse_map::sparse_map map_copy(map); - - BOOST_CHECK(map.empty()); - BOOST_CHECK(map_copy.empty()); - - BOOST_CHECK(map.find("") == map.end()); - BOOST_CHECK(map_copy.find("") == map_copy.end()); -} - -BOOST_AUTO_TEST_CASE(test_copy_operator_empty) { - dice::sparse_map::sparse_map map(0); - dice::sparse_map::sparse_map map_copy(16); - map_copy = map; - - BOOST_CHECK(map.empty()); - BOOST_CHECK(map_copy.empty()); - - BOOST_CHECK(map.find("") == map.end()); - BOOST_CHECK(map_copy.find("") == map_copy.end()); -} - -/** - * at - */ -BOOST_AUTO_TEST_CASE(test_at) { - // insert x values, use at for known and unknown values. - const dice::sparse_map::sparse_map map = {{0, 10}, {-2, 20}}; - - BOOST_CHECK_EQUAL(map.at(0), 10); - BOOST_CHECK_EQUAL(map.at(-2), 20); - - std::int64_t no_discard_dummy; - BOOST_CHECK_THROW(no_discard_dummy = map.at(1), std::out_of_range); - (void) no_discard_dummy; -} - -/** - * contains - */ -BOOST_AUTO_TEST_CASE(test_contains) { - const dice::sparse_map::sparse_map map = {{0, 10}, {-2, 20}}; - - BOOST_CHECK(map.contains(0)); - BOOST_CHECK(map.contains(-2)); - BOOST_CHECK(!map.contains(-3)); -} - -/** - * equal_range - */ -BOOST_AUTO_TEST_CASE(test_equal_range) { - const dice::sparse_map::sparse_map map = {{0, 10}, {-2, 20}}; - - auto it_pair = map.equal_range(0); - BOOST_REQUIRE_EQUAL(std::distance(it_pair.first, it_pair.second), 1); - BOOST_CHECK_EQUAL(it_pair.first->second, 10); - - it_pair = map.equal_range(1); - BOOST_CHECK(it_pair.first == it_pair.second); - BOOST_CHECK(it_pair.first == map.end()); -} - -/** - * operator[] - */ -BOOST_AUTO_TEST_CASE(test_access_operator) { - // insert x values, use at for known and unknown values. - dice::sparse_map::sparse_map map = {{0, 10}, {-2, 20}}; - - BOOST_CHECK_EQUAL(map[0], 10); - BOOST_CHECK_EQUAL(map[-2], 20); - BOOST_CHECK_EQUAL(map[2], std::int64_t()); - - BOOST_CHECK_EQUAL(map.size(), 3); -} - -/** - * swap - */ -BOOST_AUTO_TEST_CASE(test_swap) { - dice::sparse_map::sparse_map map = {{1, 10}, {8, 80}, {3, 30}}; - dice::sparse_map::sparse_map map2 = {{4, 40}, {5, 50}}; - - using std::swap; - swap(map, map2); - - BOOST_CHECK(map == - (dice::sparse_map::sparse_map{{4, 40}, {5, 50}})); - BOOST_CHECK(map2 == (dice::sparse_map::sparse_map{ - {1, 10}, {8, 80}, {3, 30}})); - - map.insert({6, 60}); - map2.insert({4, 40}); - - BOOST_CHECK(map == (dice::sparse_map::sparse_map{ - {4, 40}, {5, 50}, {6, 60}})); - BOOST_CHECK(map2 == (dice::sparse_map::sparse_map{ - {1, 10}, {8, 80}, {3, 30}, {4, 40}})); -} - -BOOST_AUTO_TEST_CASE(test_swap_empty) { - dice::sparse_map::sparse_map map = {{1, 10}, {8, 80}, {3, 30}}; - dice::sparse_map::sparse_map map2; - - using std::swap; - swap(map, map2); - - BOOST_CHECK(map == (dice::sparse_map::sparse_map{})); - BOOST_CHECK(map2 == (dice::sparse_map::sparse_map{ - {1, 10}, {8, 80}, {3, 30}})); - - map.insert({6, 60}); - map2.insert({4, 40}); - - BOOST_CHECK(map == (dice::sparse_map::sparse_map{{6, 60}})); - BOOST_CHECK(map2 == (dice::sparse_map::sparse_map{ - {1, 10}, {8, 80}, {3, 30}, {4, 40}})); -} + TEST_CASE("extreme bucket count value construction") { + CHECK_THROWS_AS( + (dice::sparse_map::sparse_map, std::equal_to, + std::allocator>, + dice::sparse_map::power_of_two_growth_policy<2>>( + std::numeric_limits::max())), + std::length_error); + + CHECK_THROWS_AS( + (dice::sparse_map::sparse_map, std::equal_to, + std::allocator>, + dice::sparse_map::power_of_two_growth_policy<2>>( + std::numeric_limits::max() / 2 + 1)), + std::length_error); + + CHECK_THROWS_AS( + (dice::sparse_map::sparse_map, std::equal_to, + std::allocator>, + dice::sparse_map::prime_growth_policy>( + std::numeric_limits::max())), + std::length_error); + + CHECK_THROWS_AS( + (dice::sparse_map::sparse_map, std::equal_to, + std::allocator>, + dice::sparse_map::prime_growth_policy>( + std::numeric_limits::max() / 2)), + std::length_error); + + CHECK_THROWS_AS( + (dice::sparse_map::sparse_map, std::equal_to, + std::allocator>, + dice::sparse_map::mod_growth_policy<>>( + std::numeric_limits::max())), + std::length_error); + } + + TEST_CASE("range construct") { + dice::sparse_map::sparse_map map = {{2, 1}, {1, 0}, {3, 2}}; + + dice::sparse_map::sparse_map map2(map.begin(), map.end()); + dice::sparse_map::sparse_map map3(map.cbegin(), map.cend()); + } + + /** + * operator=(std::initializer_list) + */ + TEST_CASE("assign op") { + dice::sparse_map::sparse_map map = {{0, 10}, {-2, 20}}; + CHECK_EQ(map.size(), 2); + + map = {{1, 3}, {2, 4}}; + CHECK_EQ(map.size(), 2); + CHECK_EQ(map.at(1), 3); + CHECK_EQ(map.at(2), 4); + CHECK(map.find(0) == map.end()); + + map = {}; + CHECK(map.empty()); + } + + /** + * move/copy constructor/operator + */ + TEST_CASE("move ctor") { + // insert x values in map, move map into map_move with move constructor, check + // map and map_move, insert additional values in map_move, check map_move + using HMap = dice::sparse_map::sparse_map; + + const std::size_t nb_values = 100; + HMap map = utils::get_filled_hash_map(nb_values); + HMap map_move(std::move(map)); + + CHECK(map_move == utils::get_filled_hash_map(nb_values)); + CHECK(map == (HMap())); + + for (std::size_t i = nb_values; i < nb_values * 2; i++) { + map_move.insert( + {utils::get_key(i), utils::get_value(i)}); + } + + CHECK_EQ(map_move.size(), nb_values * 2); + CHECK(map_move == utils::get_filled_hash_map(nb_values * 2)); + } + + TEST_CASE("move ctor empty") { + dice::sparse_map::sparse_map map(0); + dice::sparse_map::sparse_map map_move(std::move(map)); + + CHECK(map.empty()); + CHECK(map_move.empty()); + + CHECK(map.find("") == map.end()); + CHECK(map_move.find("") == map_move.end()); + } + + TEST_CASE("move op") { + // insert x values in map, move map into map_move, check map and map_move, + // insert additional values in map_move, check map_move + using HMap = dice::sparse_map::sparse_map; + + const std::size_t nb_values = 100; + HMap map = utils::get_filled_hash_map(nb_values); + HMap map_move = utils::get_filled_hash_map(1); + map_move = std::move(map); + + CHECK(map_move == utils::get_filled_hash_map(nb_values)); + CHECK(map == (HMap())); + + for (std::size_t i = nb_values; i < nb_values * 2; i++) { + map_move.insert( + {utils::get_key(i), utils::get_value(i)}); + } + + CHECK_EQ(map_move.size(), nb_values * 2); + CHECK(map_move == utils::get_filled_hash_map(nb_values * 2)); + } + + TEST_CASE("move op empty") { + dice::sparse_map::sparse_map map(0); + dice::sparse_map::sparse_map map_move; + map_move = (std::move(map)); + + CHECK(map.empty()); + CHECK(map_move.empty()); + + CHECK(map.find("") == map.end()); + CHECK(map_move.find("") == map_move.end()); + } + + TEST_CASE("reassign moved object move ctor") { + using HMap = dice::sparse_map::sparse_map; + + HMap map = {{"Key1", "Value1"}, {"Key2", "Value2"}, {"Key3", "Value3"}}; + HMap map_move(std::move(map)); + + CHECK_EQ(map_move.size(), 3); + CHECK_EQ(map.size(), 0); + + map = {{"Key4", "Value4"}, {"Key5", "Value5"}}; + CHECK(map == (HMap({{"Key4", "Value4"}, {"Key5", "Value5"}}))); + } + + TEST_CASE("reassign moved object move op") { + using HMap = dice::sparse_map::sparse_map; + + HMap map = {{"Key1", "Value1"}, {"Key2", "Value2"}, {"Key3", "Value3"}}; + HMap map_move = std::move(map); -/** - * KeyEqual - */ -BOOST_AUTO_TEST_CASE(test_key_equal) { - // Use a KeyEqual and Hash where any odd unsigned number 'x' is equal to - // 'x-1'. Make sure that KeyEqual is called (and not ==). - struct hash { - std::size_t operator()(std::uint64_t v) const { - if (v % 2u == 1u) { - return std::hash()(v - 1); - } else { - return std::hash()(v); - } - } - }; - - struct key_equal { - bool operator()(std::uint64_t lhs, std::uint64_t rhs) const { - if (lhs % 2u == 1u) { - lhs--; - } - - if (rhs % 2u == 1u) { - rhs--; - } - - return lhs == rhs; - } - }; - - dice::sparse_map::sparse_map map; - BOOST_CHECK(map.insert({2, 10}).second); - BOOST_CHECK_EQUAL(map.at(2), 10); - BOOST_CHECK_EQUAL(map.at(3), 10); - BOOST_CHECK(!map.insert({3, 10}).second); - - BOOST_CHECK_EQUAL(map.size(), 1); -} - -/** - * other - */ -BOOST_AUTO_TEST_CASE( - test_operations_with_all_buckets_marked_as_deleted_or_with_a_value) { - // Test find/erase/insert operations on a map which we craft to have all its - // buckets marked as deleted or containing a value to be sure that everything - // works well in this edge case. Intrusive test (it's tightly coupled with the - // implementation of the map). - struct identity_hash { - std::size_t operator()(unsigned int value) const { - return std::size_t(value); - } - }; - - dice::sparse_map::sparse_map map; - map.max_load_factor(0.8f); - map.rehash(64); - - BOOST_CHECK_EQUAL(map.bucket_count(), 64); - BOOST_CHECK_EQUAL(map.max_load_factor(), 0.8f); - - for (unsigned int i = 0; i < 51; i++) { - BOOST_CHECK(map.insert({i, i}).second); - } - - for (unsigned int i = 0; i < 14; i++) { - BOOST_CHECK_EQUAL(map.erase(i), 1); - } - - for (unsigned int i = 51; i < 64; i++) { - BOOST_CHECK(map.insert({i, i}).second); - } - - BOOST_CHECK_EQUAL(map.size(), 50); - BOOST_CHECK_EQUAL(map.bucket_count(), 64); - - /** - * Map full of buckets marked as deleted or with a value. Check that find, - * erase and insert operations work well. - */ - - // Find inexistent values. - for (unsigned int i = 0; i < 14; i++) { - BOOST_CHECK(map.find(i) == map.end()); - } - - // Erase inexistent values. - for (unsigned int i = 0; i < 14; i++) { - BOOST_CHECK_EQUAL(map.erase(i), 0); - } - BOOST_CHECK_EQUAL(map.size(), 50); - BOOST_CHECK_EQUAL(map.bucket_count(), 64); - - // Try to insert existing values. - for (unsigned int i = 14; i < 64; i++) { - BOOST_CHECK(!map.insert({i, i}).second); - } - BOOST_CHECK_EQUAL(map.size(), 50); - BOOST_CHECK_EQUAL(map.bucket_count(), 64); - - // Insert new values - for (unsigned int i = 0; i < 14; i++) { - BOOST_CHECK(map.insert({i, i}).second); - } - BOOST_CHECK_EQUAL(map.size(), 64); - BOOST_CHECK_EQUAL(map.bucket_count(), 128); -} - -BOOST_AUTO_TEST_CASE(test_heterogeneous_lookups) { - struct hash_ptr { - std::size_t operator()(const std::unique_ptr& p) const { - return std::hash()( - reinterpret_cast(p.get())); - } - - std::size_t operator()(std::uintptr_t p) const { - return std::hash()(p); - } - - std::size_t operator()(const int* const& p) const { - return std::hash()(reinterpret_cast(p)); - } - }; - - struct equal_to_ptr { - using is_transparent = std::true_type; - - bool operator()(const std::unique_ptr& p1, - const std::unique_ptr& p2) const { - return p1 == p2; - } - - bool operator()(const std::unique_ptr& p1, std::uintptr_t p2) const { - return reinterpret_cast(p1.get()) == p2; - } - - bool operator()(std::uintptr_t p1, const std::unique_ptr& p2) const { - return p1 == reinterpret_cast(p2.get()); - } - - bool operator()(const std::unique_ptr& p1, - const int* const& p2) const { - return p1.get() == p2; - } - - bool operator()(const int* const& p1, - const std::unique_ptr& p2) const { - return p1 == p2.get(); - } - }; + CHECK_EQ(map_move.size(), 3); + CHECK_EQ(map.size(), 0); + + map = {{"Key4", "Value4"}, {"Key5", "Value5"}}; + CHECK(map == (HMap({{"Key4", "Value4"}, {"Key5", "Value5"}}))); + } - std::unique_ptr ptr1(new int(1)); - std::unique_ptr ptr2(new int(2)); - std::unique_ptr ptr3(new int(3)); - int other = -1; + TEST_CASE("use after move ctor") { + using HMap = dice::sparse_map::sparse_map; - const std::uintptr_t addr1 = reinterpret_cast(ptr1.get()); - const int* const addr2 = ptr2.get(); - const int* const addr_unknown = &other; + const std::size_t nb_values = 100; + HMap map = utils::get_filled_hash_map(nb_values); + HMap map_move(std::move(map)); - dice::sparse_map::sparse_map, int, hash_ptr, equal_to_ptr> map; - map.insert({std::move(ptr1), 4}); - map.insert({std::move(ptr2), 5}); - map.insert({std::move(ptr3), 6}); + CHECK(map == (HMap())); + CHECK_EQ(map.size(), 0); + CHECK_EQ(map.bucket_count(), 0); + CHECK_EQ(map.erase("a"), 0); + CHECK(map.find("a") == map.end()); - BOOST_CHECK_EQUAL(map.size(), 3); + for (std::size_t i = 0; i < nb_values; i++) { + map.insert( + {utils::get_key(i), utils::get_value(i)}); + } - BOOST_CHECK_EQUAL(map.at(addr1), 4); - BOOST_CHECK_EQUAL(map.at(addr2), 5); + CHECK_EQ(map.size(), nb_values); + CHECK(map == map_move); + } - int no_discard_dummy; - BOOST_CHECK_THROW(no_discard_dummy = map.at(addr_unknown), std::out_of_range); - (void) no_discard_dummy; + TEST_CASE("use after move op") { + using HMap = dice::sparse_map::sparse_map; - BOOST_REQUIRE(map.find(addr1) != map.end()); - BOOST_CHECK_EQUAL(*map.find(addr1)->first, 1); + const std::size_t nb_values = 100; + HMap map = utils::get_filled_hash_map(nb_values); + HMap map_move(0); + map_move = std::move(map); - BOOST_REQUIRE(map.find(addr2) != map.end()); - BOOST_CHECK_EQUAL(*map.find(addr2)->first, 2); + CHECK(map == (HMap())); + CHECK_EQ(map.size(), 0); + CHECK_EQ(map.bucket_count(), 0); + CHECK_EQ(map.erase("a"), 0); + CHECK(map.find("a") == map.end()); - BOOST_CHECK(map.find(addr_unknown) == map.end()); + for (std::size_t i = 0; i < nb_values; i++) { + map.insert( + {utils::get_key(i), utils::get_value(i)}); + } - BOOST_CHECK_EQUAL(map.count(addr1), 1); - BOOST_CHECK_EQUAL(map.count(addr2), 1); - BOOST_CHECK_EQUAL(map.count(addr_unknown), 0); + CHECK_EQ(map.size(), nb_values); + CHECK(map == map_move); + } - BOOST_CHECK_EQUAL(map.erase(addr1), 1); - BOOST_CHECK_EQUAL(map.erase(addr2), 1); - BOOST_CHECK_EQUAL(map.erase(addr_unknown), 0); + TEST_CASE("copy ctor and op") { + using HMap = dice::sparse_map::sparse_map>; - BOOST_CHECK_EQUAL(map.size(), 1); -} - -/** - * Various operations on empty map - */ -BOOST_AUTO_TEST_CASE(test_empty_map) { - dice::sparse_map::sparse_map map(0); - - BOOST_CHECK_EQUAL(map.bucket_count(), 0); - BOOST_CHECK_EQUAL(map.size(), 0); - BOOST_CHECK_EQUAL(map.load_factor(), 0); - BOOST_CHECK(map.empty()); - - BOOST_CHECK(map.begin() == map.end()); - BOOST_CHECK(map.begin() == map.cend()); - BOOST_CHECK(map.cbegin() == map.cend()); - - BOOST_CHECK(map.find("") == map.end()); - BOOST_CHECK(map.find("test") == map.end()); - - BOOST_CHECK_EQUAL(map.count(""), 0); - BOOST_CHECK_EQUAL(map.count("test"), 0); + const std::size_t nb_values = 100; + HMap map = utils::get_filled_hash_map(nb_values); - BOOST_CHECK(!map.contains("")); - BOOST_CHECK(!map.contains("test")); + HMap map_copy = map; + HMap map_copy2(map); + HMap map_copy3 = utils::get_filled_hash_map(1); + map_copy3 = map; + + CHECK(map == map_copy); + map.clear(); + + CHECK(map_copy == map_copy2); + CHECK(map_copy == map_copy3); + } + + TEST_CASE("copy ctor empty") { + dice::sparse_map::sparse_map map(0); + dice::sparse_map::sparse_map map_copy(map); - int no_discard_dummy; - BOOST_CHECK_THROW(no_discard_dummy = map.at(""), std::out_of_range); - BOOST_CHECK_THROW(no_discard_dummy = map.at("test"), std::out_of_range); - (void) no_discard_dummy; - - auto range = map.equal_range("test"); - BOOST_CHECK(range.first == range.second); - - BOOST_CHECK_EQUAL(map.erase("test"), 0); - BOOST_CHECK(map.erase(map.begin(), map.end()) == map.end()); - - BOOST_CHECK_EQUAL(map["new value"], int{}); -} - -/** - * Test precalculated hash - */ -BOOST_AUTO_TEST_CASE(test_precalculated_hash) { - dice::sparse_map::sparse_map> map = { - {1, -1}, {2, -2}, {3, -3}, {4, -4}, {5, -5}, {6, -6}}; - const dice::sparse_map::sparse_map> map_const = map; - - /** - * find - */ - BOOST_REQUIRE(map.find(3, map.hash_function()(3)) != map.end()); - BOOST_CHECK_EQUAL(map.find(3, map.hash_function()(3))->second, -3); - - BOOST_REQUIRE(map_const.find(3, map_const.hash_function()(3)) != - map_const.end()); - BOOST_CHECK_EQUAL(map_const.find(3, map_const.hash_function()(3))->second, - -3); - - /** - * at - */ - BOOST_CHECK_EQUAL(map.at(3, map.hash_function()(3)), -3); - BOOST_CHECK_EQUAL(map_const.at(3, map_const.hash_function()(3)), -3); - - /** - * contains - */ - BOOST_CHECK(map.contains(3, map.hash_function()(3))); - BOOST_CHECK(map_const.contains(3, map_const.hash_function()(3))); - - /** - * count - */ - BOOST_CHECK_EQUAL(map.count(3, map.hash_function()(3)), 1); - BOOST_CHECK_EQUAL(map_const.count(3, map_const.hash_function()(3)), 1); - - /** - * equal_range - */ - auto it_range = map.equal_range(3, map.hash_function()(3)); - BOOST_REQUIRE_EQUAL(std::distance(it_range.first, it_range.second), 1); - BOOST_CHECK_EQUAL(it_range.first->second, -3); - - auto it_range_const = map_const.equal_range(3, map_const.hash_function()(3)); - BOOST_REQUIRE_EQUAL( - std::distance(it_range_const.first, it_range_const.second), 1); - BOOST_CHECK_EQUAL(it_range_const.first->second, -3); - - /** - * erase - */ - BOOST_CHECK_EQUAL(map.erase(3, map.hash_function()(3)), 1); + CHECK(map.empty()); + CHECK(map_copy.empty()); + + CHECK(map.find("") == map.end()); + CHECK(map_copy.find("") == map_copy.end()); + } + + TEST_CASE("copy op empty") { + dice::sparse_map::sparse_map map(0); + dice::sparse_map::sparse_map map_copy(16); + map_copy = map; + + CHECK(map.empty()); + CHECK(map_copy.empty()); + + CHECK(map.find("") == map.end()); + CHECK(map_copy.find("") == map_copy.end()); + } + + TEST_CASE("at") { + // insert x values, use at for known and unknown values. + const dice::sparse_map::sparse_map map = {{0, 10}, {-2, 20}}; + + CHECK_EQ(map.at(0), 10); + CHECK_EQ(map.at(-2), 20); + + std::int64_t no_discard_dummy; + CHECK_THROWS_AS(no_discard_dummy = map.at(1), std::out_of_range); + (void) no_discard_dummy; + } + + TEST_CASE("contains") { + const dice::sparse_map::sparse_map map = {{0, 10}, {-2, 20}}; + + CHECK(map.contains(0)); + CHECK(map.contains(-2)); + CHECK(!map.contains(-3)); + } + + TEST_CASE("equal range") { + const dice::sparse_map::sparse_map map = {{0, 10}, {-2, 20}}; + + auto it_pair = map.equal_range(0); + REQUIRE_EQ(std::distance(it_pair.first, it_pair.second), 1); + CHECK_EQ(it_pair.first->second, 10); + + it_pair = map.equal_range(1); + CHECK(it_pair.first == it_pair.second); + CHECK(it_pair.first == map.end()); + } + + TEST_CASE("index op") { + // insert x values, use at for known and unknown values. + dice::sparse_map::sparse_map map = {{0, 10}, {-2, 20}}; + + CHECK_EQ(map[0], 10); + CHECK_EQ(map[-2], 20); + CHECK_EQ(map[2], std::int64_t()); + + CHECK_EQ(map.size(), 3); + } + + /** + * swap + */ + TEST_CASE("swap") { + dice::sparse_map::sparse_map map = {{1, 10}, {8, 80}, {3, 30}}; + dice::sparse_map::sparse_map map2 = {{4, 40}, {5, 50}}; + + using std::swap; + swap(map, map2); + + CHECK(map == + (dice::sparse_map::sparse_map{{4, 40}, {5, 50}})); + CHECK(map2 == (dice::sparse_map::sparse_map{ + {1, 10}, + {8, 80}, + {3, 30}})); + + map.insert({6, 60}); + map2.insert({4, 40}); + + CHECK(map == (dice::sparse_map::sparse_map{ + {4, 40}, + {5, 50}, + {6, 60}})); + CHECK(map2 == (dice::sparse_map::sparse_map{ + {1, 10}, + {8, 80}, + {3, 30}, + {4, 40}})); + } + + TEST_CASE("swap empty") { + dice::sparse_map::sparse_map map = {{1, 10}, {8, 80}, {3, 30}}; + dice::sparse_map::sparse_map map2; + + using std::swap; + swap(map, map2); + + CHECK(map == (dice::sparse_map::sparse_map{})); + CHECK(map2 == (dice::sparse_map::sparse_map{ + {1, 10}, + {8, 80}, + {3, 30}})); + + map.insert({6, 60}); + map2.insert({4, 40}); + + CHECK(map == (dice::sparse_map::sparse_map{{6, 60}})); + CHECK(map2 == (dice::sparse_map::sparse_map{ + {1, 10}, + {8, 80}, + {3, 30}, + {4, 40}})); + } + + TEST_CASE("key equal") { + // Use a KeyEqual and Hash where any odd unsigned number 'x' is equal to + // 'x-1'. Make sure that KeyEqual is called (and not ==). + struct hash { + std::size_t operator()(std::uint64_t v) const { + if (v % 2u == 1u) { + return std::hash()(v - 1); + } else { + return std::hash()(v); + } + } + }; + + struct key_equal { + bool operator()(std::uint64_t lhs, std::uint64_t rhs) const { + if (lhs % 2u == 1u) { + lhs--; + } + + if (rhs % 2u == 1u) { + rhs--; + } + + return lhs == rhs; + } + }; + + dice::sparse_map::sparse_map map; + CHECK(map.insert({2, 10}).second); + CHECK_EQ(map.at(2), 10); + CHECK_EQ(map.at(3), 10); + CHECK(!map.insert({3, 10}).second); + + CHECK_EQ(map.size(), 1); + } + + /** + * other + */ + TEST_CASE("operations with all buckets marked as deleted or with a value") { + // Test find/erase/insert operations on a map which we craft to have all its + // buckets marked as deleted or containing a value to be sure that everything + // works well in this edge case. Intrusive test (it's tightly coupled with the + // implementation of the map). + struct identity_hash { + std::size_t operator()(unsigned int value) const { + return std::size_t(value); + } + }; + + dice::sparse_map::sparse_map map; + map.max_load_factor(0.8f); + map.rehash(64); + + CHECK_EQ(map.bucket_count(), 64); + CHECK_EQ(map.max_load_factor(), 0.8f); + + for (unsigned int i = 0; i < 51; i++) { + CHECK(map.insert({i, i}).second); + } + + for (unsigned int i = 0; i < 14; i++) { + CHECK_EQ(map.erase(i), 1); + } + + for (unsigned int i = 51; i < 64; i++) { + CHECK(map.insert({i, i}).second); + } + + CHECK_EQ(map.size(), 50); + CHECK_EQ(map.bucket_count(), 64); + + /** + * Map full of buckets marked as deleted or with a value. Check that find, + * erase and insert operations work well. + */ + + // Find inexistent values. + for (unsigned int i = 0; i < 14; i++) { + CHECK(map.find(i) == map.end()); + } + + // Erase inexistent values. + for (unsigned int i = 0; i < 14; i++) { + CHECK_EQ(map.erase(i), 0); + } + CHECK_EQ(map.size(), 50); + CHECK_EQ(map.bucket_count(), 64); + + // Try to insert existing values. + for (unsigned int i = 14; i < 64; i++) { + CHECK(!map.insert({i, i}).second); + } + CHECK_EQ(map.size(), 50); + CHECK_EQ(map.bucket_count(), 64); + + // Insert new values + for (unsigned int i = 0; i < 14; i++) { + CHECK(map.insert({i, i}).second); + } + CHECK_EQ(map.size(), 64); + CHECK_EQ(map.bucket_count(), 128); + } + + TEST_CASE("heterogeneous lookup") { + struct hash_ptr { + std::size_t operator()(const std::unique_ptr &p) const { + return std::hash()( + reinterpret_cast(p.get())); + } + + std::size_t operator()(std::uintptr_t p) const { + return std::hash()(p); + } + + std::size_t operator()(const int *const &p) const { + return std::hash()(reinterpret_cast(p)); + } + }; + + struct equal_to_ptr { + using is_transparent = std::true_type; + + bool operator()(const std::unique_ptr &p1, + const std::unique_ptr &p2) const { + return p1 == p2; + } + + bool operator()(const std::unique_ptr &p1, std::uintptr_t p2) const { + return reinterpret_cast(p1.get()) == p2; + } + + bool operator()(std::uintptr_t p1, const std::unique_ptr &p2) const { + return p1 == reinterpret_cast(p2.get()); + } + + bool operator()(const std::unique_ptr &p1, + const int *const &p2) const { + return p1.get() == p2; + } + + bool operator()(const int *const &p1, + const std::unique_ptr &p2) const { + return p1 == p2.get(); + } + }; + + std::unique_ptr ptr1(new int(1)); + std::unique_ptr ptr2(new int(2)); + std::unique_ptr ptr3(new int(3)); + int other = -1; + + const std::uintptr_t addr1 = reinterpret_cast(ptr1.get()); + const int *const addr2 = ptr2.get(); + const int *const addr_unknown = &other; + + dice::sparse_map::sparse_map, int, hash_ptr, equal_to_ptr> map; + map.insert({std::move(ptr1), 4}); + map.insert({std::move(ptr2), 5}); + map.insert({std::move(ptr3), 6}); + + CHECK_EQ(map.size(), 3); + + CHECK_EQ(map.at(addr1), 4); + CHECK_EQ(map.at(addr2), 5); + + int no_discard_dummy; + CHECK_THROWS_AS(no_discard_dummy = map.at(addr_unknown), std::out_of_range); + (void) no_discard_dummy; + + REQUIRE(map.find(addr1) != map.end()); + CHECK_EQ(*map.find(addr1)->first, 1); + + REQUIRE(map.find(addr2) != map.end()); + CHECK_EQ(*map.find(addr2)->first, 2); + + CHECK(map.find(addr_unknown) == map.end()); + + CHECK_EQ(map.count(addr1), 1); + CHECK_EQ(map.count(addr2), 1); + CHECK_EQ(map.count(addr_unknown), 0); + + CHECK_EQ(map.erase(addr1), 1); + CHECK_EQ(map.erase(addr2), 1); + CHECK_EQ(map.erase(addr_unknown), 0); + + CHECK_EQ(map.size(), 1); + } + + /** + * Various operations on empty map + */ + TEST_CASE("empty map") { + dice::sparse_map::sparse_map map(0); + + CHECK_EQ(map.bucket_count(), 0); + CHECK_EQ(map.size(), 0); + CHECK_EQ(map.load_factor(), 0); + CHECK(map.empty()); + + CHECK(map.begin() == map.end()); + CHECK(map.begin() == map.cend()); + CHECK(map.cbegin() == map.cend()); + + CHECK(map.find("") == map.end()); + CHECK(map.find("test") == map.end()); + + CHECK_EQ(map.count(""), 0); + CHECK_EQ(map.count("test"), 0); + + CHECK(!map.contains("")); + CHECK(!map.contains("test")); + + int no_discard_dummy; + CHECK_THROWS_AS(no_discard_dummy = map.at(""), std::out_of_range); + CHECK_THROWS_AS(no_discard_dummy = map.at("test"), std::out_of_range); + (void) no_discard_dummy; + + auto range = map.equal_range("test"); + CHECK(range.first == range.second); + + CHECK_EQ(map.erase("test"), 0); + CHECK(map.erase(map.begin(), map.end()) == map.end()); + + CHECK_EQ(map["new value"], int{}); + } + + TEST_CASE("precalculated hash") { + dice::sparse_map::sparse_map> map = { + {1, -1}, + {2, -2}, + {3, -3}, + {4, -4}, + {5, -5}, + {6, -6}}; + const dice::sparse_map::sparse_map> map_const = map; + + /** + * find + */ + REQUIRE(map.find(3, map.hash_function()(3)) != map.end()); + CHECK_EQ(map.find(3, map.hash_function()(3))->second, -3); + + REQUIRE(map_const.find(3, map_const.hash_function()(3)) != + map_const.end()); + CHECK_EQ(map_const.find(3, map_const.hash_function()(3))->second, + -3); + + /** + * at + */ + CHECK_EQ(map.at(3, map.hash_function()(3)), -3); + CHECK_EQ(map_const.at(3, map_const.hash_function()(3)), -3); + + /** + * contains + */ + CHECK(map.contains(3, map.hash_function()(3))); + CHECK(map_const.contains(3, map_const.hash_function()(3))); + + /** + * count + */ + CHECK_EQ(map.count(3, map.hash_function()(3)), 1); + CHECK_EQ(map_const.count(3, map_const.hash_function()(3)), 1); + + /** + * equal_range + */ + auto it_range = map.equal_range(3, map.hash_function()(3)); + REQUIRE_EQ(std::distance(it_range.first, it_range.second), 1); + CHECK_EQ(it_range.first->second, -3); + + auto it_range_const = map_const.equal_range(3, map_const.hash_function()(3)); + REQUIRE_EQ( + std::distance(it_range_const.first, it_range_const.second), 1); + CHECK_EQ(it_range_const.first->second, -3); + + /** + * erase + */ + CHECK_EQ(map.erase(3, map.hash_function()(3)), 1); + } } - -BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/sparse_set_tests.cpp b/tests/sparse_set_tests.cpp index 50ccd14..487d76d 100644 --- a/tests/sparse_set_tests.cpp +++ b/tests/sparse_set_tests.cpp @@ -21,111 +21,105 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include + #include -#include -#include -#include -#include #include -#include #include #include -#include #include "utils.h" -BOOST_AUTO_TEST_SUITE(test_sparse_set) - -using test_types = - boost::mpl::list, - dice::sparse_map::sparse_set, - dice::sparse_map::sparse_set, - dice::sparse_map::sparse_set, - dice::sparse_map::sparse_pg_set, - dice::sparse_map::sparse_set, - std::equal_to, - std::allocator, - dice::sparse_map::prime_growth_policy>, - dice::sparse_map::sparse_set, - std::equal_to, - std::allocator, - dice::sparse_map::mod_growth_policy<>>, - dice::sparse_map::sparse_set, - std::equal_to, - std::allocator, - dice::sparse_map::mod_growth_policy<>>>; - -BOOST_AUTO_TEST_CASE_TEMPLATE(test_insert, HSet, test_types) { - // insert x values, insert them again, check values - using key_t = typename HSet::key_type; - - const std::size_t nb_values = 1000; - HSet set; - - for (std::size_t i = 0; i < nb_values; i++) { - auto [it, inserted] = set.insert(utils::get_key(i)); - - BOOST_CHECK_EQUAL(*it, utils::get_key(i)); - BOOST_CHECK(inserted); - } - BOOST_CHECK_EQUAL(set.size(), nb_values); - - for (std::size_t i = 0; i < nb_values; i++) { - auto [it, inserted] = set.insert(utils::get_key(i)); - - BOOST_CHECK_EQUAL(*it, utils::get_key(i)); - BOOST_CHECK(!inserted); - } - - for (std::size_t i = 0; i < nb_values; i++) { - auto it = set.find(utils::get_key(i)); - - BOOST_CHECK_EQUAL(*it, utils::get_key(i)); - } +#define TEST_SETS dice::sparse_map::sparse_set, \ + dice::sparse_map::sparse_set, \ + dice::sparse_map::sparse_set, \ + dice::sparse_map::sparse_set, \ + dice::sparse_map::sparse_pg_set, \ + dice::sparse_map::sparse_set, \ + std::equal_to, \ + std::allocator, \ + dice::sparse_map::prime_growth_policy>, \ + dice::sparse_map::sparse_set, \ + std::equal_to, \ + std::allocator, \ + dice::sparse_map::mod_growth_policy<>>, \ + dice::sparse_map::sparse_set < move_only_test, std::hash, \ + std::equal_to, \ + std::allocator, \ + dice::sparse_map::mod_growth_policy<>> + +TEST_SUITE("sparse set") { + TEST_CASE_TEMPLATE("insert", HSet, TEST_SETS) { + // insert x values, insert them again, check values + using key_t = typename HSet::key_type; + + const std::size_t nb_values = 1000; + HSet set; + + for (std::size_t i = 0; i < nb_values; i++) { + auto [it, inserted] = set.insert(utils::get_key(i)); + + CHECK_EQ(*it, utils::get_key(i)); + CHECK(inserted); + } + CHECK_EQ(set.size(), nb_values); + + for (std::size_t i = 0; i < nb_values; i++) { + auto [it, inserted] = set.insert(utils::get_key(i)); + + CHECK_EQ(*it, utils::get_key(i)); + CHECK(!inserted); + } + + for (std::size_t i = 0; i < nb_values; i++) { + auto it = set.find(utils::get_key(i)); + + CHECK_EQ(*it, utils::get_key(i)); + } + } + + TEST_CASE("compare") { + const dice::sparse_map::sparse_set set1 = {"a", "e", "d", "c", "b"}; + const dice::sparse_map::sparse_set set1_copy = {"e", "c", "b", "a", "d"}; + const dice::sparse_map::sparse_set set2 = {"e", "c", "b", "a", "d", "f"}; + const dice::sparse_map::sparse_set set3 = {"e", "c", "b", "a"}; + const dice::sparse_map::sparse_set set4 = {"a", "e", "d", "c", "z"}; + + CHECK(set1 == set1_copy); + CHECK(set1_copy == set1); + + CHECK(set1 != set2); + CHECK(set2 != set1); + + CHECK(set1 != set3); + CHECK(set3 != set1); + + CHECK(set1 != set4); + CHECK(set4 != set1); + + CHECK(set2 != set3); + CHECK(set3 != set2); + + CHECK(set2 != set4); + CHECK(set4 != set2); + + CHECK(set3 != set4); + CHECK(set4 != set3); + } + + TEST_CASE("insert pointer") { + // Test added mainly to be sure that the code compiles with MSVC + std::string value; + std::string* value_ptr = &value; + + dice::sparse_map::sparse_set set; + set.insert(value_ptr); + set.emplace(value_ptr); + + CHECK_EQ(set.size(), 1); + CHECK_EQ(**set.begin(), value); + } } - -BOOST_AUTO_TEST_CASE(test_compare) { - const dice::sparse_map::sparse_set set1 = {"a", "e", "d", "c", "b"}; - const dice::sparse_map::sparse_set set1_copy = {"e", "c", "b", "a", "d"}; - const dice::sparse_map::sparse_set set2 = {"e", "c", "b", "a", "d", "f"}; - const dice::sparse_map::sparse_set set3 = {"e", "c", "b", "a"}; - const dice::sparse_map::sparse_set set4 = {"a", "e", "d", "c", "z"}; - - BOOST_CHECK(set1 == set1_copy); - BOOST_CHECK(set1_copy == set1); - - BOOST_CHECK(set1 != set2); - BOOST_CHECK(set2 != set1); - - BOOST_CHECK(set1 != set3); - BOOST_CHECK(set3 != set1); - - BOOST_CHECK(set1 != set4); - BOOST_CHECK(set4 != set1); - - BOOST_CHECK(set2 != set3); - BOOST_CHECK(set3 != set2); - - BOOST_CHECK(set2 != set4); - BOOST_CHECK(set4 != set2); - - BOOST_CHECK(set3 != set4); - BOOST_CHECK(set4 != set3); -} - -BOOST_AUTO_TEST_CASE(test_insert_pointer) { - // Test added mainly to be sure that the code compiles with MSVC - std::string value; - std::string* value_ptr = &value; - - dice::sparse_map::sparse_set set; - set.insert(value_ptr); - set.emplace(value_ptr); - - BOOST_CHECK_EQUAL(set.size(), 1); - BOOST_CHECK_EQUAL(**set.begin(), value); -} - -BOOST_AUTO_TEST_SUITE_END() From 0c79106e6b68696922b93e212ae89721492d6ffe Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Thu, 3 Aug 2023 15:52:37 +0200 Subject: [PATCH 14/41] sparse buckets array v1 --- .../{sparse_array.hpp => sparse_bucket.hpp} | 43 +-- .../dice/sparse-map/sparse_bucket_array.hpp | 301 ++++++++++++++++++ .../dice/sparse-map/sparse_growth_policy.hpp | 1 + include/dice/sparse-map/sparse_hash.hpp | 300 +++++++---------- tests/fancy_pointer/sparse_array_tests.cpp | 4 +- 5 files changed, 435 insertions(+), 214 deletions(-) rename include/dice/sparse-map/{sparse_array.hpp => sparse_bucket.hpp} (93%) create mode 100644 include/dice/sparse-map/sparse_bucket_array.hpp diff --git a/include/dice/sparse-map/sparse_array.hpp b/include/dice/sparse-map/sparse_bucket.hpp similarity index 93% rename from include/dice/sparse-map/sparse_array.hpp rename to include/dice/sparse-map/sparse_bucket.hpp index 66ee85a..6bce2ee 100644 --- a/include/dice/sparse-map/sparse_array.hpp +++ b/include/dice/sparse-map/sparse_bucket.hpp @@ -1,5 +1,5 @@ -#ifndef DICE_SPARSE_MAP_SPARSE_ARRAY_HPP -#define DICE_SPARSE_MAP_SPARSE_ARRAY_HPP +#ifndef DICE_SPARSE_MAP_SPARSE_BUCKET_HPP +#define DICE_SPARSE_MAP_SPARSE_BUCKET_HPP #include "dice/sparse-map/sparse_props.hpp" @@ -33,7 +33,7 @@ namespace dice::sparse_map::detail { * the idea behinds the implementation. */ template - struct sparse_array { + struct sparse_bucket { private: using alloc_traits = std::allocator_traits; @@ -125,9 +125,9 @@ namespace dice::sparse_map::detail { } public: - constexpr sparse_array() noexcept = default; + constexpr sparse_bucket() noexcept = default; - sparse_array(size_type capacity, allocator_type &alloc) : m_capacity{capacity} { + sparse_bucket(size_type capacity, allocator_type &alloc) : m_capacity{capacity} { if (m_capacity == 0) { return; } @@ -136,9 +136,9 @@ namespace dice::sparse_map::detail { assert(m_values != nullptr);// allocate should throw if there is a failure } - sparse_array(sparse_array const &other) = delete; + sparse_bucket(sparse_bucket const &other) = delete; - sparse_array(sparse_array const &other, allocator_type &alloc) : m_values{nullptr}, + sparse_bucket(sparse_bucket const &other, allocator_type &alloc) : m_values{nullptr}, m_bitmap_vals{other.m_bitmap_vals}, m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, m_nb_elements{0}, @@ -162,18 +162,18 @@ namespace dice::sparse_map::detail { } } - constexpr sparse_array(sparse_array &&other) noexcept : m_values{std::exchange(other.m_values, nullptr)}, - m_bitmap_vals{std::exchange(other.m_bitmap_vals, 0)}, - m_bitmap_deleted_vals{std::exchange(other.m_bitmap_deleted_vals, 0)}, - m_nb_elements{std::exchange(other.m_nb_elements, 0)}, - m_capacity{std::exchange(other.m_capacity, 0)} { + constexpr sparse_bucket(sparse_bucket &&other) noexcept : m_values{std::exchange(other.m_values, nullptr)}, + m_bitmap_vals{std::exchange(other.m_bitmap_vals, 0)}, + m_bitmap_deleted_vals{std::exchange(other.m_bitmap_deleted_vals, 0)}, + m_nb_elements{std::exchange(other.m_nb_elements, 0)}, + m_capacity{std::exchange(other.m_capacity, 0)} { } - sparse_array(sparse_array &&other, [[maybe_unused]] allocator_type &alloc) noexcept requires (alloc_traits::is_always_equal::value) - : sparse_array{std::move(other)} { + sparse_bucket(sparse_bucket &&other, [[maybe_unused]] allocator_type &alloc) noexcept requires (alloc_traits::is_always_equal::value) + : sparse_bucket{std::move(other)} { } - sparse_array(sparse_array &&other, allocator_type &alloc) requires (!alloc_traits::is_always_equal::value) + sparse_bucket(sparse_bucket &&other, allocator_type &alloc) requires (!alloc_traits::is_always_equal::value) : m_bitmap_vals{other.m_bitmap_vals}, m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, m_nb_elements{0}, @@ -208,11 +208,12 @@ namespace dice::sparse_map::detail { } } - sparse_array &operator=(sparse_array const &) = delete; + sparse_bucket &operator=(sparse_bucket const &) = delete; - constexpr sparse_array &operator=(sparse_array &&other) noexcept { + constexpr sparse_bucket &operator=(sparse_bucket &&other) noexcept { assert(this != &other); + clear(); this->m_values = std::exchange(other.m_values, nullptr); this->m_bitmap_vals = std::exchange(other.m_bitmap_vals, 0); this->m_bitmap_deleted_vals = std::exchange(other.m_bitmap_deleted_vals, 0); @@ -225,7 +226,7 @@ namespace dice::sparse_map::detail { // The code that manages the sparse_array_type must have called clear before // destruction. See documentation of sparse_array_type for more details. - ~sparse_array() noexcept = default; + ~sparse_bucket() noexcept = default; /** * @safety This function is only safe to call if the underlying object is non-const @@ -249,7 +250,7 @@ namespace dice::sparse_map::detail { [[nodiscard]] constexpr size_type size() const noexcept { return m_nb_elements; } - void clear(allocator_type &alloc) noexcept { + void clear(allocator_type &alloc) noexcept(std::is_nothrow_destructible_v) { destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); m_values = nullptr; @@ -324,7 +325,7 @@ namespace dice::sparse_map::detail { return m_values + offset; } - void swap(sparse_array &other) noexcept { + void swap(sparse_bucket &other) noexcept { using std::swap; swap(m_values, other.m_values); @@ -586,4 +587,4 @@ namespace dice::sparse_map::detail { } // namespace dice::sparse_map::detail -#endif//DICE_SPARSE_MAP_SPARSE_ARRAY_HPP +#endif//DICE_SPARSE_MAP_SPARSE_BUCKET_HPP diff --git a/include/dice/sparse-map/sparse_bucket_array.hpp b/include/dice/sparse-map/sparse_bucket_array.hpp new file mode 100644 index 0000000..b223b90 --- /dev/null +++ b/include/dice/sparse-map/sparse_bucket_array.hpp @@ -0,0 +1,301 @@ +#ifndef DICE_SPARSE_MAP_SPARSE_BUCKET_ARRAY_HPP +#define DICE_SPARSE_MAP_SPARSE_BUCKET_ARRAY_HPP + +#include "dice/sparse-map/sparse_props.hpp" +#include "dice/sparse-map/sparse_bucket.hpp" + +namespace dice::sparse_map::detail { + + template + struct sparse_bucket_array { + using bucket_type = sparse_bucket; + using element_type = typename bucket_type::value_type; + using allocator_type = Allocator; + + private: + using element_alloc_traits = std::allocator_traits; + using bucket_alloc_traits = std::allocator_traits::template rebind_traits; + + using bucket_allocator_type = typename bucket_alloc_traits::allocator_type; + using element_allocator_type = typename element_alloc_traits::allocator_type; + + public: + using pointer = typename bucket_alloc_traits::pointer; + using const_pointer = typename bucket_alloc_traits::const_pointer; + + using bucket_iterator = pointer; + using bucket_const_iterator = const_pointer; + using element_iterator = typename bucket_type::iterator; + using element_const_iterator = typename bucket_type::const_iterator; + + using size_type = typename bucket_alloc_traits::size_type; + using difference_type = typename bucket_alloc_traits::difference_type; + using reference = bucket_type &; + using const_reference = bucket_type const &; + + private: + pointer data_ = nullptr; + size_type size_ = 0; + size_type cap_ = 0; + [[no_unique_address]] bucket_allocator_type bucket_alloc_; + [[no_unique_address]] element_allocator_type elem_alloc_; + + [[nodiscard]] static constexpr size_type next_cap(size_type cap) noexcept { + return static_cast(static_cast(cap) * 1.5); + } + + void move_buckets_from(sparse_bucket_array &&other) { + assert(size_ == 0); + reserve(other.size_); + + try { + for (auto &&bucket : other) { + emplace_back(std::move(bucket)); + } + } catch (...) { + clear(); + throw; + } + } + + void copy_buckets_from(sparse_bucket_array const &other) { + assert(size_ == 0); + reserve(other.size_); + + try { + for (auto const &bucket : other) { + emplace_back(bucket); + } + } catch (...) { + clear(); + throw; + } + } + + void clear_deallocate() noexcept { + clear(); + bucket_alloc_traits::deallocate(bucket_alloc_, data_, cap_); + data_ = nullptr; + cap_ = 0; + } + + public: + sparse_bucket_array(allocator_type const &alloc) : data_{nullptr}, + size_{0}, + cap_{0}, + bucket_alloc_{alloc}, + elem_alloc_{alloc} { + } + + sparse_bucket_array(size_type capacity, allocator_type const &alloc) : size_{0}, + bucket_alloc_{alloc}, + elem_alloc_{alloc} { + reserve(capacity); + fill(capacity); + } + + void reserve(size_type capacity) { + if (capacity <= cap_) { + return; + } + + if (capacity > max_size()) [[unlikely]] { + throw std::length_error{"maximum possible capacity exceeded"}; + } + + pointer new_data = bucket_alloc_traits::allocate(bucket_alloc_, capacity); + assert(new_data != nullptr); + + for (size_type ix = 0; ix < size_; ++ix) { + new (&new_data[ix]) bucket_type{std::move(data_[ix])}; + } + + static_assert(std::is_trivially_destructible_v); + bucket_alloc_traits::deallocate(bucket_alloc_, data_, cap_); + data_ = new_data; + cap_ = capacity; + } + + void fill(size_type size) { + assert(size <= cap_); + + for (size_type ix = 0; ix < size; ++ix) { + new (&data_[ix]) bucket_type{}; + } + + size_ = size; + } + + void resize(size_type size) { + reserve(size); + fill(size); + } + + sparse_bucket_array(sparse_bucket_array const &other) : bucket_alloc_{bucket_alloc_traits::select_on_container_copy_construction(other.bucket_alloc_)}, + elem_alloc_{element_alloc_traits::select_on_container_copy_construction(other.elem_alloc_)} { + reserve(other.size_); + copy_buckets_from(other); + } + + sparse_bucket_array(sparse_bucket_array &&other) noexcept : data_{std::exchange(other.data_, nullptr)}, + size_{std::exchange(other.size_, 0)}, + cap_{other.cap_}, + bucket_alloc_{std::move(other.bucket_alloc_)}, + elem_alloc_{std::move(other.elem_alloc_)} { + } + + sparse_bucket_array &operator=(sparse_bucket_array const &other) { + if (this == &other) { + return *this; + } + + // no need to fully realloc, either: + // 1. alloc is not propagated + // 2. alloc is propagated but is same as this + // => need to fully realloc if propagate and not same as this + + if constexpr (bucket_alloc_traits::propagate_on_container_copy_assignment::value) { + if (bucket_alloc_ != other.bucket_alloc_) { + clear_deallocate(); + bucket_alloc_ = other.bucket_alloc_; + elem_alloc_ = other.elem_alloc_; + + copy_buckets_from(other); + return *this; + } + } + + clear(); + copy_buckets_from(other); + return *this; + } + + sparse_bucket_array &operator=(sparse_bucket_array &&other) noexcept { + assert(this != &other); + + clear_deallocate(); + + if constexpr (!bucket_alloc_traits::propagate_on_container_move_assignment::value) { + if (bucket_alloc_ != other.bucket_alloc_) { + bucket_alloc_ = std::move(other.bucket_alloc_); + elem_alloc_ = std::move(other.elem_alloc_); + + move_buckets_from(std::move(other)); + return *this; + } + } + + data_ = std::exchange(other.data_, nullptr); + size_ = std::exchange(other.size_, 0); + cap_ = std::exchange(other.cap_, 0); + + return *this; + } + + ~sparse_bucket_array() noexcept { + clear_deallocate(); + } + + void swap(sparse_bucket_array &other) noexcept { + using std::swap; + + swap(data_, other.data_); + swap(size_, other.size_); + swap(cap_, other.cap_); + + if constexpr (bucket_alloc_traits::propagate_on_container_swap::value) { + swap(bucket_alloc_, other.bucket_alloc_); + swap(elem_alloc_, other.elem_alloc_); + } + } + + void clear_buckets() noexcept { + for (size_type ix = 0; ix < size_; ++ix) { + data_[ix].clear(elem_alloc_); + } + } + + void clear() noexcept { + clear_buckets(); + size_ = 0; + } + + [[nodiscard]] constexpr bucket_iterator begin() noexcept { return data_; } + [[nodiscard]] constexpr bucket_iterator end() noexcept { return data_ + size_; } + [[nodiscard]] constexpr bucket_const_iterator begin() const noexcept { return data_; } + [[nodiscard]] constexpr bucket_const_iterator end() const noexcept { return data_ + size_; } + [[nodiscard]] constexpr bucket_const_iterator cbegin() const noexcept { return data_; } + [[nodiscard]] constexpr bucket_const_iterator cend() const noexcept { return data_ + size_; } + + [[nodiscard]] constexpr bool empty() const noexcept { return size_ == 0; } + [[nodiscard]] constexpr size_type size() const noexcept { return size_; } + [[nodiscard]] constexpr size_type max_size() const noexcept { return bucket_alloc_traits::max_size(bucket_alloc_); }; + + reference operator[](size_type const ix) noexcept { + assert(ix < size_); + return data_[ix]; + } + + const_reference operator[](size_type const ix) const noexcept { + assert(ix < size_); + return data_[ix]; + } + + template + bucket_iterator emplace_back(Args &&...args) { + if (size_ < cap_) { + new (std::to_address(data_ + size_)) bucket_type{std::forward(args)..., elem_alloc_}; + return data_ + size_++; + } + + auto const new_cap = next_cap(cap_); + pointer new_data = bucket_alloc_traits::allocate(bucket_alloc_, new_cap); + + try { + new (&new_data[size_]) bucket_type{std::forward(args)..., elem_alloc_}; + } catch (...) { + bucket_alloc_traits::deallocate(bucket_alloc_, new_data, new_cap); + throw; + } + + for (size_type ix = 0; ix < size_; ++ix) { + new (&new_data[ix]) bucket_type{std::move(data_[ix])}; + } + + static_assert(std::is_trivially_destructible_v); + bucket_alloc_traits::deallocate(bucket_alloc_, data_, cap_); + + data_ = new_data; + cap_ = new_cap; + return data_ + size_++; + } + + template + element_iterator set_element(size_type bucket_ix, typename bucket_type::size_type element_ix, Args &&...args) { + return data_[bucket_ix].set(elem_alloc_, element_ix, std::forward(args)...); + } + + element_iterator erase_element(bucket_iterator bucket, element_iterator elem) { + return (*bucket).erase(elem_alloc_, elem); + } + + element_iterator erase_element(bucket_iterator bucket, element_iterator elem, typename bucket_type::size_type elem_ix) { + return (*bucket).erase(elem_alloc_, elem, elem_ix); + } + + element_allocator_type &element_allocator() noexcept { + return elem_alloc_; + } + + bool has_value(size_type bucket_ix, typename bucket_type::size_type element_ix) const noexcept { + return data_[bucket_ix].has_value(element_ix); + } + + bool has_deleted_value(size_type bucket_ix, typename bucket_type::size_type element_ix) const noexcept { + return data_[bucket_ix].has_deleted_value(element_ix); + } + }; + +} // namespace dice::sparse_map::detail + +#endif//DICE_SPARSE_MAP_SPARSE_BUCKET_ARRAY_HPP diff --git a/include/dice/sparse-map/sparse_growth_policy.hpp b/include/dice/sparse-map/sparse_growth_policy.hpp index 74dcb78..42f2b6a 100644 --- a/include/dice/sparse-map/sparse_growth_policy.hpp +++ b/include/dice/sparse-map/sparse_growth_policy.hpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include diff --git a/include/dice/sparse-map/sparse_hash.hpp b/include/dice/sparse-map/sparse_hash.hpp index 129f376..a0b9147 100644 --- a/include/dice/sparse-map/sparse_hash.hpp +++ b/include/dice/sparse-map/sparse_hash.hpp @@ -42,8 +42,9 @@ #include +#include "dice/sparse-map/sparse_bucket.hpp" #include "dice/sparse-map/sparse_growth_policy.hpp" -#include "dice/sparse-map/sparse_array.hpp" +#include "dice/sparse-map/sparse_bucket_array.hpp" namespace dice::sparse_map::detail { template @@ -137,13 +138,11 @@ namespace dice::sparse_map::detail { private: static constexpr bool has_mapped_type = !std::is_same_v; - using sparse_array_type = sparse_array; - - using sparse_buckets_allocator = typename std::allocator_traits::template rebind_alloc; - using sparse_buckets_container = boost::interprocess::vector; + using sparse_bucket_type = sparse_bucket; + using sparse_bucket_array_type = sparse_bucket_array; private: - sparse_buckets_container m_sparse_buckets_data; + sparse_bucket_array_type m_sparse_buckets_data; size_type m_bucket_count; size_type m_nb_elements; @@ -162,7 +161,6 @@ namespace dice::sparse_map::detail { size_type m_load_threshold_clear_deleted; float m_max_load_factor; - [[no_unique_address]] allocator_type m_alloc; [[no_unique_address]] hasher m_h; [[no_unique_address]] key_equal m_keq; [[no_unique_address]] growth_policy m_gpol; @@ -173,32 +171,32 @@ namespace dice::sparse_map::detail { private: friend class sparse_hash; - using sparse_bucket_iterator = std::conditional_t; + using sparse_bucket_array_iterator = std::conditional_t; - using sparse_array_iterator = std::conditional_t; + using sparse_bucket_iterator = std::conditional_t; private: - sparse_bucket_iterator m_sparse_buckets_it; - sparse_bucket_iterator m_sparse_buckets_end; - sparse_array_iterator m_sparse_array_it; + sparse_bucket_array_iterator cur_bucket_; + sparse_bucket_array_iterator end_bucket_; + sparse_bucket_iterator bucket_it_; private: /** * sparse_array_it should be nullptr if sparse_bucket_it == * m_sparse_buckets_data.end(). (TODO better way?) */ - sparse_iterator(sparse_bucket_iterator sparse_bucket_it, - sparse_bucket_iterator sparse_bucket_end, - sparse_array_iterator sparse_array_it) noexcept : m_sparse_buckets_it{sparse_bucket_it}, - m_sparse_buckets_end{sparse_bucket_end}, - m_sparse_array_it{sparse_array_it} { - - assert((m_sparse_buckets_it == m_sparse_buckets_end && m_sparse_array_it == nullptr) - || (m_sparse_buckets_it != m_sparse_buckets_end && m_sparse_array_it != nullptr)); + sparse_iterator(sparse_bucket_array_iterator bucket, + sparse_bucket_array_iterator end_bucket, + sparse_bucket_iterator bucket_it) noexcept : cur_bucket_{bucket}, + end_bucket_{end_bucket}, + bucket_it_{bucket_it} { + + assert((cur_bucket_ == end_bucket_ && bucket_it_ == nullptr) + || (cur_bucket_ != end_bucket_ && bucket_it_ != nullptr)); } public: @@ -213,9 +211,9 @@ namespace dice::sparse_map::detail { typename std::allocator_traits::template rebind_traits::pointer>; // Copy constructor from iterator to const_iterator. - sparse_iterator(sparse_iterator const &other) noexcept requires (IsConst) : m_sparse_buckets_it{other.m_sparse_buckets_it}, - m_sparse_buckets_end{other.m_sparse_buckets_end}, - m_sparse_array_it{other.m_sparse_array_it} { + sparse_iterator(sparse_iterator const &other) noexcept requires (IsConst) : cur_bucket_{other.cur_bucket_}, + end_bucket_{other.end_bucket_}, + bucket_it_{other.bucket_it_} { } sparse_iterator(sparse_iterator const &other) noexcept = default; @@ -223,25 +221,25 @@ namespace dice::sparse_map::detail { sparse_iterator &operator=(sparse_iterator const &other) noexcept = default; sparse_iterator &operator=(sparse_iterator &&other) noexcept = default; - reference operator*() const noexcept { return KeyValueSelect::both(*m_sparse_array_it); } - pointer operator->() const noexcept { return &KeyValueSelect::both(*m_sparse_array_it); } + reference operator*() const noexcept { return KeyValueSelect::both(*bucket_it_); } + pointer operator->() const noexcept { return &KeyValueSelect::both(*bucket_it_); } sparse_iterator &operator++() noexcept { - assert(m_sparse_array_it != nullptr); - ++m_sparse_array_it; + assert(bucket_it_ != nullptr); + ++bucket_it_; - if (m_sparse_array_it != (*m_sparse_buckets_it).end()) { + if (bucket_it_ != (*cur_bucket_).end()) { return *this; } do { - if (++m_sparse_buckets_it == m_sparse_buckets_end) { - m_sparse_array_it = nullptr; + if (++cur_bucket_ == end_bucket_) { + bucket_it_ = nullptr; return *this; } - } while ((*m_sparse_buckets_it).empty()); + } while ((*cur_bucket_).empty()); - m_sparse_array_it = (*m_sparse_buckets_it).begin(); + bucket_it_ = (*cur_bucket_).begin(); return *this; } @@ -253,22 +251,22 @@ namespace dice::sparse_map::detail { template bool operator==(sparse_iterator const &other) const noexcept { - return m_sparse_buckets_it == other.m_sparse_buckets_it && m_sparse_array_it == other.m_sparse_array_it; + return cur_bucket_ == other.cur_bucket_ && bucket_it_ == other.bucket_it_; } template bool operator!=(sparse_iterator const &other) const noexcept { - return m_sparse_buckets_it != other.m_sparse_buckets_it || m_sparse_array_it != other.m_sparse_array_it; + return cur_bucket_ != other.cur_bucket_ || bucket_it_ != other.bucket_it_; } }; iterator mutable_iterator(const_iterator pos) noexcept { // SAFETY: this is non-const therefore the underlying buckets are also non-const // as evidenced by the fact that we can call begin on them - auto it_sparse_buckets = m_sparse_buckets_data.begin() + std::distance(m_sparse_buckets_data.cbegin(), pos.m_sparse_buckets_it); + auto it_sparse_buckets = m_sparse_buckets_data.begin() + std::distance(m_sparse_buckets_data.cbegin(), pos.cur_bucket_); // SAFETY: this is non-const therefore the underlying sparse array is also non-const - auto it_array = sparse_array_type::unsafe_mutable_iterator(pos.m_sparse_array_it); + auto it_array = sparse_bucket_type::unsafe_mutable_iterator(pos.bucket_it_); return iterator{it_sparse_buckets, m_sparse_buckets_data.end(), it_array}; } @@ -279,7 +277,6 @@ namespace dice::sparse_map::detail { m_bucket_count{bucket_count}, m_nb_elements{0}, m_nb_deleted_buckets{0}, - m_alloc{alloc}, m_h{hash}, m_keq{equal}, m_gpol{bucket_count} { @@ -288,7 +285,7 @@ namespace dice::sparse_map::detail { } if (m_bucket_count > 0) { - m_sparse_buckets_data.resize(sparse_array_type::nb_sparse_buckets(bucket_count)); + m_sparse_buckets_data.resize(bucket_count); assert(!m_sparse_buckets_data.empty()); } @@ -304,26 +301,13 @@ namespace dice::sparse_map::detail { ~sparse_hash() { clear(); } - sparse_hash(const sparse_hash &other) - : m_sparse_buckets_data(std::allocator_traits::select_on_container_copy_construction(other.m_alloc)), - m_bucket_count(other.m_bucket_count), - m_nb_elements(other.m_nb_elements), - m_nb_deleted_buckets(other.m_nb_deleted_buckets), - m_load_threshold_rehash(other.m_load_threshold_rehash), - m_load_threshold_clear_deleted(other.m_load_threshold_clear_deleted), - m_max_load_factor(other.m_max_load_factor), - m_alloc{std::allocator_traits::select_on_container_copy_construction(other.m_alloc)}, - m_h{other.m_h}, - m_keq{other.m_keq}, - m_gpol{other.m_gpol} { - copy_buckets_from(other); - } + sparse_hash(const sparse_hash &other) = default; sparse_hash(sparse_hash &&other) noexcept(std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value - && std::is_nothrow_move_constructible::value) + && std::is_nothrow_move_constructible::value) : m_sparse_buckets_data(std::move(other.m_sparse_buckets_data)), m_bucket_count(other.m_bucket_count), m_nb_elements(other.m_nb_elements), @@ -331,12 +315,10 @@ namespace dice::sparse_map::detail { m_load_threshold_rehash(other.m_load_threshold_rehash), m_load_threshold_clear_deleted(other.m_load_threshold_clear_deleted), m_max_load_factor(other.m_max_load_factor), - m_alloc{std::move(other.m_alloc)}, m_h{std::move(other.m_h)}, m_keq{std::move(other.m_keq)}, m_gpol{std::move(other.m_gpol)} { other.m_gpol.clear(); - other.m_sparse_buckets_data.clear(); other.m_bucket_count = 0; other.m_nb_elements = 0; other.m_nb_deleted_buckets = 0; @@ -345,68 +327,41 @@ namespace dice::sparse_map::detail { } sparse_hash &operator=(const sparse_hash &other) { - if (this != &other) { - clear(); - - if (std::allocator_traits::propagate_on_container_copy_assignment::value) { - m_alloc = other.m_alloc; - } - - m_h = other.m_h; - m_keq = other.m_keq; - m_gpol = other.m_gpol; - - if (std::allocator_traits::propagate_on_container_copy_assignment::value) { - m_sparse_buckets_data = sparse_buckets_container(other.m_alloc); - } else { - if (m_sparse_buckets_data.size() != - other.m_sparse_buckets_data.size()) { - m_sparse_buckets_data = sparse_buckets_container(m_alloc); - } else { - m_sparse_buckets_data.clear(); - } - } - - copy_buckets_from(other); - - m_bucket_count = other.m_bucket_count; - m_nb_elements = other.m_nb_elements; - m_nb_deleted_buckets = other.m_nb_deleted_buckets; - m_load_threshold_rehash = other.m_load_threshold_rehash; - m_load_threshold_clear_deleted = other.m_load_threshold_clear_deleted; - m_max_load_factor = other.m_max_load_factor; + if (this == &other) { + return *this; } - return *this; - } - - sparse_hash &operator=(sparse_hash &&other) noexcept { clear(); - if (!std::allocator_traits::propagate_on_container_move_assignment::value && m_alloc != other.m_alloc) { - move_buckets_from(std::move(other)); - } else { - m_alloc = std::move(other.m_alloc); - m_sparse_buckets_data = std::move(other.m_sparse_buckets_data); - } - - m_h = std::move(other.m_h); - m_keq = std::move(other.m_keq); - m_gpol = std::move(other.m_gpol); + m_sparse_buckets_data = other.m_sparse_buckets_data; m_bucket_count = other.m_bucket_count; m_nb_elements = other.m_nb_elements; m_nb_deleted_buckets = other.m_nb_deleted_buckets; m_load_threshold_rehash = other.m_load_threshold_rehash; m_load_threshold_clear_deleted = other.m_load_threshold_clear_deleted; m_max_load_factor = other.m_max_load_factor; + m_h = other.m_h; + m_keq = other.m_keq; + m_gpol = other.m_gpol; + + return *this; + } + + sparse_hash &operator=(sparse_hash &&other) noexcept { + assert(this != &other); + + m_sparse_buckets_data = std::move(other.m_sparse_buckets_data); + m_bucket_count = std::exchange(other.m_bucket_count, 0); + m_nb_elements = std::exchange(other.m_nb_elements, 0); + m_nb_deleted_buckets = std::exchange(other.m_nb_deleted_buckets, 0); + m_load_threshold_rehash = std::exchange(other.m_load_threshold_rehash, 0); + m_load_threshold_clear_deleted = std::exchange(other.m_load_threshold_clear_deleted, 0); + m_max_load_factor = other.m_max_load_factor; + m_h = std::move(other.m_h); + m_keq = std::move(other.m_keq); + m_gpol = std::move(other.m_gpol); other.m_gpol.clear(); - other.m_sparse_buckets_data.clear(); - other.m_bucket_count = 0; - other.m_nb_elements = 0; - other.m_nb_deleted_buckets = 0; - other.m_load_threshold_rehash = 0; - other.m_load_threshold_clear_deleted = 0; return *this; } @@ -433,15 +388,15 @@ namespace dice::sparse_map::detail { } const_iterator cbegin() const noexcept { - auto begin = m_sparse_buckets_data.cbegin(); + auto begin = m_sparse_buckets_data.begin(); //vector iterator with fancy pointers have a problem with -> - while (begin != m_sparse_buckets_data.cend() && (*begin).empty()) { + while (begin != m_sparse_buckets_data.end() && (*begin).empty()) { ++begin; } return const_iterator{begin, - m_sparse_buckets_data.cend(), - begin != m_sparse_buckets_data.cend() ? (*begin).cbegin() : nullptr}; + m_sparse_buckets_data.end(), + begin != m_sparse_buckets_data.end() ? (*begin).begin() : nullptr}; } iterator end() noexcept { @@ -455,8 +410,8 @@ namespace dice::sparse_map::detail { } const_iterator cend() const noexcept { - return const_iterator{m_sparse_buckets_data.cend(), - m_sparse_buckets_data.cend(), + return const_iterator{m_sparse_buckets_data.end(), + m_sparse_buckets_data.end(), nullptr}; } @@ -470,10 +425,7 @@ namespace dice::sparse_map::detail { } void clear() noexcept { - for (auto &bucket : m_sparse_buckets_data) { - bucket.clear(m_alloc); - } - + m_sparse_buckets_data.clear_buckets(); m_nb_elements = 0; m_nb_deleted_buckets = 0; } @@ -568,28 +520,28 @@ namespace dice::sparse_map::detail { iterator erase(iterator pos) { assert(pos != end() && m_nb_elements > 0); //vector iterator with fancy pointers have a problem with -> - auto it_sparse_array_next = (*pos.m_sparse_buckets_it).erase(m_alloc, pos.m_sparse_array_it); + auto next_bucket_it = m_sparse_buckets_data.erase_element(pos.cur_bucket_, pos.bucket_it_); m_nb_elements--; m_nb_deleted_buckets++; - if (it_sparse_array_next == (*pos.m_sparse_buckets_it).end()) { - auto it_sparse_buckets_next = pos.m_sparse_buckets_it; - do { - ++it_sparse_buckets_next; - } while (it_sparse_buckets_next != m_sparse_buckets_data.end() - && (*it_sparse_buckets_next).empty()); + if (next_bucket_it != (*pos.cur_bucket_).end()) { + return iterator{pos.cur_bucket_, + m_sparse_buckets_data.end(), + next_bucket_it}; + } - if (it_sparse_buckets_next == m_sparse_buckets_data.end()) { - return end(); - } else { - return iterator{it_sparse_buckets_next, - m_sparse_buckets_data.end(), - (*it_sparse_buckets_next).begin()}; - } + auto it_sparse_buckets_next = pos.cur_bucket_; + do { + ++it_sparse_buckets_next; + } while (it_sparse_buckets_next != m_sparse_buckets_data.end() + && (*it_sparse_buckets_next).empty()); + + if (it_sparse_buckets_next == m_sparse_buckets_data.end()) { + return end(); } else { - return iterator{pos.m_sparse_buckets_it, + return iterator{it_sparse_buckets_next, m_sparse_buckets_data.end(), - it_sparse_array_next}; + (*it_sparse_buckets_next).begin()}; } } @@ -629,15 +581,6 @@ namespace dice::sparse_map::detail { void swap(sparse_hash &other) { using std::swap; - if (std::allocator_traits::propagate_on_container_swap::value) { - swap(m_alloc, other.m_alloc); - } else { - assert(m_alloc == other.m_alloc); - } - - swap(m_h, other.m_h); - swap(m_keq, other.m_keq); - swap(m_gpol, other.m_gpol); swap(m_sparse_buckets_data, other.m_sparse_buckets_data); swap(m_bucket_count, other.m_bucket_count); swap(m_nb_elements, other.m_nb_elements); @@ -645,6 +588,9 @@ namespace dice::sparse_map::detail { swap(m_load_threshold_rehash, other.m_load_threshold_rehash); swap(m_load_threshold_clear_deleted, other.m_load_threshold_clear_deleted); swap(m_max_load_factor, other.m_max_load_factor); + swap(m_h, other.m_h); + swap(m_keq, other.m_keq); + swap(m_gpol, other.m_gpol); } template requires (has_mapped_type) @@ -783,8 +729,8 @@ namespace dice::sparse_map::detail { private: size_type bucket_for_hash(std::size_t hash) const { auto const bucket = m_gpol.bucket_for_hash(hash); - assert(sparse_array_type::sparse_ibucket(bucket) < m_sparse_buckets_data.size() - || (bucket == 0 && m_sparse_buckets_data.empty())); + assert(sparse_bucket_type::sparse_ibucket(bucket) < m_sparse_buckets_data.size() + || (bucket == 0 && m_sparse_buckets_data.empty())); return bucket; } @@ -809,33 +755,6 @@ namespace dice::sparse_map::detail { } } - // TODO encapsulate m_sparse_buckets_data to avoid the managing the allocator - void copy_buckets_from(const sparse_hash &other) { - m_sparse_buckets_data.reserve(other.m_sparse_buckets_data.size()); - - try { - for (const auto &bucket : other.m_sparse_buckets_data) { - m_sparse_buckets_data.emplace_back(bucket, m_alloc); - } - } catch (...) { - clear(); - throw; - } - } - - void move_buckets_from(sparse_hash &&other) { - m_sparse_buckets_data.reserve(other.m_sparse_buckets_data.size()); - - try { - for (auto &&bucket : other.m_sparse_buckets_data) { - m_sparse_buckets_data.emplace_back(std::move(bucket), m_alloc); - } - } catch (...) { - clear(); - throw; - } - } - template std::pair insert_impl(const K &key, Args &&...value_type_args) { @@ -845,7 +764,7 @@ namespace dice::sparse_map::detail { m_load_threshold_clear_deleted) { clear_deleted_buckets(); } - assert(!m_sparse_buckets_data.empty()); + //assert(!m_sparse_buckets_data.empty()); /** * We must insert the value in the first empty or deleted bucket we find. If @@ -857,16 +776,15 @@ namespace dice::sparse_map::detail { */ bool found_first_deleted_bucket = false; std::size_t sparse_ibucket_first_deleted = 0; - typename sparse_array_type::size_type index_in_sparse_bucket_first_deleted = 0; + typename sparse_bucket_type::size_type index_in_sparse_bucket_first_deleted = 0; const std::size_t hash = m_h(key); std::size_t ibucket = bucket_for_hash(hash); std::size_t probe = 0; while (true) { - std::size_t sparse_ibucket = sparse_array_type::sparse_ibucket(ibucket); - auto index_in_sparse_bucket = - sparse_array_type::index_in_sparse_bucket(ibucket); + std::size_t sparse_ibucket = sparse_bucket_type::sparse_ibucket(ibucket); + auto index_in_sparse_bucket = sparse_bucket_type::index_in_sparse_bucket(ibucket); if (!m_sparse_buckets_data.empty()) { if (m_sparse_buckets_data[sparse_ibucket].has_value(index_in_sparse_bucket)) { @@ -906,10 +824,10 @@ namespace dice::sparse_map::detail { template std::pair insert_in_bucket(std::size_t sparse_ibucket, - typename sparse_array_type::size_type index_in_sparse_bucket, + typename sparse_bucket_type::size_type index_in_sparse_bucket, Args &&...value_type_args) { // is not called when empty - auto value_it = m_sparse_buckets_data[sparse_ibucket].set(m_alloc, index_in_sparse_bucket, std::forward(value_type_args)...); + auto value_it = m_sparse_buckets_data.set_element(sparse_ibucket, index_in_sparse_bucket, std::forward(value_type_args)...); m_nb_elements++; return std::make_pair(iterator{std::next(m_sparse_buckets_data.begin(), sparse_ibucket), @@ -928,8 +846,8 @@ namespace dice::sparse_map::detail { std::size_t probe = 0; while (true) { - auto const sparse_ibucket = sparse_array_type::sparse_ibucket(ibucket); - auto const index_in_sparse_bucket = sparse_array_type::index_in_sparse_bucket(ibucket); + auto const sparse_ibucket = sparse_bucket_type::sparse_ibucket(ibucket); + auto const index_in_sparse_bucket = sparse_bucket_type::index_in_sparse_bucket(ibucket); auto &bucket = m_sparse_buckets_data[sparse_ibucket]; @@ -937,7 +855,7 @@ namespace dice::sparse_map::detail { auto value_it = bucket.value(index_in_sparse_bucket); if (m_keq(key, KeyValueSelect::key(*value_it))) { - bucket.erase(m_alloc, value_it, index_in_sparse_bucket); + bucket.erase(m_sparse_buckets_data.element_allocator(), value_it, index_in_sparse_bucket); m_nb_elements--; m_nb_deleted_buckets++; @@ -962,8 +880,8 @@ namespace dice::sparse_map::detail { std::size_t probe = 0; while (true) { - auto const sparse_ibucket = sparse_array_type::sparse_ibucket(ibucket); - auto const index_in_sparse_bucket = sparse_array_type::index_in_sparse_bucket(ibucket); + auto const sparse_ibucket = sparse_bucket_type::sparse_ibucket(ibucket); + auto const index_in_sparse_bucket = sparse_bucket_type::index_in_sparse_bucket(ibucket); auto &bucket = self.m_sparse_buckets_data[sparse_ibucket]; @@ -1002,7 +920,7 @@ namespace dice::sparse_map::detail { } void rehash_impl(size_type count) requires (ExceptionSafety == exception_safety::basic) { - sparse_hash new_table(count, m_h, m_keq, m_alloc, m_max_load_factor); + sparse_hash new_table(count, m_h, m_keq, m_sparse_buckets_data.element_allocator(), m_max_load_factor); for (auto &bucket : m_sparse_buckets_data) { for (auto &val : bucket) { @@ -1010,7 +928,7 @@ namespace dice::sparse_map::detail { } // TODO try to reuse some of the memory - bucket.clear(m_alloc); + bucket.clear(m_sparse_buckets_data.element_allocator()); } new_table.swap(*this); @@ -1022,7 +940,7 @@ namespace dice::sparse_map::detail { * any exception if we reserve enough space in the sparse arrays beforehand. */ void rehash_impl(size_type count) requires (ExceptionSafety == exception_safety::strong) { - sparse_hash new_table(count, m_h, m_keq, m_alloc, m_max_load_factor); + sparse_hash new_table(count, m_h, m_keq, m_sparse_buckets_data.element_allocator(), m_max_load_factor); for (const auto &bucket : m_sparse_buckets_data) { for (const auto &val : bucket) { @@ -1042,13 +960,13 @@ namespace dice::sparse_map::detail { std::size_t probe = 0; while (true) { - auto const sparse_ibucket = sparse_array_type::sparse_ibucket(ibucket); - auto const index_in_sparse_bucket = sparse_array_type::index_in_sparse_bucket(ibucket); + auto const sparse_ibucket = sparse_bucket_type::sparse_ibucket(ibucket); + auto const index_in_sparse_bucket = sparse_bucket_type::index_in_sparse_bucket(ibucket); auto &bucket = m_sparse_buckets_data[sparse_ibucket]; if (!bucket.has_value(index_in_sparse_bucket)) { - bucket.set(m_alloc, index_in_sparse_bucket, std::forward(key_value)); + bucket.set(m_sparse_buckets_data.element_allocator(), index_in_sparse_bucket, std::forward(key_value)); m_nb_elements++; return; diff --git a/tests/fancy_pointer/sparse_array_tests.cpp b/tests/fancy_pointer/sparse_array_tests.cpp index 4697d55..6d99fde 100644 --- a/tests/fancy_pointer/sparse_array_tests.cpp +++ b/tests/fancy_pointer/sparse_array_tests.cpp @@ -34,7 +34,7 @@ namespace details { template struct STD { using Allocator = std::allocator; - using Array = dice::sparse_map::detail::sparse_array, Sparsity>; + using Array = dice::sparse_map::detail::sparse_bucket, Sparsity>; using Const_Iterator = T const*; using Value_Type = T; }; @@ -42,7 +42,7 @@ struct STD { template struct CUSTOM { using Allocator = OffsetAllocator; - using Array = dice::sparse_map::detail::sparse_array, Sparsity>; + using Array = dice::sparse_map::detail::sparse_bucket, Sparsity>; using Const_Iterator = boost::interprocess::offset_ptr; using Value_Type = T; }; From 16273b8508449b3294e9476c56394b7d7a0f5e36 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Fri, 4 Aug 2023 07:31:10 +0200 Subject: [PATCH 15/41] sparse buckets array cleanup --- .../dice/sparse-map/sparse_bucket_array.hpp | 182 +++++++----------- include/dice/sparse-map/sparse_hash.hpp | 29 +-- 2 files changed, 89 insertions(+), 122 deletions(-) diff --git a/include/dice/sparse-map/sparse_bucket_array.hpp b/include/dice/sparse-map/sparse_bucket_array.hpp index b223b90..ed2844d 100644 --- a/include/dice/sparse-map/sparse_bucket_array.hpp +++ b/include/dice/sparse-map/sparse_bucket_array.hpp @@ -8,37 +8,31 @@ namespace dice::sparse_map::detail { template struct sparse_bucket_array { - using bucket_type = sparse_bucket; - using element_type = typename bucket_type::value_type; - using allocator_type = Allocator; - private: using element_alloc_traits = std::allocator_traits; - using bucket_alloc_traits = std::allocator_traits::template rebind_traits; + using bucket_alloc_traits = std::allocator_traits::template rebind_traits>; using bucket_allocator_type = typename bucket_alloc_traits::allocator_type; using element_allocator_type = typename element_alloc_traits::allocator_type; public: + using bucket_type = typename bucket_alloc_traits::value_type; + using value_type = bucket_type; using pointer = typename bucket_alloc_traits::pointer; using const_pointer = typename bucket_alloc_traits::const_pointer; - - using bucket_iterator = pointer; - using bucket_const_iterator = const_pointer; - using element_iterator = typename bucket_type::iterator; - using element_const_iterator = typename bucket_type::const_iterator; - + using iterator = pointer; + using const_iterator = const_pointer; using size_type = typename bucket_alloc_traits::size_type; using difference_type = typename bucket_alloc_traits::difference_type; using reference = bucket_type &; using const_reference = bucket_type const &; private: - pointer data_ = nullptr; + pointer buckets_ = nullptr; size_type size_ = 0; size_type cap_ = 0; [[no_unique_address]] bucket_allocator_type bucket_alloc_; - [[no_unique_address]] element_allocator_type elem_alloc_; + [[no_unique_address]] element_allocator_type elem_alloc_; // this allocator lives here so that the allocator management code doesn't need to be written twice [[nodiscard]] static constexpr size_type next_cap(size_type cap) noexcept { return static_cast(static_cast(cap) * 1.5); @@ -74,61 +68,17 @@ namespace dice::sparse_map::detail { void clear_deallocate() noexcept { clear(); - bucket_alloc_traits::deallocate(bucket_alloc_, data_, cap_); - data_ = nullptr; + bucket_alloc_traits::deallocate(bucket_alloc_, buckets_, cap_); + buckets_ = nullptr; cap_ = 0; } public: - sparse_bucket_array(allocator_type const &alloc) : data_{nullptr}, - size_{0}, - cap_{0}, - bucket_alloc_{alloc}, - elem_alloc_{alloc} { - } - - sparse_bucket_array(size_type capacity, allocator_type const &alloc) : size_{0}, - bucket_alloc_{alloc}, - elem_alloc_{alloc} { - reserve(capacity); - fill(capacity); - } - - void reserve(size_type capacity) { - if (capacity <= cap_) { - return; - } - - if (capacity > max_size()) [[unlikely]] { - throw std::length_error{"maximum possible capacity exceeded"}; - } - - pointer new_data = bucket_alloc_traits::allocate(bucket_alloc_, capacity); - assert(new_data != nullptr); - - for (size_type ix = 0; ix < size_; ++ix) { - new (&new_data[ix]) bucket_type{std::move(data_[ix])}; - } - - static_assert(std::is_trivially_destructible_v); - bucket_alloc_traits::deallocate(bucket_alloc_, data_, cap_); - data_ = new_data; - cap_ = capacity; - } - - void fill(size_type size) { - assert(size <= cap_); - - for (size_type ix = 0; ix < size; ++ix) { - new (&data_[ix]) bucket_type{}; - } - - size_ = size; - } - - void resize(size_type size) { - reserve(size); - fill(size); + explicit constexpr sparse_bucket_array(element_allocator_type const &alloc) : buckets_{nullptr}, + size_{0}, + cap_{0}, + bucket_alloc_{alloc}, + elem_alloc_{alloc} { } sparse_bucket_array(sparse_bucket_array const &other) : bucket_alloc_{bucket_alloc_traits::select_on_container_copy_construction(other.bucket_alloc_)}, @@ -137,11 +87,11 @@ namespace dice::sparse_map::detail { copy_buckets_from(other); } - sparse_bucket_array(sparse_bucket_array &&other) noexcept : data_{std::exchange(other.data_, nullptr)}, - size_{std::exchange(other.size_, 0)}, - cap_{other.cap_}, - bucket_alloc_{std::move(other.bucket_alloc_)}, - elem_alloc_{std::move(other.elem_alloc_)} { + constexpr sparse_bucket_array(sparse_bucket_array &&other) noexcept : buckets_{std::exchange(other.buckets_, nullptr)}, + size_{std::exchange(other.size_, 0)}, + cap_{other.cap_}, + bucket_alloc_{std::move(other.bucket_alloc_)}, + elem_alloc_{std::move(other.elem_alloc_)} { } sparse_bucket_array &operator=(sparse_bucket_array const &other) { @@ -185,7 +135,7 @@ namespace dice::sparse_map::detail { } } - data_ = std::exchange(other.data_, nullptr); + buckets_ = std::exchange(other.buckets_, nullptr); size_ = std::exchange(other.size_, 0); cap_ = std::exchange(other.cap_, 0); @@ -196,10 +146,47 @@ namespace dice::sparse_map::detail { clear_deallocate(); } + void reserve(size_type capacity) { + if (capacity <= cap_) { + return; + } + + if (capacity > max_size()) [[unlikely]] { + throw std::length_error{"maximum possible capacity exceeded"}; + } + + pointer new_data = bucket_alloc_traits::allocate(bucket_alloc_, capacity); + assert(new_data != nullptr); + + for (size_type ix = 0; ix < size_; ++ix) { + new (&new_data[ix]) bucket_type{std::move(buckets_[ix])}; + } + + static_assert(std::is_trivially_destructible_v); + bucket_alloc_traits::deallocate(bucket_alloc_, buckets_, cap_); + buckets_ = new_data; + cap_ = capacity; + } + + void fill(size_type size) { + assert(size <= cap_); + + for (size_type ix = 0; ix < size; ++ix) { + new (&buckets_[ix]) bucket_type{}; + } + + size_ = size; + } + + void resize(size_type size) { + reserve(size); + fill(size); + } + void swap(sparse_bucket_array &other) noexcept { using std::swap; - swap(data_, other.data_); + swap(buckets_, other.buckets_); swap(size_, other.size_); swap(cap_, other.cap_); @@ -211,7 +198,7 @@ namespace dice::sparse_map::detail { void clear_buckets() noexcept { for (size_type ix = 0; ix < size_; ++ix) { - data_[ix].clear(elem_alloc_); + buckets_[ix].clear(elem_alloc_); } } @@ -220,12 +207,12 @@ namespace dice::sparse_map::detail { size_ = 0; } - [[nodiscard]] constexpr bucket_iterator begin() noexcept { return data_; } - [[nodiscard]] constexpr bucket_iterator end() noexcept { return data_ + size_; } - [[nodiscard]] constexpr bucket_const_iterator begin() const noexcept { return data_; } - [[nodiscard]] constexpr bucket_const_iterator end() const noexcept { return data_ + size_; } - [[nodiscard]] constexpr bucket_const_iterator cbegin() const noexcept { return data_; } - [[nodiscard]] constexpr bucket_const_iterator cend() const noexcept { return data_ + size_; } + [[nodiscard]] constexpr iterator begin() noexcept { return buckets_; } + [[nodiscard]] constexpr iterator end() noexcept { return buckets_ + size_; } + [[nodiscard]] constexpr const_iterator begin() const noexcept { return buckets_; } + [[nodiscard]] constexpr const_iterator end() const noexcept { return buckets_ + size_; } + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return buckets_; } + [[nodiscard]] constexpr const_iterator cend() const noexcept { return buckets_ + size_; } [[nodiscard]] constexpr bool empty() const noexcept { return size_ == 0; } [[nodiscard]] constexpr size_type size() const noexcept { return size_; } @@ -233,19 +220,19 @@ namespace dice::sparse_map::detail { reference operator[](size_type const ix) noexcept { assert(ix < size_); - return data_[ix]; + return buckets_[ix]; } const_reference operator[](size_type const ix) const noexcept { assert(ix < size_); - return data_[ix]; + return buckets_[ix]; } template - bucket_iterator emplace_back(Args &&...args) { + iterator emplace_back(Args &&...args) { if (size_ < cap_) { - new (std::to_address(data_ + size_)) bucket_type{std::forward(args)..., elem_alloc_}; - return data_ + size_++; + new (std::to_address(buckets_ + size_)) bucket_type{std::forward(args)..., elem_alloc_}; + return buckets_ + size_++; } auto const new_cap = next_cap(cap_); @@ -259,41 +246,20 @@ namespace dice::sparse_map::detail { } for (size_type ix = 0; ix < size_; ++ix) { - new (&new_data[ix]) bucket_type{std::move(data_[ix])}; + new (&new_data[ix]) bucket_type{std::move(buckets_[ix])}; } static_assert(std::is_trivially_destructible_v); - bucket_alloc_traits::deallocate(bucket_alloc_, data_, cap_); + bucket_alloc_traits::deallocate(bucket_alloc_, buckets_, cap_); - data_ = new_data; + buckets_ = new_data; cap_ = new_cap; - return data_ + size_++; - } - - template - element_iterator set_element(size_type bucket_ix, typename bucket_type::size_type element_ix, Args &&...args) { - return data_[bucket_ix].set(elem_alloc_, element_ix, std::forward(args)...); - } - - element_iterator erase_element(bucket_iterator bucket, element_iterator elem) { - return (*bucket).erase(elem_alloc_, elem); - } - - element_iterator erase_element(bucket_iterator bucket, element_iterator elem, typename bucket_type::size_type elem_ix) { - return (*bucket).erase(elem_alloc_, elem, elem_ix); + return buckets_ + size_++; } element_allocator_type &element_allocator() noexcept { return elem_alloc_; } - - bool has_value(size_type bucket_ix, typename bucket_type::size_type element_ix) const noexcept { - return data_[bucket_ix].has_value(element_ix); - } - - bool has_deleted_value(size_type bucket_ix, typename bucket_type::size_type element_ix) const noexcept { - return data_[bucket_ix].has_deleted_value(element_ix); - } }; } // namespace dice::sparse_map::detail diff --git a/include/dice/sparse-map/sparse_hash.hpp b/include/dice/sparse-map/sparse_hash.hpp index a0b9147..0c67923 100644 --- a/include/dice/sparse-map/sparse_hash.hpp +++ b/include/dice/sparse-map/sparse_hash.hpp @@ -138,8 +138,8 @@ namespace dice::sparse_map::detail { private: static constexpr bool has_mapped_type = !std::is_same_v; - using sparse_bucket_type = sparse_bucket; using sparse_bucket_array_type = sparse_bucket_array; + using sparse_bucket_type = typename sparse_bucket_array_type::bucket_type; private: sparse_bucket_array_type m_sparse_buckets_data; @@ -172,12 +172,12 @@ namespace dice::sparse_map::detail { friend class sparse_hash; using sparse_bucket_array_iterator = std::conditional_t; + typename sparse_bucket_array_type::const_iterator, + typename sparse_bucket_array_type::iterator>; using sparse_bucket_iterator = std::conditional_t; + typename sparse_bucket_type::const_iterator, + typename sparse_bucket_type::iterator>; private: sparse_bucket_array_iterator cur_bucket_; @@ -303,11 +303,10 @@ namespace dice::sparse_map::detail { sparse_hash(const sparse_hash &other) = default; - sparse_hash(sparse_hash &&other) noexcept(std::is_nothrow_move_constructible::value - && std::is_nothrow_move_constructible::value - && std::is_nothrow_move_constructible::value - && std::is_nothrow_move_constructible::value - && std::is_nothrow_move_constructible::value) + sparse_hash(sparse_hash &&other) noexcept(std::is_nothrow_move_constructible_v + && std::is_nothrow_move_constructible_v + && std::is_nothrow_move_constructible_v + && std::is_nothrow_move_constructible_v) : m_sparse_buckets_data(std::move(other.m_sparse_buckets_data)), m_bucket_count(other.m_bucket_count), m_nb_elements(other.m_nb_elements), @@ -520,11 +519,11 @@ namespace dice::sparse_map::detail { iterator erase(iterator pos) { assert(pos != end() && m_nb_elements > 0); //vector iterator with fancy pointers have a problem with -> - auto next_bucket_it = m_sparse_buckets_data.erase_element(pos.cur_bucket_, pos.bucket_it_); + auto next_bucket_it = pos.cur_bucket_->erase(m_sparse_buckets_data.element_allocator(), pos.bucket_it_); m_nb_elements--; m_nb_deleted_buckets++; - if (next_bucket_it != (*pos.cur_bucket_).end()) { + if (next_bucket_it != pos.cur_bucket_->end()) { return iterator{pos.cur_bucket_, m_sparse_buckets_data.end(), next_bucket_it}; @@ -541,7 +540,7 @@ namespace dice::sparse_map::detail { } else { return iterator{it_sparse_buckets_next, m_sparse_buckets_data.end(), - (*it_sparse_buckets_next).begin()}; + it_sparse_buckets_next->begin()}; } } @@ -827,7 +826,9 @@ namespace dice::sparse_map::detail { typename sparse_bucket_type::size_type index_in_sparse_bucket, Args &&...value_type_args) { // is not called when empty - auto value_it = m_sparse_buckets_data.set_element(sparse_ibucket, index_in_sparse_bucket, std::forward(value_type_args)...); + auto value_it = m_sparse_buckets_data[sparse_ibucket].set(m_sparse_buckets_data.element_allocator(), + index_in_sparse_bucket, + std::forward(value_type_args)...); m_nb_elements++; return std::make_pair(iterator{std::next(m_sparse_buckets_data.begin(), sparse_ibucket), From a9d82637b951a779ddc30e1c8b5bcdcf0f3a9877 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Fri, 4 Aug 2023 08:22:18 +0200 Subject: [PATCH 16/41] getting rid of more members --- include/dice/sparse-map/sparse_bucket.hpp | 4 +- .../dice/sparse-map/sparse_bucket_array.hpp | 4 + include/dice/sparse-map/sparse_hash.hpp | 374 ++++++++---------- include/dice/sparse-map/sparse_map.hpp | 18 +- include/dice/sparse-map/sparse_props.hpp | 8 + include/dice/sparse-map/sparse_set.hpp | 15 +- tests/fancy_pointer/sparse_hash_map_tests.cpp | 8 +- tests/fancy_pointer/sparse_hash_set_tests.cpp | 5 +- .../sparse_hash_set_tests.cpp | 8 +- tests/sparse_map_tests.cpp | 11 +- 10 files changed, 220 insertions(+), 235 deletions(-) diff --git a/include/dice/sparse-map/sparse_bucket.hpp b/include/dice/sparse-map/sparse_bucket.hpp index 6bce2ee..99778e5 100644 --- a/include/dice/sparse-map/sparse_bucket.hpp +++ b/include/dice/sparse-map/sparse_bucket.hpp @@ -389,12 +389,12 @@ namespace dice::sparse_map::detail { * - Either we are in a situation where * std::is_nothrow_move_constructible::value is true. In this * case, on insertion we just reallocate m_values when we reach its capacity - * (i.e. m_nb_elements == m_capacity), otherwise we just put the new value at + * (i.e. n_elements_ == m_capacity), otherwise we just put the new value at * its appropriate place. We can easily keep the strong exception guarantee as * moving the values around is safe. * - Otherwise we are in a situation where * std::is_nothrow_move_constructible::value is false. In this - * case on EACH insertion we allocate a new area of m_nb_elements + 1 where we + * case on EACH insertion we allocate a new area of n_elements_ + 1 where we * copy the values of m_values into it and put the new value there. On * success, we set m_values to this new area. Even if slower, it's the only * way to preserve to strong exception guarantee. diff --git a/include/dice/sparse-map/sparse_bucket_array.hpp b/include/dice/sparse-map/sparse_bucket_array.hpp index ed2844d..96c35ca 100644 --- a/include/dice/sparse-map/sparse_bucket_array.hpp +++ b/include/dice/sparse-map/sparse_bucket_array.hpp @@ -260,6 +260,10 @@ namespace dice::sparse_map::detail { element_allocator_type &element_allocator() noexcept { return elem_alloc_; } + + element_allocator_type const &element_allocator() const noexcept { + return elem_alloc_; + } }; } // namespace dice::sparse_map::detail diff --git a/include/dice/sparse-map/sparse_hash.hpp b/include/dice/sparse-map/sparse_hash.hpp index 0c67923..b4cdd2a 100644 --- a/include/dice/sparse-map/sparse_hash.hpp +++ b/include/dice/sparse-map/sparse_hash.hpp @@ -77,7 +77,7 @@ namespace dice::sparse_map::detail { * * The class holds its buckets in a 2-dimensional fashion. Instead of having a * linear `std::vector` for [0, bucket_count) where each bucket stores - * one value, we have a `std::vector` (m_sparse_buckets_data) + * one value, we have a `std::vector` (buckets_) * where each `sparse_array_type` stores multiple values (up to * `sparse_array_type::BITMAP_NB_BITS`). To convert a one dimensional `ibucket` * position to a position in `std::vector` and a position in @@ -93,7 +93,8 @@ namespace dice::sparse_map::detail { growth_policy GrowthPolicy, exception_safety ExceptionSafety, sparsity Sparsity, - probing Probing> + probing Probing, + ratio MaxLoadFactor> class sparse_hash { private: template @@ -132,38 +133,51 @@ namespace dice::sparse_map::detail { using iterator = sparse_iterator; using const_iterator = sparse_iterator; - static constexpr size_type DEFAULT_INIT_BUCKET_COUNT = 0; - static constexpr float DEFAULT_MAX_LOAD_FACTOR = 0.5f; + static constexpr size_type default_init_bucket_count = 0; + + static constexpr float max_load_factor = static_cast(MaxLoadFactor::num) / static_cast(MaxLoadFactor::den); + static_assert(max_load_factor >= 0.1f && max_load_factor <= 0.8f, + "Specified invalid MaxLoadFactor, must be in range [0.1, 0.8]"); private: + [[nodiscard]] static constexpr size_type calc_load_threshold_rehash(size_type bucket_count) noexcept { + return size_type(float(bucket_count) * max_load_factor); + } + + [[nodiscard]] static constexpr size_type calc_load_threshold_clear_deleted(size_type bucket_count) noexcept { + float const max_load_factor_with_deleted_buckets = max_load_factor + 0.5f * (1.0f - max_load_factor); + assert(max_load_factor_with_deleted_buckets > 0.0f && max_load_factor_with_deleted_buckets <= 1.0f); + + return size_type(float(bucket_count) * max_load_factor_with_deleted_buckets); + } + + static constexpr bool has_mapped_type = !std::is_same_v; using sparse_bucket_array_type = sparse_bucket_array; using sparse_bucket_type = typename sparse_bucket_array_type::bucket_type; private: - sparse_bucket_array_type m_sparse_buckets_data; + sparse_bucket_array_type buckets_; - size_type m_bucket_count; - size_type m_nb_elements; - size_type m_nb_deleted_buckets; + size_type n_elements_; + size_type n_deleted_elements_; /** - * Maximum that m_nb_elements can reach before a rehash occurs automatically + * Maximum that n_elements_ can reach before a rehash occurs automatically * to grow the hash table. */ - size_type m_load_threshold_rehash; + size_type load_threshold_rehash_; /** - * Maximum that m_nb_elements + m_nb_deleted_buckets can reach before cleaning + * Maximum that n_elements_ + n_deleted_elements_ can reach before cleaning * up the buckets marked as deleted. */ - size_type m_load_threshold_clear_deleted; - float m_max_load_factor; + size_type load_threshold_clear_deleted_; - [[no_unique_address]] hasher m_h; - [[no_unique_address]] key_equal m_keq; - [[no_unique_address]] growth_policy m_gpol; + [[no_unique_address]] hasher h_; + [[no_unique_address]] key_equal keq_; + [[no_unique_address]] growth_policy gpol_; public: template @@ -187,7 +201,7 @@ namespace dice::sparse_map::detail { private: /** * sparse_array_it should be nullptr if sparse_bucket_it == - * m_sparse_buckets_data.end(). (TODO better way?) + * buckets_.end(). (TODO better way?) */ sparse_iterator(sparse_bucket_array_iterator bucket, sparse_bucket_array_iterator end_bucket, @@ -263,34 +277,33 @@ namespace dice::sparse_map::detail { iterator mutable_iterator(const_iterator pos) noexcept { // SAFETY: this is non-const therefore the underlying buckets are also non-const // as evidenced by the fact that we can call begin on them - auto it_sparse_buckets = m_sparse_buckets_data.begin() + std::distance(m_sparse_buckets_data.cbegin(), pos.cur_bucket_); + auto it_sparse_buckets = buckets_.begin() + std::distance(buckets_.cbegin(), pos.cur_bucket_); // SAFETY: this is non-const therefore the underlying sparse array is also non-const auto it_array = sparse_bucket_type::unsafe_mutable_iterator(pos.bucket_it_); - return iterator{it_sparse_buckets, m_sparse_buckets_data.end(), it_array}; + return iterator{it_sparse_buckets, buckets_.end(), it_array}; } public: sparse_hash(size_type bucket_count, Hash const &hash, KeyEqual const &equal, - allocator_type const &alloc, float max_load_factor) : m_sparse_buckets_data{alloc}, - m_bucket_count{bucket_count}, - m_nb_elements{0}, - m_nb_deleted_buckets{0}, - m_h{hash}, - m_keq{equal}, - m_gpol{bucket_count} { - if (m_bucket_count > max_bucket_count()) { + allocator_type const &alloc) : buckets_{alloc}, + n_elements_{0}, + n_deleted_elements_{0}, + load_threshold_rehash_{calc_load_threshold_rehash(bucket_count)}, + load_threshold_clear_deleted_{calc_load_threshold_clear_deleted(bucket_count)}, + h_{hash}, + keq_{equal}, + gpol_{bucket_count} { + if (bucket_count > max_bucket_count()) { throw std::length_error("The map exceeds its maximum size."); } - if (m_bucket_count > 0) { - m_sparse_buckets_data.resize(bucket_count); - assert(!m_sparse_buckets_data.empty()); + if (bucket_count > 0) { + buckets_.resize(bucket_count); + assert(!buckets_.empty()); } - this->max_load_factor(max_load_factor); - // Check in the constructor instead of outside of a function to avoid // compilation issues when value_type is not complete. static_assert(std::is_nothrow_move_constructible::value || @@ -299,87 +312,58 @@ namespace dice::sparse_map::detail { "and/or copy constructible."); } - ~sparse_hash() { clear(); } - - sparse_hash(const sparse_hash &other) = default; + sparse_hash(sparse_hash const &other) = default; + sparse_hash &operator=(sparse_hash const &other) = default; sparse_hash(sparse_hash &&other) noexcept(std::is_nothrow_move_constructible_v && std::is_nothrow_move_constructible_v && std::is_nothrow_move_constructible_v && std::is_nothrow_move_constructible_v) - : m_sparse_buckets_data(std::move(other.m_sparse_buckets_data)), - m_bucket_count(other.m_bucket_count), - m_nb_elements(other.m_nb_elements), - m_nb_deleted_buckets(other.m_nb_deleted_buckets), - m_load_threshold_rehash(other.m_load_threshold_rehash), - m_load_threshold_clear_deleted(other.m_load_threshold_clear_deleted), - m_max_load_factor(other.m_max_load_factor), - m_h{std::move(other.m_h)}, - m_keq{std::move(other.m_keq)}, - m_gpol{std::move(other.m_gpol)} { - other.m_gpol.clear(); - other.m_bucket_count = 0; - other.m_nb_elements = 0; - other.m_nb_deleted_buckets = 0; - other.m_load_threshold_rehash = 0; - other.m_load_threshold_clear_deleted = 0; - } - - sparse_hash &operator=(const sparse_hash &other) { - if (this == &other) { - return *this; - } - - clear(); - - m_sparse_buckets_data = other.m_sparse_buckets_data; - m_bucket_count = other.m_bucket_count; - m_nb_elements = other.m_nb_elements; - m_nb_deleted_buckets = other.m_nb_deleted_buckets; - m_load_threshold_rehash = other.m_load_threshold_rehash; - m_load_threshold_clear_deleted = other.m_load_threshold_clear_deleted; - m_max_load_factor = other.m_max_load_factor; - m_h = other.m_h; - m_keq = other.m_keq; - m_gpol = other.m_gpol; - - return *this; + : buckets_{std::move(other.buckets_)}, + n_elements_{std::exchange(other.n_elements_, 0)}, + n_deleted_elements_{std::exchange(other.n_deleted_elements_, 0)}, + load_threshold_rehash_{std::exchange(other.load_threshold_rehash_, 0)}, + load_threshold_clear_deleted_{std::exchange(other.load_threshold_clear_deleted_, 0)}, + h_{std::move(other.h_)}, + keq_{std::move(other.keq_)}, + gpol_{std::move(other.gpol_)} { + other.gpol_.clear(); } sparse_hash &operator=(sparse_hash &&other) noexcept { assert(this != &other); - m_sparse_buckets_data = std::move(other.m_sparse_buckets_data); - m_bucket_count = std::exchange(other.m_bucket_count, 0); - m_nb_elements = std::exchange(other.m_nb_elements, 0); - m_nb_deleted_buckets = std::exchange(other.m_nb_deleted_buckets, 0); - m_load_threshold_rehash = std::exchange(other.m_load_threshold_rehash, 0); - m_load_threshold_clear_deleted = std::exchange(other.m_load_threshold_clear_deleted, 0); - m_max_load_factor = other.m_max_load_factor; + buckets_ = std::move(other.buckets_); + n_elements_ = std::exchange(other.n_elements_, 0); + n_deleted_elements_ = std::exchange(other.n_deleted_elements_, 0); + load_threshold_rehash_ = std::exchange(other.load_threshold_rehash_, 0); + load_threshold_clear_deleted_ = std::exchange(other.load_threshold_clear_deleted_, 0); - m_h = std::move(other.m_h); - m_keq = std::move(other.m_keq); - m_gpol = std::move(other.m_gpol); - other.m_gpol.clear(); + h_ = std::move(other.h_); + keq_ = std::move(other.keq_); + gpol_ = std::move(other.gpol_); + other.gpol_.clear(); return *this; } + ~sparse_hash() = default; + allocator_type get_allocator() const { - return static_cast(*this); + return buckets_.element_allocator(); } iterator begin() noexcept { - auto begin = m_sparse_buckets_data.begin(); + auto begin = buckets_.begin(); //vector iterator with fancy pointers have a problem with -> - while (begin != m_sparse_buckets_data.end() && (*begin).empty()) { + while (begin != buckets_.end() && (*begin).empty()) { ++begin; } //vector iterator with fancy pointers have a problem with -> return iterator{begin, - m_sparse_buckets_data.end(), - begin != m_sparse_buckets_data.end() ? (*begin).begin() : nullptr}; + buckets_.end(), + begin != buckets_.end() ? (*begin).begin() : nullptr}; } const_iterator begin() const noexcept { @@ -387,20 +371,20 @@ namespace dice::sparse_map::detail { } const_iterator cbegin() const noexcept { - auto begin = m_sparse_buckets_data.begin(); + auto begin = buckets_.begin(); //vector iterator with fancy pointers have a problem with -> - while (begin != m_sparse_buckets_data.end() && (*begin).empty()) { + while (begin != buckets_.end() && (*begin).empty()) { ++begin; } return const_iterator{begin, - m_sparse_buckets_data.end(), - begin != m_sparse_buckets_data.end() ? (*begin).begin() : nullptr}; + buckets_.end(), + begin != buckets_.end() ? (*begin).begin() : nullptr}; } iterator end() noexcept { - return iterator{m_sparse_buckets_data.end(), - m_sparse_buckets_data.end(), + return iterator{buckets_.end(), + buckets_.end(), nullptr}; } @@ -409,24 +393,24 @@ namespace dice::sparse_map::detail { } const_iterator cend() const noexcept { - return const_iterator{m_sparse_buckets_data.end(), - m_sparse_buckets_data.end(), + return const_iterator{buckets_.end(), + buckets_.end(), nullptr}; } - bool empty() const noexcept { return m_nb_elements == 0; } + bool empty() const noexcept { return n_elements_ == 0; } - size_type size() const noexcept { return m_nb_elements; } + size_type size() const noexcept { return n_elements_; } size_type max_size() const noexcept { return std::min(std::allocator_traits::max_size(), - m_sparse_buckets_data.max_size()); + buckets_.max_size()); } void clear() noexcept { - m_sparse_buckets_data.clear_buckets(); - m_nb_elements = 0; - m_nb_deleted_buckets = 0; + buckets_.clear_buckets(); + n_elements_ = 0; + n_deleted_elements_ = 0; } template @@ -437,7 +421,7 @@ namespace dice::sparse_map::detail { template iterator insert_hint(const_iterator hint, P &&value) { if (hint != cend() && - m_keq(KeyValueSelect::key(*hint), KeyValueSelect::key(value))) { + keq_(KeyValueSelect::key(*hint), KeyValueSelect::key(value))) { return mutable_iterator(hint); } @@ -450,8 +434,8 @@ namespace dice::sparse_map::detail { std::forward_iterator_tag, typename std::iterator_traits::iterator_category>::value) { const auto nb_elements_insert = std::distance(first, last); - const size_type nb_free_buckets = m_load_threshold_rehash - size(); - assert(m_load_threshold_rehash >= size()); + const size_type nb_free_buckets = load_threshold_rehash_ - size(); + assert(load_threshold_rehash_ >= size()); if (nb_elements_insert > 0 && nb_free_buckets < size_type(nb_elements_insert)) { @@ -476,7 +460,7 @@ namespace dice::sparse_map::detail { template iterator insert_or_assign(const_iterator hint, K &&key, M &&obj) { - if (hint != cend() && m_keq(KeyValueSelect::key(*hint), key)) { + if (hint != cend() && keq_(KeyValueSelect::key(*hint), key)) { auto it = mutable_iterator(hint); it->second = std::forward(obj); @@ -505,7 +489,7 @@ namespace dice::sparse_map::detail { template iterator try_emplace_hint(const_iterator hint, K &&key, Args &&...args) { - if (hint != cend() && m_keq(KeyValueSelect::key(*hint), key)) { + if (hint != cend() && keq_(KeyValueSelect::key(*hint), key)) { return mutable_iterator(hint); } @@ -517,29 +501,29 @@ namespace dice::sparse_map::detail { * when we use an iterator instead of a const_iterator. */ iterator erase(iterator pos) { - assert(pos != end() && m_nb_elements > 0); + assert(pos != end() && n_elements_ > 0); //vector iterator with fancy pointers have a problem with -> - auto next_bucket_it = pos.cur_bucket_->erase(m_sparse_buckets_data.element_allocator(), pos.bucket_it_); - m_nb_elements--; - m_nb_deleted_buckets++; + auto next_bucket_it = pos.cur_bucket_->erase(buckets_.element_allocator(), pos.bucket_it_); + n_elements_--; + n_deleted_elements_++; if (next_bucket_it != pos.cur_bucket_->end()) { return iterator{pos.cur_bucket_, - m_sparse_buckets_data.end(), + buckets_.end(), next_bucket_it}; } auto it_sparse_buckets_next = pos.cur_bucket_; do { ++it_sparse_buckets_next; - } while (it_sparse_buckets_next != m_sparse_buckets_data.end() + } while (it_sparse_buckets_next != buckets_.end() && (*it_sparse_buckets_next).empty()); - if (it_sparse_buckets_next == m_sparse_buckets_data.end()) { + if (it_sparse_buckets_next == buckets_.end()) { return end(); } else { return iterator{it_sparse_buckets_next, - m_sparse_buckets_data.end(), + buckets_.end(), it_sparse_buckets_next->begin()}; } } @@ -569,7 +553,7 @@ namespace dice::sparse_map::detail { template size_type erase(const K &key) { - return erase(key, m_h(key)); + return erase(key, h_(key)); } template @@ -580,21 +564,19 @@ namespace dice::sparse_map::detail { void swap(sparse_hash &other) { using std::swap; - swap(m_sparse_buckets_data, other.m_sparse_buckets_data); - swap(m_bucket_count, other.m_bucket_count); - swap(m_nb_elements, other.m_nb_elements); - swap(m_nb_deleted_buckets, other.m_nb_deleted_buckets); - swap(m_load_threshold_rehash, other.m_load_threshold_rehash); - swap(m_load_threshold_clear_deleted, other.m_load_threshold_clear_deleted); - swap(m_max_load_factor, other.m_max_load_factor); - swap(m_h, other.m_h); - swap(m_keq, other.m_keq); - swap(m_gpol, other.m_gpol); + swap(buckets_, other.buckets_); + swap(n_elements_, other.n_elements_); + swap(n_deleted_elements_, other.n_deleted_elements_); + swap(load_threshold_rehash_, other.load_threshold_rehash_); + swap(load_threshold_clear_deleted_, other.load_threshold_clear_deleted_); + swap(h_, other.h_); + swap(keq_, other.keq_); + swap(gpol_, other.gpol_); } template requires (has_mapped_type) mapped_reference at(const K &key) { - return at_impl(*this, key, m_h(key)); + return at_impl(*this, key, h_(key)); } template requires (has_mapped_type) @@ -604,7 +586,7 @@ namespace dice::sparse_map::detail { template requires (has_mapped_type) mapped_const_reference at(const K &key) const { - return at_impl(*this, key, m_h(key)); + return at_impl(*this, key, h_(key)); } template requires (has_mapped_type) @@ -619,7 +601,7 @@ namespace dice::sparse_map::detail { template bool contains(const K &key) const { - return contains(key, m_h(key)); + return contains(key, h_(key)); } template @@ -629,7 +611,7 @@ namespace dice::sparse_map::detail { template size_type count(const K &key) const { - return count(key, m_h(key)); + return count(key, h_(key)); } template @@ -643,7 +625,7 @@ namespace dice::sparse_map::detail { template iterator find(const K &key) { - return find_impl(*this, key, m_h(key)); + return find_impl(*this, key, h_(key)); } template @@ -653,7 +635,7 @@ namespace dice::sparse_map::detail { template const_iterator find(const K &key) const { - return find_impl(*this, key, m_h(key)); + return find_impl(*this, key, h_(key)); } template @@ -663,7 +645,7 @@ namespace dice::sparse_map::detail { template std::pair equal_range(const K &key) { - return equal_range(key, m_h(key)); + return equal_range(key, h_(key)); } template @@ -674,7 +656,7 @@ namespace dice::sparse_map::detail { template std::pair equal_range(const K &key) const { - return equal_range(key, m_h(key)); + return equal_range(key, h_(key)); } template @@ -684,10 +666,10 @@ namespace dice::sparse_map::detail { return std::make_pair(it, (it == cend()) ? it : std::next(it)); } - size_type bucket_count() const { return m_bucket_count; } + size_type bucket_count() const { return buckets_.size(); } size_type max_bucket_count() const { - return m_sparse_buckets_data.max_size(); + return buckets_.max_size(); } float load_factor() const { @@ -695,51 +677,36 @@ namespace dice::sparse_map::detail { return 0; } - return float(m_nb_elements) / float(bucket_count()); - } - - float max_load_factor() const { return m_max_load_factor; } - - void max_load_factor(float ml) { - m_max_load_factor = std::max(0.1f, std::min(ml, 0.8f)); - m_load_threshold_rehash = - size_type(float(bucket_count()) * m_max_load_factor); - - const float max_load_factor_with_deleted_buckets = - m_max_load_factor + 0.5f * (1.0f - m_max_load_factor); - assert(max_load_factor_with_deleted_buckets > 0.0f && - max_load_factor_with_deleted_buckets <= 1.0f); - m_load_threshold_clear_deleted = - size_type(float(bucket_count()) * max_load_factor_with_deleted_buckets); + return float(n_elements_) / float(bucket_count()); } void rehash(size_type count) { - count = std::max(count, size_type(std::ceil(float(size()) / max_load_factor()))); + count = std::max(count, size_type(std::ceil(float(size()) / max_load_factor))); rehash_impl(count); } void reserve(size_type count) { - rehash(size_type(std::ceil(float(count) / max_load_factor()))); + rehash(size_type(std::ceil(float(count) / max_load_factor))); } - [[nodiscard]] hasher hash_function() const { return m_h; } - [[nodiscard]] key_equal key_eq() const { return m_keq; } + [[nodiscard]] hasher hash_function() const { return h_; } + [[nodiscard]] key_equal key_eq() const { return keq_; } private: size_type bucket_for_hash(std::size_t hash) const { - auto const bucket = m_gpol.bucket_for_hash(hash); - assert(sparse_bucket_type::sparse_ibucket(bucket) < m_sparse_buckets_data.size() - || (bucket == 0 && m_sparse_buckets_data.empty())); + auto const bucket = gpol_.bucket_for_hash(hash); + assert(sparse_bucket_type::sparse_ibucket(bucket) < buckets_.size() + || (bucket == 0 && buckets_.empty())); return bucket; } size_type next_bucket(size_type ibucket, [[maybe_unused]] size_type iprobe) const requires (is_power_of_two_policy::value) { if constexpr (Probing == probing::linear) { - return (ibucket + 1) & m_gpol.mask(); + return (ibucket + 1) & gpol_.mask(); } else { assert(Probing == probing::quadratic); - return (ibucket + iprobe) & m_gpol.mask(); + return (ibucket + iprobe) & gpol_.mask(); } } @@ -757,13 +724,12 @@ namespace dice::sparse_map::detail { template std::pair insert_impl(const K &key, Args &&...value_type_args) { - if (size() >= m_load_threshold_rehash) { - rehash_impl(m_gpol.next_bucket_count()); - } else if (size() + m_nb_deleted_buckets >= - m_load_threshold_clear_deleted) { + if (size() >= load_threshold_rehash_) { + rehash_impl(gpol_.next_bucket_count()); + } else if (size() + n_deleted_elements_ >= load_threshold_clear_deleted_) { clear_deleted_buckets(); } - //assert(!m_sparse_buckets_data.empty()); + assert(!buckets_.empty()); /** * We must insert the value in the first empty or deleted bucket we find. If @@ -777,7 +743,7 @@ namespace dice::sparse_map::detail { std::size_t sparse_ibucket_first_deleted = 0; typename sparse_bucket_type::size_type index_in_sparse_bucket_first_deleted = 0; - const std::size_t hash = m_h(key); + const std::size_t hash = h_(key); std::size_t ibucket = bucket_for_hash(hash); std::size_t probe = 0; @@ -785,16 +751,16 @@ namespace dice::sparse_map::detail { std::size_t sparse_ibucket = sparse_bucket_type::sparse_ibucket(ibucket); auto index_in_sparse_bucket = sparse_bucket_type::index_in_sparse_bucket(ibucket); - if (!m_sparse_buckets_data.empty()) { - if (m_sparse_buckets_data[sparse_ibucket].has_value(index_in_sparse_bucket)) { - auto value_it = m_sparse_buckets_data[sparse_ibucket].value(index_in_sparse_bucket); - if (m_keq(key, KeyValueSelect::key(*value_it))) { - return std::make_pair(iterator{std::next(m_sparse_buckets_data.begin(), sparse_ibucket), - m_sparse_buckets_data.end(), + if (!buckets_.empty()) { + if (buckets_[sparse_ibucket].has_value(index_in_sparse_bucket)) { + auto value_it = buckets_[sparse_ibucket].value(index_in_sparse_bucket); + if (keq_(key, KeyValueSelect::key(*value_it))) { + return std::make_pair(iterator{std::next(buckets_.begin(), sparse_ibucket), + buckets_.end(), value_it}, false); } - } else if (m_sparse_buckets_data[sparse_ibucket].has_deleted_value(index_in_sparse_bucket) && probe < m_bucket_count) { + } else if (buckets_[sparse_ibucket].has_deleted_value(index_in_sparse_bucket) && probe < buckets_.size()) { if (!found_first_deleted_bucket) { found_first_deleted_bucket = true; sparse_ibucket_first_deleted = sparse_ibucket; @@ -804,7 +770,7 @@ namespace dice::sparse_map::detail { auto it = insert_in_bucket(sparse_ibucket_first_deleted, index_in_sparse_bucket_first_deleted, std::forward(value_type_args)...); - m_nb_deleted_buckets--; + n_deleted_elements_--; return it; } else { @@ -826,20 +792,20 @@ namespace dice::sparse_map::detail { typename sparse_bucket_type::size_type index_in_sparse_bucket, Args &&...value_type_args) { // is not called when empty - auto value_it = m_sparse_buckets_data[sparse_ibucket].set(m_sparse_buckets_data.element_allocator(), + auto value_it = buckets_[sparse_ibucket].set(buckets_.element_allocator(), index_in_sparse_bucket, std::forward(value_type_args)...); - m_nb_elements++; + n_elements_++; - return std::make_pair(iterator{std::next(m_sparse_buckets_data.begin(), sparse_ibucket), - m_sparse_buckets_data.end(), + return std::make_pair(iterator{std::next(buckets_.begin(), sparse_ibucket), + buckets_.end(), value_it}, true); } template size_type erase_impl(K const &key, std::size_t hash) { - if (m_sparse_buckets_data.empty()) { + if (buckets_.empty()) { return 0; } @@ -850,19 +816,19 @@ namespace dice::sparse_map::detail { auto const sparse_ibucket = sparse_bucket_type::sparse_ibucket(ibucket); auto const index_in_sparse_bucket = sparse_bucket_type::index_in_sparse_bucket(ibucket); - auto &bucket = m_sparse_buckets_data[sparse_ibucket]; + auto &bucket = buckets_[sparse_ibucket]; if (bucket.has_value(index_in_sparse_bucket)) { auto value_it = bucket.value(index_in_sparse_bucket); - if (m_keq(key, KeyValueSelect::key(*value_it))) { - bucket.erase(m_sparse_buckets_data.element_allocator(), value_it, index_in_sparse_bucket); - m_nb_elements--; - m_nb_deleted_buckets++; + if (keq_(key, KeyValueSelect::key(*value_it))) { + bucket.erase(buckets_.element_allocator(), value_it, index_in_sparse_bucket); + n_elements_--; + n_deleted_elements_++; return 1; } - } else if (!bucket.has_deleted_value(index_in_sparse_bucket) || probe >= m_bucket_count) { + } else if (!bucket.has_deleted_value(index_in_sparse_bucket) || probe >= buckets_.size()) { return 0; } @@ -873,7 +839,7 @@ namespace dice::sparse_map::detail { template static auto find_impl(Self &&self, K const &key, std::size_t hash) { - if (self.m_sparse_buckets_data.empty()) { + if (self.buckets_.empty()) { return self.end(); } @@ -884,18 +850,18 @@ namespace dice::sparse_map::detail { auto const sparse_ibucket = sparse_bucket_type::sparse_ibucket(ibucket); auto const index_in_sparse_bucket = sparse_bucket_type::index_in_sparse_bucket(ibucket); - auto &bucket = self.m_sparse_buckets_data[sparse_ibucket]; + auto &bucket = self.buckets_[sparse_ibucket]; if (bucket.has_value(index_in_sparse_bucket)) { auto value_it = bucket.value(index_in_sparse_bucket); - if (self.m_keq(key, KeyValueSelect::key(*value_it))) { + if (self.keq_(key, KeyValueSelect::key(*value_it))) { static constexpr bool is_const = std::is_const_v>; - return sparse_iterator{std::next(self.m_sparse_buckets_data.begin(), sparse_ibucket), - self.m_sparse_buckets_data.end(), + return sparse_iterator{std::next(self.buckets_.begin(), sparse_ibucket), + self.buckets_.end(), value_it}; } - } else if (!bucket.has_deleted_value(index_in_sparse_bucket) || probe >= self.m_bucket_count) { + } else if (!bucket.has_deleted_value(index_in_sparse_bucket) || probe >= self.buckets_.size()) { return self.end(); } @@ -916,20 +882,20 @@ namespace dice::sparse_map::detail { void clear_deleted_buckets() { // TODO could be optimized, we could do it in-place instead of allocating a // new bucket array. - rehash_impl(m_bucket_count); - assert(m_nb_deleted_buckets == 0); + rehash_impl(buckets_.size()); + assert(n_deleted_elements_ == 0); } void rehash_impl(size_type count) requires (ExceptionSafety == exception_safety::basic) { - sparse_hash new_table(count, m_h, m_keq, m_sparse_buckets_data.element_allocator(), m_max_load_factor); + sparse_hash new_table(count, h_, keq_, buckets_.element_allocator()); - for (auto &bucket : m_sparse_buckets_data) { + for (auto &bucket : buckets_) { for (auto &val : bucket) { new_table.insert_on_rehash(std::move(val)); } // TODO try to reuse some of the memory - bucket.clear(m_sparse_buckets_data.element_allocator()); + bucket.clear(buckets_.element_allocator()); } new_table.swap(*this); @@ -941,9 +907,9 @@ namespace dice::sparse_map::detail { * any exception if we reserve enough space in the sparse arrays beforehand. */ void rehash_impl(size_type count) requires (ExceptionSafety == exception_safety::strong) { - sparse_hash new_table(count, m_h, m_keq, m_sparse_buckets_data.element_allocator(), m_max_load_factor); + sparse_hash new_table(count, h_, keq_, buckets_.element_allocator()); - for (const auto &bucket : m_sparse_buckets_data) { + for (const auto &bucket : buckets_) { for (const auto &val : bucket) { new_table.insert_on_rehash(val); } @@ -956,7 +922,7 @@ namespace dice::sparse_map::detail { void insert_on_rehash(K &&key_value) { const key_type &key = KeyValueSelect::key(key_value); - std::size_t const hash = m_h(key); + std::size_t const hash = h_(key); std::size_t ibucket = bucket_for_hash(hash); std::size_t probe = 0; @@ -964,15 +930,15 @@ namespace dice::sparse_map::detail { auto const sparse_ibucket = sparse_bucket_type::sparse_ibucket(ibucket); auto const index_in_sparse_bucket = sparse_bucket_type::index_in_sparse_bucket(ibucket); - auto &bucket = m_sparse_buckets_data[sparse_ibucket]; + auto &bucket = buckets_[sparse_ibucket]; if (!bucket.has_value(index_in_sparse_bucket)) { - bucket.set(m_sparse_buckets_data.element_allocator(), index_in_sparse_bucket, std::forward(key_value)); - m_nb_elements++; + bucket.set(buckets_.element_allocator(), index_in_sparse_bucket, std::forward(key_value)); + n_elements_++; return; } else { - assert(!m_keq(key, KeyValueSelect::key(*bucket.value(index_in_sparse_bucket)))); + assert(!keq_(key, KeyValueSelect::key(*bucket.value(index_in_sparse_bucket)))); } probe++; diff --git a/include/dice/sparse-map/sparse_map.hpp b/include/dice/sparse-map/sparse_map.hpp index 4344f5b..6166568 100644 --- a/include/dice/sparse-map/sparse_map.hpp +++ b/include/dice/sparse-map/sparse_map.hpp @@ -85,7 +85,8 @@ namespace dice::sparse_map { typename Allocator = std::allocator>, growth_policy GrowthPolicy = power_of_two_growth_policy<2>, exception_safety ExceptionSafety = exception_safety::basic, - sparsity Sparsity = sparsity::medium> + sparsity Sparsity = sparsity::medium, + ratio MaxLoadFactor = std::ratio<1, 2>> class sparse_map { static constexpr bool key_equal_is_transparent = requires { typename KeyEqual::is_transparent; @@ -123,7 +124,7 @@ namespace dice::sparse_map { }; using ht = detail::sparse_hash, KVSelect, Hash, KeyEqual, Allocator, - GrowthPolicy, ExceptionSafety, Sparsity, probing::quadratic>; + GrowthPolicy, ExceptionSafety, Sparsity, probing::quadratic, MaxLoadFactor>; public: using key_type = typename ht::key_type; @@ -140,14 +141,15 @@ namespace dice::sparse_map { using const_pointer = typename ht::const_pointer; using iterator = typename ht::iterator; using const_iterator = typename ht::const_iterator; + static constexpr float max_load_factor = ht::max_load_factor; public: - sparse_map() : sparse_map(ht::DEFAULT_INIT_BUCKET_COUNT) {} + sparse_map() : sparse_map(ht::default_init_bucket_count) {} explicit sparse_map(size_type bucket_count, const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), const Allocator &alloc = Allocator()) - : m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) {} + : m_ht(bucket_count, hash, equal, alloc) {} sparse_map(size_type bucket_count, const Allocator &alloc) : sparse_map(bucket_count, Hash(), KeyEqual(), alloc) {} @@ -156,11 +158,11 @@ namespace dice::sparse_map { : sparse_map(bucket_count, hash, KeyEqual(), alloc) {} explicit sparse_map(const Allocator &alloc) - : sparse_map(ht::DEFAULT_INIT_BUCKET_COUNT, alloc) {} + : sparse_map(ht::default_init_bucket_count, alloc) {} template sparse_map(InputIt first, InputIt last, - size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, + size_type bucket_count = ht::default_init_bucket_count, const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), const Allocator &alloc = Allocator()) : sparse_map(bucket_count, hash, equal, alloc) { @@ -178,7 +180,7 @@ namespace dice::sparse_map { : sparse_map(first, last, bucket_count, hash, KeyEqual(), alloc) {} sparse_map(std::initializer_list init, - size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, + size_type bucket_count = ht::default_init_bucket_count, const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), const Allocator &alloc = Allocator()) : sparse_map(init.begin(), init.end(), bucket_count, hash, equal, alloc) { @@ -629,8 +631,6 @@ namespace dice::sparse_map { [[nodiscard]] size_type max_bucket_count() const { return m_ht.max_bucket_count(); } [[nodiscard]] float load_factor() const { return m_ht.load_factor(); } - [[nodiscard]] float max_load_factor() const { return m_ht.max_load_factor(); } - void max_load_factor(float ml) { m_ht.max_load_factor(ml); } void rehash(size_type count) { m_ht.rehash(count); } void reserve(size_type count) { m_ht.reserve(count); } diff --git a/include/dice/sparse-map/sparse_props.hpp b/include/dice/sparse-map/sparse_props.hpp index d4c0c74..9fd1619 100644 --- a/include/dice/sparse-map/sparse_props.hpp +++ b/include/dice/sparse-map/sparse_props.hpp @@ -21,6 +21,14 @@ namespace dice::sparse_map { medium, low }; + + template + concept ratio = requires { + { T::num } -> std::convertible_to; + { T::den } -> std::convertible_to; + }; + + using default_max_load_factor = std::ratio<1, 2>; } // namespace dice::sparse_map #endif//DICE_SPARSE_MAP_SPARSE_PROPS_HPP diff --git a/include/dice/sparse-map/sparse_set.hpp b/include/dice/sparse-map/sparse_set.hpp index 7e5e9b5..0be9348 100644 --- a/include/dice/sparse-map/sparse_set.hpp +++ b/include/dice/sparse-map/sparse_set.hpp @@ -85,7 +85,8 @@ namespace dice::sparse_map { typename Allocator = std::allocator, growth_policy GrowthPolicy = power_of_two_growth_policy<2>, exception_safety ExceptionSafety = exception_safety::basic, - sparsity Sparsity = sparsity::medium> + sparsity Sparsity = sparsity::medium, + ratio MaxLoadFactor = std::ratio<1, 2>> class sparse_set { static constexpr bool key_equal_is_transparent = requires { typename KeyEqual::is_transparent; @@ -106,7 +107,7 @@ namespace dice::sparse_map { using ht = detail::sparse_hash; + Sparsity, probing::quadratic, MaxLoadFactor>; public: using key_type = typename ht::key_type; @@ -123,12 +124,12 @@ namespace dice::sparse_map { using iterator = typename ht::iterator; using const_iterator = typename ht::const_iterator; - sparse_set() : sparse_set(ht::DEFAULT_INIT_BUCKET_COUNT) {} + sparse_set() : sparse_set(ht::default_init_bucket_count) {} explicit sparse_set(size_type bucket_count, const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), const Allocator &alloc = Allocator()) - : m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) {} + : m_ht(bucket_count, hash, equal, alloc) {} sparse_set(size_type bucket_count, const Allocator &alloc) : sparse_set(bucket_count, Hash(), KeyEqual(), alloc) {} @@ -137,11 +138,11 @@ namespace dice::sparse_map { : sparse_set(bucket_count, hash, KeyEqual(), alloc) {} explicit sparse_set(const Allocator &alloc) - : sparse_set(ht::DEFAULT_INIT_BUCKET_COUNT, alloc) {} + : sparse_set(ht::default_init_bucket_count, alloc) {} template sparse_set(InputIt first, InputIt last, - size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, + size_type bucket_count = ht::default_init_bucket_count, const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), const Allocator &alloc = Allocator()) : sparse_set(bucket_count, hash, equal, alloc) { @@ -159,7 +160,7 @@ namespace dice::sparse_map { : sparse_set(first, last, bucket_count, hash, KeyEqual(), alloc) {} sparse_set(std::initializer_list init, - size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, + size_type bucket_count = ht::default_init_bucket_count, const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), const Allocator &alloc = Allocator()) : sparse_set(init.begin(), init.end(), bucket_count, hash, equal, alloc) { diff --git a/tests/fancy_pointer/sparse_hash_map_tests.cpp b/tests/fancy_pointer/sparse_hash_map_tests.cpp index 18ffb74..1b8a2bb 100644 --- a/tests/fancy_pointer/sparse_hash_map_tests.cpp +++ b/tests/fancy_pointer/sparse_hash_map_tests.cpp @@ -53,16 +53,16 @@ namespace details { dice::sparse_map::power_of_two_growth_policy<2>, dice::sparse_map::exception_safety::basic, dice::sparse_map::sparsity::medium, - dice::sparse_map::probing::quadratic>; + dice::sparse_map::probing::quadratic, + dice::sparse_map::default_max_load_factor>; template typename T::Map default_construct_map() { using Key = typename T::key_type; - return typename T::Map(T::Map::DEFAULT_INIT_BUCKET_COUNT, + return typename T::Map(T::Map::default_init_bucket_count, std::hash(), std::equal_to(), - typename T::Allocator(), - T::Map::DEFAULT_MAX_LOAD_FACTOR); + typename T::Allocator()); } /** Checks if all values of the map are in the initializer_list and than if the lengths are equal. diff --git a/tests/fancy_pointer/sparse_hash_set_tests.cpp b/tests/fancy_pointer/sparse_hash_set_tests.cpp index 0e135c6..28cc09f 100644 --- a/tests/fancy_pointer/sparse_hash_set_tests.cpp +++ b/tests/fancy_pointer/sparse_hash_set_tests.cpp @@ -28,11 +28,12 @@ namespace details { dice::sparse_map::power_of_two_growth_policy<2>, dice::sparse_map::exception_safety::basic, dice::sparse_map::sparsity::medium, - dice::sparse_map::probing::quadratic>; + dice::sparse_map::probing::quadratic, + dice::sparse_map::default_max_load_factor>; template Set default_construct_set() { - return Set{Set::DEFAULT_INIT_BUCKET_COUNT, {}, {}, {}, Set::DEFAULT_MAX_LOAD_FACTOR}; + return Set{Set::default_init_bucket_count, {}, {}, {}}; } /** checks if all values of the set are in the initializer_list and than if the lengths are equal. diff --git a/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp b/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp index d99b4de..82e9aaf 100644 --- a/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp +++ b/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp @@ -31,15 +31,15 @@ using sparse_set = dice::sparse_map::detail::sparse_hash< dice::sparse_map::power_of_two_growth_policy<2>, dice::sparse_map::exception_safety::basic, dice::sparse_map::sparsity::medium, - dice::sparse_map::probing::quadratic>; + dice::sparse_map::probing::quadratic, + dice::sparse_map::default_max_load_factor>; } // namespace details template void construction() { using Type = typename T::value_type; - typename T::Set(T::Set::DEFAULT_INIT_BUCKET_COUNT, details::Hash(), - std::equal_to(), typename T::Allocator(), - T::Set::DEFAULT_MAX_LOAD_FACTOR); + typename T::Set(T::Set::default_init_bucket_count, details::Hash(), + std::equal_to(), typename T::Allocator()); } diff --git a/tests/sparse_map_tests.cpp b/tests/sparse_map_tests.cpp index 41e2cda..3cc351b 100644 --- a/tests/sparse_map_tests.cpp +++ b/tests/sparse_map_tests.cpp @@ -1008,12 +1008,17 @@ TEST_SUITE("sparse map") { } }; - dice::sparse_map::sparse_map map; - map.max_load_factor(0.8f); + dice::sparse_map::sparse_map, + std::allocator>, + dice::sparse_map::power_of_two_growth_policy<2>, + dice::sparse_map::exception_safety::basic, + dice::sparse_map::sparsity::medium, + std::ratio<8, 10>> map; map.rehash(64); CHECK_EQ(map.bucket_count(), 64); - CHECK_EQ(map.max_load_factor(), 0.8f); + CHECK_EQ(map.max_load_factor, 0.8f); for (unsigned int i = 0; i < 51; i++) { CHECK(map.insert({i, i}).second); From 64fa09a9ca568749c8058909ecb80d624e2c578b Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Fri, 4 Aug 2023 08:36:06 +0200 Subject: [PATCH 17/41] throw out boost and update readme and stuff --- CMakeLists.txt | 6 - README.md | 217 +----------------------- include/dice/sparse-map/sparse_hash.hpp | 2 - tests/CMakeLists.txt | 3 + tsl-sparse-map.natvis | 73 -------- 5 files changed, 9 insertions(+), 292 deletions(-) delete mode 100644 tsl-sparse-map.natvis diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d3b4a2..608694c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,6 @@ project(dice-sparse-map include(cmake/boilerplate_init.cmake) boilerplate_init() -find_package(Boost REQUIRED) - add_library(${PROJECT_NAME} INTERFACE) # Use dice::sparse_map as target, more consistent with other libraries conventions (Boost, Qt, ...) add_library("${PROJECT_NAME}::${PROJECT_NAME}" ALIAS "${PROJECT_NAME}") @@ -15,10 +13,6 @@ add_library("${PROJECT_NAME}::${PROJECT_NAME}" ALIAS "${PROJECT_NAME}") target_include_directories(${PROJECT_NAME} INTERFACE "$") -target_link_libraries(${PROJECT_NAME} INTERFACE - Boost::headers - ) - if(MSVC) target_sources(${PROJECT_NAME} INTERFACE "$" diff --git a/README.md b/README.md index ea27c7b..e549187 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ A **benchmark** of `dice::sparse_map::sparse_map` against other hash maps may be - Support for heterogeneous lookups allowing the usage of `find` with a type different than `Key` (e.g. if you have a map that uses `std::unique_ptr` as key, you can use a `foo*` or a `std::uintptr_t` as key parameter to `find` without constructing a `std::unique_ptr`, see [example](#heterogeneous-lookups)). - No need to reserve any sentinel value from the keys. - If the hash is known before a lookup, it is possible to pass it as parameter to speed-up the lookup (see `precalculated_hash` parameter in [API](https://tessil.github.io/sparse-map/classtsl_1_1sparse__map.html)). -- Support for efficient serialization and deserialization (see [example](#serialization) and the `serialize/deserialize` methods in the [API](https://tessil.github.io/sparse-map/classtsl_1_1sparse__map.html) for details). - Possibility to control the balance between insertion speed and memory usage with the `Sparsity` template parameter. A high sparsity means less memory but longer insertion times, and vice-versa for low sparsity. The default medium sparsity offers a good compromise (see [API](https://tessil.github.io/sparse-map/classtsl_1_1sparse__map.html#details) for details). For reference, with simple 64 bits integers as keys and values, a low sparsity offers ~15% faster insertions times but uses ~12% more memory. Nothing change regarding lookup speed. - API closely similar to `std::unordered_map` and `std::unordered_set`. @@ -47,8 +46,6 @@ The library relies heavily on the [popcount](https://en.wikipedia.org/wiki/Hammi With Clang and GCC, the library uses the `__builtin_popcount` function which will use the fast CPU instruction `POPCNT` when the library is compiled with `-mpopcnt`. Using the `POPCNT` instruction offers an improvement of ~15% to ~30% on lookups. So if you are compiling your code for a specific architecture that support the operation, don't forget the `-mpopcnt` (or `-march=native`) flag of your compiler. -On Windows with MSVC, the detection is done at runtime. - #### Move constructor Make sure that your key `Key` and potential value `T` have a `noexcept` move constructor. The library will work without it but insertions will be much slower if the copy constructor is expensive (the structure often needs to move some values around on insertion). @@ -102,23 +99,21 @@ If the project has been installed through `make install`, you can also use `find The code should work with any C++11 standard-compliant compiler and has been tested with GCC 4.8.4, Clang 3.5.0 and Visual Studio 2015. -To run the tests you will need the Boost Test library and CMake. +To run the tests you will need CMake and CTest. ```bash -git clone https://github.com/Tessil/sparse-map.git -cd sparse-map/tests +git clone https://github.com/dice-group/dice-sparse-map.git +cd sparse-map mkdir build cd build -cmake .. +cmake -DBUILD_TESTING=ON .. cmake --build . -./tsl_sparse_map_tests +ctest ``` ### Usage -The API can be found [here](https://tessil.github.io/sparse-map/). - -All methods are not documented yet, but they replicate the behaviour of the ones in `std::unordered_map` and `std::unordered_set`, except if specified otherwise. +Not all methods are documented yet, but they replicate the behaviour of the ones in `std::unordered_map` and `std::unordered_set`, except if specified otherwise. ### Example @@ -263,206 +258,6 @@ int main() { } ``` -#### Serialization - -The library provides an efficient way to serialize and deserialize a map or a set so that it can be saved to a file or send through the network. -To do so, it requires the user to provide a function object for both serialization and deserialization. - -```c++ -struct serializer { - // Must support the following types for U: std::uint64_t, float - // and std::pair if a map is used or Key for a set. - template - void operator()(const U& value); -}; -``` - -```c++ -struct deserializer { - // Must support the following types for U: std::uint64_t, float - // and std::pair if a map is used or Key for a set. - template - U operator()(); -}; -``` - -Note that the implementation leaves binary compatibility (endianness, float binary representation, size of int, ...) of the types it serializes/deserializes in the hands of the provided function objects if compatibility is required. - -More details regarding the `serialize` and `deserialize` methods can be found in the [API](https://tessil.github.io/sparse-map/classtsl_1_1sparse__map.html). - -```c++ -#include -#include -#include -#include -#include - - -class serializer { -public: - serializer(const char* file_name) { - m_ostream.exceptions(m_ostream.badbit | m_ostream.failbit); - m_ostream.open(file_name, std::ios::binary); - } - - template::value>::type* = nullptr> - void operator()(const T& value) { - m_ostream.write(reinterpret_cast(&value), sizeof(T)); - } - - void operator()(const std::pair& value) { - (*this)(value.first); - (*this)(value.second); - } - -private: - std::ofstream m_ostream; -}; - -class deserializer { -public: - deserializer(const char* file_name) { - m_istream.exceptions(m_istream.badbit | m_istream.failbit | m_istream.eofbit); - m_istream.open(file_name, std::ios::binary); - } - - template - T operator()() { - T value; - deserialize(value); - - return value; - } - -private: - template::value>::type* = nullptr> - void deserialize(T& value) { - m_istream.read(reinterpret_cast(&value), sizeof(T)); - } - - void deserialize(std::pair& value) { - deserialize(value.first); - deserialize(value.second); - } - -private: - std::ifstream m_istream; -}; - - -int main() { - const dice::sparse_map::sparse_map map = {{1, -1}, {2, -2}, {3, -3}, {4, -4}}; - - - const char* file_name = "sparse_map.data"; - { - serializer serial(file_name); - map.serialize(serial); - } - - { - deserializer dserial(file_name); - auto map_deserialized = dice::sparse_map::sparse_map::deserialize(dserial); - - assert(map == map_deserialized); - } - - { - deserializer dserial(file_name); - - /** - * If the serialized and deserialized map are hash compatibles (see conditions in API), - * setting the argument to true speed-up the deserialization process as we don't have - * to recalculate the hash of each key. We also know how much space each bucket needs. - */ - const bool hash_compatible = true; - auto map_deserialized = - dice::sparse_map::sparse_map::deserialize(dserial, hash_compatible); - - assert(map == map_deserialized); - } -} -``` - -##### Serialization with Boost Serialization and compression with zlib - -It's possible to use a serialization library to avoid the boilerplate. - -The following example uses Boost Serialization with the Boost zlib compression stream to reduce the size of the resulting serialized file. The example requires C++20 due to the usage of the template parameter list syntax in lambdas, but it can be adapted to less recent versions. - -```c++ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -namespace boost { namespace serialization { - template - void serialize(Archive & ar, dice::sparse_map::sparse_map& map, const unsigned int version) { - split_free(ar, map, version); - } - - template - void save(Archive & ar, const dice::sparse_map::sparse_map& map, const unsigned int /*version*/) { - auto serializer = [&ar](const auto& v) { ar & v; }; - map.serialize(serializer); - } - - template - void load(Archive & ar, dice::sparse_map::sparse_map& map, const unsigned int /*version*/) { - auto deserializer = [&ar]() { U u; ar & u; return u; }; - map = dice::sparse_map::sparse_map::deserialize(deserializer); - } -}} - - -int main() { - dice::sparse_map::sparse_map map = {{1, -1}, {2, -2}, {3, -3}, {4, -4}}; - - - const char* file_name = "sparse_map.data"; - { - std::ofstream ofs; - ofs.exceptions(ofs.badbit | ofs.failbit); - ofs.open(file_name, std::ios::binary); - - boost::iostreams::filtering_ostream fo; - fo.push(boost::iostreams::zlib_compressor()); - fo.push(ofs); - - boost::archive::binary_oarchive oa(fo); - - oa << map; - } - - { - std::ifstream ifs; - ifs.exceptions(ifs.badbit | ifs.failbit | ifs.eofbit); - ifs.open(file_name, std::ios::binary); - - boost::iostreams::filtering_istream fi; - fi.push(boost::iostreams::zlib_decompressor()); - fi.push(ifs); - - boost::archive::binary_iarchive ia(fi); - - dice::sparse_map::sparse_map map_deserialized; - ia >> map_deserialized; - - assert(map == map_deserialized); - } -} -``` - ### License The code is licensed under the MIT license, see the LICENSE files ([1](LICENSE-tsl-sparse-map), [2](LICENSE-dice-sparse-map)) for details. diff --git a/include/dice/sparse-map/sparse_hash.hpp b/include/dice/sparse-map/sparse_hash.hpp index b4cdd2a..8bd86c1 100644 --- a/include/dice/sparse-map/sparse_hash.hpp +++ b/include/dice/sparse-map/sparse_hash.hpp @@ -40,8 +40,6 @@ #include #include -#include - #include "dice/sparse-map/sparse_bucket.hpp" #include "dice/sparse-map/sparse_growth_policy.hpp" #include "dice/sparse-map/sparse_bucket_array.hpp" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 21f9963..e93393e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -17,6 +17,7 @@ macro(make_test DIR NAME) target_link_libraries(${TARGET} doctest::doctest dice-sparse-map::dice-sparse-map + Boost::headers ) set_property(TARGET ${TARGET} PROPERTY CXX_STANDARD 20) add_test(NAME ${TARGET} COMMAND ${TARGET}) @@ -28,6 +29,8 @@ macro(make_test DIR NAME) endif() endmacro () +find_package(Boost REQUIRED COMPONENTS) + make_test(. custom_allocator_tests) make_test(. policy_tests) make_test(. sparse_map_tests) diff --git a/tsl-sparse-map.natvis b/tsl-sparse-map.natvis deleted file mode 100644 index 876ebfc..0000000 --- a/tsl-sparse-map.natvis +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - {{ size={m_ht.m_nb_elements} }} - - m_ht.m_bucket_count - ((float)m_ht.m_nb_elements) / ((float)m_ht.m_bucket_count) - 0 - m_ht.m_max_load_factor - - - - - - m_ht.m_nb_elements - - - - *element - ++element - --num_elements - - - ++bucket - element = bucket->m_values - num_elements = bucket->m_nb_elements - - - - - - - - {{ size={m_ht.m_nb_elements} }} - - m_ht.m_bucket_count - ((float)m_ht.m_nb_elements) / ((float)m_ht.m_bucket_count) - 0 - m_ht.m_max_load_factor - - - - - - m_ht.m_nb_elements - - - - *element - ++element - --num_elements - - - ++bucket - element = bucket->m_values - num_elements = bucket->m_nb_elements - - - - - - - {*m_sparse_array_it} - end - - *m_sparse_array_it - - - From 96802e840c91e2440c2ab7fefdeac51ce73c6957 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Sat, 5 Aug 2023 09:27:57 +0200 Subject: [PATCH 18/41] optimize bucket array --- include/dice/sparse-map/sparse_bucket.hpp | 72 +++---- .../dice/sparse-map/sparse_bucket_array.hpp | 197 +++++++----------- include/dice/sparse-map/sparse_hash.hpp | 28 +-- include/dice/sparse-map/sparse_map.hpp | 2 +- include/dice/sparse-map/sparse_props.hpp | 3 +- include/dice/sparse-map/sparse_set.hpp | 2 +- tests/fancy_pointer/CustomAllocator.hpp | 1 + tests/fancy_pointer/sparse_array_tests.cpp | 26 ++- tests/sparse_map_tests.cpp | 48 +++-- 9 files changed, 170 insertions(+), 209 deletions(-) diff --git a/include/dice/sparse-map/sparse_bucket.hpp b/include/dice/sparse-map/sparse_bucket.hpp index 99778e5..57b8858 100644 --- a/include/dice/sparse-map/sparse_bucket.hpp +++ b/include/dice/sparse-map/sparse_bucket.hpp @@ -1,7 +1,9 @@ #ifndef DICE_SPARSE_MAP_SPARSE_BUCKET_HPP #define DICE_SPARSE_MAP_SPARSE_BUCKET_HPP -#include "dice/sparse-map/sparse_props.hpp" +#include + +#include "sparse_props.hpp" namespace dice::sparse_map::detail { @@ -127,6 +129,15 @@ namespace dice::sparse_map::detail { public: constexpr sparse_bucket() noexcept = default; + sparse_bucket(sparse_bucket const &other) = delete; + sparse_bucket(sparse_bucket &&other) = delete; + sparse_bucket &operator=(sparse_bucket const &) = delete; + sparse_bucket &operator=(sparse_bucket &&) = delete; + + // The code that manages the bucket must have called clear before + // destruction. See documentation of sparse_array_type for more details. + ~sparse_bucket() noexcept = default; + sparse_bucket(size_type capacity, allocator_type &alloc) : m_capacity{capacity} { if (m_capacity == 0) { return; @@ -136,13 +147,11 @@ namespace dice::sparse_map::detail { assert(m_values != nullptr);// allocate should throw if there is a failure } - sparse_bucket(sparse_bucket const &other) = delete; - sparse_bucket(sparse_bucket const &other, allocator_type &alloc) : m_values{nullptr}, - m_bitmap_vals{other.m_bitmap_vals}, - m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, - m_nb_elements{0}, - m_capacity{other.m_capacity} { + m_bitmap_vals{other.m_bitmap_vals}, + m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, + m_nb_elements{0}, + m_capacity{other.m_capacity} { assert(other.m_capacity >= other.m_nb_elements); if (m_capacity == 0) { @@ -162,22 +171,13 @@ namespace dice::sparse_map::detail { } } - constexpr sparse_bucket(sparse_bucket &&other) noexcept : m_values{std::exchange(other.m_values, nullptr)}, - m_bitmap_vals{std::exchange(other.m_bitmap_vals, 0)}, - m_bitmap_deleted_vals{std::exchange(other.m_bitmap_deleted_vals, 0)}, - m_nb_elements{std::exchange(other.m_nb_elements, 0)}, - m_capacity{std::exchange(other.m_capacity, 0)} { - } - - sparse_bucket(sparse_bucket &&other, [[maybe_unused]] allocator_type &alloc) noexcept requires (alloc_traits::is_always_equal::value) - : sparse_bucket{std::move(other)} { - } - - sparse_bucket(sparse_bucket &&other, allocator_type &alloc) requires (!alloc_traits::is_always_equal::value) - : m_bitmap_vals{other.m_bitmap_vals}, - m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, - m_nb_elements{0}, - m_capacity{other.m_capacity} { + sparse_bucket(sparse_bucket &&other, allocator_type &alloc) : m_bitmap_vals{other.m_bitmap_vals}, + m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, + m_nb_elements{0}, + m_capacity{other.m_capacity} { + // this ctor must only be called when the allocator is actually different + // cannot check if the allocators were actually different, but the static_assert helps + static_assert(!alloc_traits::is_always_equal::value); assert(other.m_capacity >= other.m_nb_elements); if (m_capacity == 0) { @@ -206,28 +206,10 @@ namespace dice::sparse_map::detail { throw; } } - } - - sparse_bucket &operator=(sparse_bucket const &) = delete; - - constexpr sparse_bucket &operator=(sparse_bucket &&other) noexcept { - assert(this != &other); - clear(); - this->m_values = std::exchange(other.m_values, nullptr); - this->m_bitmap_vals = std::exchange(other.m_bitmap_vals, 0); - this->m_bitmap_deleted_vals = std::exchange(other.m_bitmap_deleted_vals, 0); - this->m_nb_elements = std::exchange(other.m_nb_elements, 0); - this->m_capacity = std::exchange(other.m_capacity, 0); - - return *this; + other.clear(alloc); } - - // The code that manages the sparse_array_type must have called clear before - // destruction. See documentation of sparse_array_type for more details. - ~sparse_bucket() noexcept = default; - /** * @safety This function is only safe to call if the underlying object is non-const */ @@ -250,8 +232,12 @@ namespace dice::sparse_map::detail { [[nodiscard]] constexpr size_type size() const noexcept { return m_nb_elements; } - void clear(allocator_type &alloc) noexcept(std::is_nothrow_destructible_v) { + void destroy_deallocate(allocator_type &alloc) noexcept(std::is_nothrow_destructible_v) { destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); + } + + void clear(allocator_type &alloc) noexcept(std::is_nothrow_destructible_v) { + destroy_deallocate(alloc); m_values = nullptr; m_bitmap_vals = 0; diff --git a/include/dice/sparse-map/sparse_bucket_array.hpp b/include/dice/sparse-map/sparse_bucket_array.hpp index 96c35ca..69781d7 100644 --- a/include/dice/sparse-map/sparse_bucket_array.hpp +++ b/include/dice/sparse-map/sparse_bucket_array.hpp @@ -1,8 +1,8 @@ #ifndef DICE_SPARSE_MAP_SPARSE_BUCKET_ARRAY_HPP #define DICE_SPARSE_MAP_SPARSE_BUCKET_ARRAY_HPP -#include "dice/sparse-map/sparse_props.hpp" -#include "dice/sparse-map/sparse_bucket.hpp" +#include "sparse_props.hpp" +#include "sparse_bucket.hpp" namespace dice::sparse_map::detail { @@ -30,66 +30,86 @@ namespace dice::sparse_map::detail { private: pointer buckets_ = nullptr; size_type size_ = 0; - size_type cap_ = 0; [[no_unique_address]] bucket_allocator_type bucket_alloc_; [[no_unique_address]] element_allocator_type elem_alloc_; // this allocator lives here so that the allocator management code doesn't need to be written twice - [[nodiscard]] static constexpr size_type next_cap(size_type cap) noexcept { - return static_cast(static_cast(cap) * 1.5); + pointer make_new_buckets(size_type new_size) { + pointer new_buckets = bucket_alloc_traits::allocate(bucket_alloc_, new_size); + assert(new_buckets != nullptr); + + static_assert(std::is_nothrow_default_constructible_v); + for (size_type ix = 0; ix < new_size; ++ix) { + new (&new_buckets[ix]) bucket_type{}; + } + + return new_buckets; + } + + void resize_drop_old(size_type new_size) { + if (new_size <= size_) { + return; + } + + pointer new_buckets = make_new_buckets(new_size); + clear_deallocate(); + buckets_ = new_buckets; + size_ = new_size; } void move_buckets_from(sparse_bucket_array &&other) { - assert(size_ == 0); - reserve(other.size_); + resize_drop_old(other.size_); try { - for (auto &&bucket : other) { - emplace_back(std::move(bucket)); + for (size_type ix = 0; ix < other.size_; ++ix) { + new (&buckets_[ix]) bucket_type{std::move(other.buckets_[ix]), elem_alloc_}; } } catch (...) { - clear(); + clear_deallocate(); throw; } } void copy_buckets_from(sparse_bucket_array const &other) { - assert(size_ == 0); - reserve(other.size_); + resize_drop_old(other.size_); try { - for (auto const &bucket : other) { - emplace_back(bucket); + for (size_type ix = 0; ix < other.size_; ++ix) { + new (&buckets_[ix]) bucket_type{other.buckets_[ix], elem_alloc_}; } } catch (...) { - clear(); + clear_deallocate(); throw; } } void clear_deallocate() noexcept { - clear(); - bucket_alloc_traits::deallocate(bucket_alloc_, buckets_, cap_); + clear_buckets(); + forget_deallocate(); buckets_ = nullptr; - cap_ = 0; + size_ = 0; } public: - explicit constexpr sparse_bucket_array(element_allocator_type const &alloc) : buckets_{nullptr}, - size_{0}, - cap_{0}, - bucket_alloc_{alloc}, - elem_alloc_{alloc} { + explicit constexpr sparse_bucket_array(size_type size, element_allocator_type const &alloc) : buckets_{nullptr}, + size_{0}, + bucket_alloc_{alloc}, + elem_alloc_{alloc} { + if (size == 0) { + return; + } + + size = bucket_type::nb_sparse_buckets(size); + buckets_ = make_new_buckets(size); + size_ = size; } sparse_bucket_array(sparse_bucket_array const &other) : bucket_alloc_{bucket_alloc_traits::select_on_container_copy_construction(other.bucket_alloc_)}, elem_alloc_{element_alloc_traits::select_on_container_copy_construction(other.elem_alloc_)} { - reserve(other.size_); copy_buckets_from(other); } constexpr sparse_bucket_array(sparse_bucket_array &&other) noexcept : buckets_{std::exchange(other.buckets_, nullptr)}, size_{std::exchange(other.size_, 0)}, - cap_{other.cap_}, bucket_alloc_{std::move(other.bucket_alloc_)}, elem_alloc_{std::move(other.elem_alloc_)} { } @@ -99,23 +119,25 @@ namespace dice::sparse_map::detail { return *this; } - // no need to fully realloc, either: - // 1. alloc is not propagated - // 2. alloc is propagated but is same as this - // => need to fully realloc if propagate and not same as this + // can potentially reuse our existing buffer: + // propagate + eq => reuse buffer + // propagate + neq => cannot reuse buffer + // npropagate + eq => reuse buffer + // npropagate + neq => cannot reuse buffer - if constexpr (bucket_alloc_traits::propagate_on_container_copy_assignment::value) { - if (bucket_alloc_ != other.bucket_alloc_) { - clear_deallocate(); - bucket_alloc_ = other.bucket_alloc_; - elem_alloc_ = other.elem_alloc_; + if (bucket_alloc_traits::is_always_equal::value || bucket_alloc_ == other.bucket_alloc_) { + // allocator before and after are equal + clear_buckets(); + copy_buckets_from(other); + return *this; + } - copy_buckets_from(other); - return *this; - } + clear_deallocate(); + if constexpr (bucket_alloc_traits::propagate_on_container_copy_assignment::value) { + bucket_alloc_ = other.bucket_alloc_; + elem_alloc_ = other.elem_alloc_; } - clear(); copy_buckets_from(other); return *this; } @@ -123,77 +145,48 @@ namespace dice::sparse_map::detail { sparse_bucket_array &operator=(sparse_bucket_array &&other) noexcept { assert(this != &other); - clear_deallocate(); + // we can always steal the other array's buffer except when we are not supposed to + // propagate the allocator and they are not equal - if constexpr (!bucket_alloc_traits::propagate_on_container_move_assignment::value) { + if constexpr (!bucket_alloc_traits::propagate_on_container_move_assignment::value && !bucket_alloc_traits::is_always_equal::value) { if (bucket_alloc_ != other.bucket_alloc_) { - bucket_alloc_ = std::move(other.bucket_alloc_); - elem_alloc_ = std::move(other.elem_alloc_); - move_buckets_from(std::move(other)); return *this; } } + clear_deallocate(); buckets_ = std::exchange(other.buckets_, nullptr); size_ = std::exchange(other.size_, 0); - cap_ = std::exchange(other.cap_, 0); + bucket_alloc_ = std::move(other.bucket_alloc_); + elem_alloc_ = std::move(other.elem_alloc_); return *this; } ~sparse_bucket_array() noexcept { - clear_deallocate(); - } - - void reserve(size_type capacity) { - if (capacity <= cap_) { - return; - } - - if (capacity > max_size()) [[unlikely]] { - throw std::length_error{"maximum possible capacity exceeded"}; - } - - pointer new_data = bucket_alloc_traits::allocate(bucket_alloc_, capacity); - assert(new_data != nullptr); - for (size_type ix = 0; ix < size_; ++ix) { - new (&new_data[ix]) bucket_type{std::move(buckets_[ix])}; + buckets_[ix].destroy_deallocate(elem_alloc_); } - - static_assert(std::is_trivially_destructible_v); - bucket_alloc_traits::deallocate(bucket_alloc_, buckets_, cap_); - buckets_ = new_data; - cap_ = capacity; - } - - void fill(size_type size) { - assert(size <= cap_); - - for (size_type ix = 0; ix < size; ++ix) { - new (&buckets_[ix]) bucket_type{}; - } - - size_ = size; - } - - void resize(size_type size) { - reserve(size); - fill(size); + bucket_alloc_traits::deallocate(bucket_alloc_, buckets_, size_); } void swap(sparse_bucket_array &other) noexcept { using std::swap; + static_assert(bucket_alloc_traits::propagate_on_container_swap::value, + "Not swapping allocators is not implemented"); + swap(buckets_, other.buckets_); swap(size_, other.size_); - swap(cap_, other.cap_); + swap(bucket_alloc_, other.bucket_alloc_); + swap(elem_alloc_, other.elem_alloc_); + } - if constexpr (bucket_alloc_traits::propagate_on_container_swap::value) { - swap(bucket_alloc_, other.bucket_alloc_); - swap(elem_alloc_, other.elem_alloc_); - } + void forget_deallocate() { + bucket_alloc_traits::deallocate(bucket_alloc_, buckets_, size_); + buckets_ = nullptr; + size_ = 0; } void clear_buckets() noexcept { @@ -202,11 +195,6 @@ namespace dice::sparse_map::detail { } } - void clear() noexcept { - clear_buckets(); - size_ = 0; - } - [[nodiscard]] constexpr iterator begin() noexcept { return buckets_; } [[nodiscard]] constexpr iterator end() noexcept { return buckets_ + size_; } [[nodiscard]] constexpr const_iterator begin() const noexcept { return buckets_; } @@ -228,35 +216,6 @@ namespace dice::sparse_map::detail { return buckets_[ix]; } - template - iterator emplace_back(Args &&...args) { - if (size_ < cap_) { - new (std::to_address(buckets_ + size_)) bucket_type{std::forward(args)..., elem_alloc_}; - return buckets_ + size_++; - } - - auto const new_cap = next_cap(cap_); - pointer new_data = bucket_alloc_traits::allocate(bucket_alloc_, new_cap); - - try { - new (&new_data[size_]) bucket_type{std::forward(args)..., elem_alloc_}; - } catch (...) { - bucket_alloc_traits::deallocate(bucket_alloc_, new_data, new_cap); - throw; - } - - for (size_type ix = 0; ix < size_; ++ix) { - new (&new_data[ix]) bucket_type{std::move(buckets_[ix])}; - } - - static_assert(std::is_trivially_destructible_v); - bucket_alloc_traits::deallocate(bucket_alloc_, buckets_, cap_); - - buckets_ = new_data; - cap_ = new_cap; - return buckets_ + size_++; - } - element_allocator_type &element_allocator() noexcept { return elem_alloc_; } diff --git a/include/dice/sparse-map/sparse_hash.hpp b/include/dice/sparse-map/sparse_hash.hpp index 8bd86c1..ef00de1 100644 --- a/include/dice/sparse-map/sparse_hash.hpp +++ b/include/dice/sparse-map/sparse_hash.hpp @@ -40,9 +40,9 @@ #include #include -#include "dice/sparse-map/sparse_bucket.hpp" -#include "dice/sparse-map/sparse_growth_policy.hpp" -#include "dice/sparse-map/sparse_bucket_array.hpp" +#include "sparse_bucket.hpp" +#include "sparse_growth_policy.hpp" +#include "sparse_bucket_array.hpp" namespace dice::sparse_map::detail { template @@ -284,8 +284,10 @@ namespace dice::sparse_map::detail { } public: - sparse_hash(size_type bucket_count, Hash const &hash, KeyEqual const &equal, - allocator_type const &alloc) : buckets_{alloc}, + sparse_hash(size_type bucket_count, + hasher const &hash, + key_equal const &equal, + allocator_type const &alloc) : buckets_{bucket_count, alloc}, n_elements_{0}, n_deleted_elements_{0}, load_threshold_rehash_{calc_load_threshold_rehash(bucket_count)}, @@ -293,14 +295,6 @@ namespace dice::sparse_map::detail { h_{hash}, keq_{equal}, gpol_{bucket_count} { - if (bucket_count > max_bucket_count()) { - throw std::length_error("The map exceeds its maximum size."); - } - - if (bucket_count > 0) { - buckets_.resize(bucket_count); - assert(!buckets_.empty()); - } // Check in the constructor instead of outside of a function to avoid // compilation issues when value_type is not complete. @@ -791,8 +785,8 @@ namespace dice::sparse_map::detail { Args &&...value_type_args) { // is not called when empty auto value_it = buckets_[sparse_ibucket].set(buckets_.element_allocator(), - index_in_sparse_bucket, - std::forward(value_type_args)...); + index_in_sparse_bucket, + std::forward(value_type_args)...); n_elements_++; return std::make_pair(iterator{std::next(buckets_.begin(), sparse_ibucket), @@ -892,10 +886,10 @@ namespace dice::sparse_map::detail { new_table.insert_on_rehash(std::move(val)); } - // TODO try to reuse some of the memory - bucket.clear(buckets_.element_allocator()); + bucket.destroy_deallocate(buckets_.element_allocator()); } + buckets_.forget_deallocate(); new_table.swap(*this); } diff --git a/include/dice/sparse-map/sparse_map.hpp b/include/dice/sparse-map/sparse_map.hpp index 6166568..3efb94c 100644 --- a/include/dice/sparse-map/sparse_map.hpp +++ b/include/dice/sparse-map/sparse_map.hpp @@ -31,7 +31,7 @@ #include #include -#include "dice/sparse-map/sparse_hash.hpp" +#include "sparse_hash.hpp" namespace dice::sparse_map { diff --git a/include/dice/sparse-map/sparse_props.hpp b/include/dice/sparse-map/sparse_props.hpp index 9fd1619..80b46d9 100644 --- a/include/dice/sparse-map/sparse_props.hpp +++ b/include/dice/sparse-map/sparse_props.hpp @@ -1,9 +1,8 @@ #ifndef DICE_SPARSE_MAP_SPARSE_PROPS_HPP #define DICE_SPARSE_MAP_SPARSE_PROPS_HPP -#include -#include #include +#include namespace dice::sparse_map { enum struct probing : bool { diff --git a/include/dice/sparse-map/sparse_set.hpp b/include/dice/sparse-map/sparse_set.hpp index 0be9348..7c375e5 100644 --- a/include/dice/sparse-map/sparse_set.hpp +++ b/include/dice/sparse-map/sparse_set.hpp @@ -31,7 +31,7 @@ #include #include -#include "dice/sparse-map/sparse_hash.hpp" +#include "sparse_hash.hpp" namespace dice::sparse_map { diff --git a/tests/fancy_pointer/CustomAllocator.hpp b/tests/fancy_pointer/CustomAllocator.hpp index 4fc18e8..377844d 100644 --- a/tests/fancy_pointer/CustomAllocator.hpp +++ b/tests/fancy_pointer/CustomAllocator.hpp @@ -20,6 +20,7 @@ struct OffsetAllocator { using void_pointer = offset_ptr; using const_void_pointer = offset_ptr; using difference_type = typename offset_ptr::difference_type; + using is_always_equal = std::false_type; // pretend this isn't just stdalloc OffsetAllocator() noexcept = default; OffsetAllocator(OffsetAllocator const &) noexcept = default; diff --git a/tests/fancy_pointer/sparse_array_tests.cpp b/tests/fancy_pointer/sparse_array_tests.cpp index 6d99fde..67dcbec 100644 --- a/tests/fancy_pointer/sparse_array_tests.cpp +++ b/tests/fancy_pointer/sparse_array_tests.cpp @@ -13,12 +13,11 @@ constexpr auto MAX_INDEX = 32; //BITMAP_NB_BITS namespace details { template - typename T::Array generate_test_array(typename T::Allocator &a) { - typename T::Array arr(MAX_INDEX, a); + void generate_test_array(typename T::Array &arr, typename T::Allocator &a) { + new (&arr) typename T::Array(MAX_INDEX, a); for (std::size_t i = 0; i < MAX_INDEX; ++i) { arr.set(a, i, static_cast(i)); } - return arr; } template @@ -47,7 +46,6 @@ struct CUSTOM { using Value_Type = T; }; - #define TEST_ARRAYS STD, CUSTOM TEST_SUITE("sparse array with fancy pointers") { @@ -64,7 +62,8 @@ TEST_SUITE("sparse array with fancy pointers") { TEST_CASE_TEMPLATE("set", T, TEST_ARRAYS) { typename T::Allocator a; - auto test = details::generate_test_array(a); + typename T::Array test; + details::generate_test_array(test, a); auto check = details::generate_check_for_test_array(); //'set' did not create the correct order of items REQUIRE(std::equal(test.begin(), test.end(), check.begin())); @@ -73,8 +72,8 @@ TEST_SUITE("sparse array with fancy pointers") { TEST_CASE_TEMPLATE("copy ctor", T, TEST_ARRAYS) { typename T::Allocator a; - //needs to be its own line, otherwise the move-construction would take place - auto test = details::generate_test_array(a); + typename T::Array test; + details::generate_test_array(test, a); typename T::Array copy(test, a); auto check = details::generate_check_for_test_array(); //'copy' changed the order of the items @@ -83,11 +82,15 @@ TEST_SUITE("sparse array with fancy pointers") { copy.clear(a); } - TEST_CASE_TEMPLATE("move ctor", T, TEST_ARRAYS) { + TEST_CASE_TEMPLATE("move ctor", T, CUSTOM) { typename T::Allocator a; + typename T::Array moved_from; //two lines needed. Otherwise move/copy elision - auto moved_from = details::generate_test_array(a); - typename T::Array moved_to(std::move(moved_from)); + details::generate_test_array(moved_from, a); + + // calling ctor indended for uses when allocator differs between moved_from and moved_to + // so need to clean up moved_from afterwards + typename T::Array moved_to(std::move(moved_from), a); auto check = details::generate_check_for_test_array(); //'move' changed the order of the items REQUIRE(std::equal(moved_to.begin(), moved_to.end(), check.begin())); @@ -96,7 +99,8 @@ TEST_SUITE("sparse array with fancy pointers") { TEST_CASE_TEMPLATE("const iterator", T, TEST_ARRAYS) { typename T::Allocator a; - auto test = details::generate_test_array(a); + typename T::Array test; + details::generate_test_array(test, a); auto const_iter = test.cbegin(); //const iterator has the wrong type REQUIRE((std::is_same::value)); diff --git a/tests/sparse_map_tests.cpp b/tests/sparse_map_tests.cpp index 3cc351b..3336fcc 100644 --- a/tests/sparse_map_tests.cpp +++ b/tests/sparse_map_tests.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include "utils.h" @@ -619,40 +620,35 @@ TEST_SUITE("sparse map") { * constructor */ TEST_CASE("extreme bucket count value construction") { - CHECK_THROWS_AS( + CHECK_THROWS( (dice::sparse_map::sparse_map, std::equal_to, std::allocator>, dice::sparse_map::power_of_two_growth_policy<2>>( - std::numeric_limits::max())), - std::length_error); + std::numeric_limits::max()))); - CHECK_THROWS_AS( + CHECK_THROWS( (dice::sparse_map::sparse_map, std::equal_to, std::allocator>, dice::sparse_map::power_of_two_growth_policy<2>>( - std::numeric_limits::max() / 2 + 1)), - std::length_error); + std::numeric_limits::max() / 2 + 1))); - CHECK_THROWS_AS( + CHECK_THROWS( (dice::sparse_map::sparse_map, std::equal_to, std::allocator>, dice::sparse_map::prime_growth_policy>( - std::numeric_limits::max())), - std::length_error); + std::numeric_limits::max()))); - CHECK_THROWS_AS( + CHECK_THROWS( (dice::sparse_map::sparse_map, std::equal_to, std::allocator>, dice::sparse_map::prime_growth_policy>( - std::numeric_limits::max() / 2)), - std::length_error); + std::numeric_limits::max() / 2))); - CHECK_THROWS_AS( + CHECK_THROWS( (dice::sparse_map::sparse_map, std::equal_to, std::allocator>, dice::sparse_map::mod_growth_policy<>>( - std::numeric_limits::max())), - std::length_error); + std::numeric_limits::max()))); } TEST_CASE("range construct") { @@ -1246,4 +1242,26 @@ TEST_SUITE("sparse map") { */ CHECK_EQ(map.erase(3, map.hash_function()(3)), 1); } + + TEST_CASE("insert iterate then remove 100M ints") { + dice::sparse_map::sparse_map m; + std::default_random_engine rng{std::random_device{}()}; + + for (size_t ix = 0; ix < 100'000'000; ++ix) { + (void) m[static_cast(rng())]; + } + + std::cout << "map size: " << m.size() << std::endl; + + /*size_t sum = 0; + for (auto it = m.begin(); it != m.end(); ++it) { + sum += it->second; + } + std::cout << sum << std::endl; + */ + + /*for (auto it = m.begin(); it != m.end(); ) { + it = m.erase(it); + }*/ + } } From f082ce5093251b989d3269ab39c4683eaafee1a5 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Sat, 5 Aug 2023 09:29:11 +0200 Subject: [PATCH 19/41] exclude .idea folder --- .gitignore | 64 +----------------------------------------------------- 1 file changed, 1 insertion(+), 63 deletions(-) diff --git a/.gitignore b/.gitignore index df56dfa..f2e7452 100644 --- a/.gitignore +++ b/.gitignore @@ -80,88 +80,26 @@ environment_run.sh.env # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr +.idea/ # CMake cmake-build-*/ -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - # File-based project format *.iws # IntelliJ out/ -# mpeltonen/sbt-idea plugin -.idea_modules/ - # JIRA plugin atlassian-ide-plugin.xml -# Cursive Clojure plugin -.idea/replstate.xml - -# SonarLint plugin -.idea/sonarlint/ - # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -### Intellij+all Patch ### -# Ignore everything but code style settings and run configurations -# that are supposed to be shared within teams. - -.idea/* - -!.idea/codeStyles -!.idea/runConfigurations ### Ninja ### .ninja_deps From 3f30390db6f800f1ec5363e0149c6a5ce4762a93 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Sat, 5 Aug 2023 10:35:46 +0200 Subject: [PATCH 20/41] rework folder structure --- .../internal}/sparse_bucket.hpp | 9 +++++++-- .../internal}/sparse_bucket_array.hpp | 2 +- .../internal}/sparse_hash.hpp | 6 +++--- .../sparse_growth_policy.hpp | 0 .../{sparse-map => sparse_map}/sparse_map.hpp | 2 +- .../{sparse-map => sparse_map}/sparse_props.hpp | 0 .../{sparse-map => sparse_map}/sparse_set.hpp | 2 +- tests/custom_allocator_tests.cpp | 2 +- tests/fancy_pointer/sparse_array_tests.cpp | 2 +- tests/fancy_pointer/sparse_hash_map_tests.cpp | 4 ++-- tests/fancy_pointer/sparse_hash_set_tests.cpp | 4 ++-- tests/policy_tests.cpp | 2 +- .../sparse_hash_set_tests.cpp | 2 +- tests/sparse_map_tests.cpp | 17 ++++++++--------- tests/sparse_set_tests.cpp | 11 ++++++++--- 15 files changed, 37 insertions(+), 28 deletions(-) rename include/dice/{sparse-map => sparse_map/internal}/sparse_bucket.hpp (99%) rename include/dice/{sparse-map => sparse_map/internal}/sparse_bucket_array.hpp (99%) rename include/dice/{sparse-map => sparse_map/internal}/sparse_hash.hpp (99%) rename include/dice/{sparse-map => sparse_map}/sparse_growth_policy.hpp (100%) rename include/dice/{sparse-map => sparse_map}/sparse_map.hpp (99%) rename include/dice/{sparse-map => sparse_map}/sparse_props.hpp (100%) rename include/dice/{sparse-map => sparse_map}/sparse_set.hpp (99%) diff --git a/include/dice/sparse-map/sparse_bucket.hpp b/include/dice/sparse_map/internal/sparse_bucket.hpp similarity index 99% rename from include/dice/sparse-map/sparse_bucket.hpp rename to include/dice/sparse_map/internal/sparse_bucket.hpp index 57b8858..1745e5a 100644 --- a/include/dice/sparse-map/sparse_bucket.hpp +++ b/include/dice/sparse_map/internal/sparse_bucket.hpp @@ -1,14 +1,19 @@ #ifndef DICE_SPARSE_MAP_SPARSE_BUCKET_HPP #define DICE_SPARSE_MAP_SPARSE_BUCKET_HPP +#include +#include +#include #include +#include +#include -#include "sparse_props.hpp" +#include "../sparse_props.hpp" namespace dice::sparse_map::detail { /** - * WARNING: the sparse_array_type class doesn't free the resources allocated through + * WARNING: the sparse_bucket class doesn't free the resources allocated through * the allocator passed in parameter in each method. You have to manually call * `clear(Allocator&)` when you don't need a sparse_array_type object anymore. * diff --git a/include/dice/sparse-map/sparse_bucket_array.hpp b/include/dice/sparse_map/internal/sparse_bucket_array.hpp similarity index 99% rename from include/dice/sparse-map/sparse_bucket_array.hpp rename to include/dice/sparse_map/internal/sparse_bucket_array.hpp index 69781d7..1547209 100644 --- a/include/dice/sparse-map/sparse_bucket_array.hpp +++ b/include/dice/sparse_map/internal/sparse_bucket_array.hpp @@ -1,7 +1,7 @@ #ifndef DICE_SPARSE_MAP_SPARSE_BUCKET_ARRAY_HPP #define DICE_SPARSE_MAP_SPARSE_BUCKET_ARRAY_HPP -#include "sparse_props.hpp" +#include "../sparse_props.hpp" #include "sparse_bucket.hpp" namespace dice::sparse_map::detail { diff --git a/include/dice/sparse-map/sparse_hash.hpp b/include/dice/sparse_map/internal/sparse_hash.hpp similarity index 99% rename from include/dice/sparse-map/sparse_hash.hpp rename to include/dice/sparse_map/internal/sparse_hash.hpp index ef00de1..0bc5c52 100644 --- a/include/dice/sparse-map/sparse_hash.hpp +++ b/include/dice/sparse_map/internal/sparse_hash.hpp @@ -41,8 +41,8 @@ #include #include "sparse_bucket.hpp" -#include "sparse_growth_policy.hpp" #include "sparse_bucket_array.hpp" +#include "../sparse_growth_policy.hpp" namespace dice::sparse_map::detail { template @@ -740,8 +740,8 @@ namespace dice::sparse_map::detail { std::size_t probe = 0; while (true) { - std::size_t sparse_ibucket = sparse_bucket_type::sparse_ibucket(ibucket); - auto index_in_sparse_bucket = sparse_bucket_type::index_in_sparse_bucket(ibucket); + auto const sparse_ibucket = sparse_bucket_type::sparse_ibucket(ibucket); + auto const index_in_sparse_bucket = sparse_bucket_type::index_in_sparse_bucket(ibucket); if (!buckets_.empty()) { if (buckets_[sparse_ibucket].has_value(index_in_sparse_bucket)) { diff --git a/include/dice/sparse-map/sparse_growth_policy.hpp b/include/dice/sparse_map/sparse_growth_policy.hpp similarity index 100% rename from include/dice/sparse-map/sparse_growth_policy.hpp rename to include/dice/sparse_map/sparse_growth_policy.hpp diff --git a/include/dice/sparse-map/sparse_map.hpp b/include/dice/sparse_map/sparse_map.hpp similarity index 99% rename from include/dice/sparse-map/sparse_map.hpp rename to include/dice/sparse_map/sparse_map.hpp index 3efb94c..58815f8 100644 --- a/include/dice/sparse-map/sparse_map.hpp +++ b/include/dice/sparse_map/sparse_map.hpp @@ -31,7 +31,7 @@ #include #include -#include "sparse_hash.hpp" +#include "internal/sparse_hash.hpp" namespace dice::sparse_map { diff --git a/include/dice/sparse-map/sparse_props.hpp b/include/dice/sparse_map/sparse_props.hpp similarity index 100% rename from include/dice/sparse-map/sparse_props.hpp rename to include/dice/sparse_map/sparse_props.hpp diff --git a/include/dice/sparse-map/sparse_set.hpp b/include/dice/sparse_map/sparse_set.hpp similarity index 99% rename from include/dice/sparse-map/sparse_set.hpp rename to include/dice/sparse_map/sparse_set.hpp index 7c375e5..8562761 100644 --- a/include/dice/sparse-map/sparse_set.hpp +++ b/include/dice/sparse_map/sparse_set.hpp @@ -31,7 +31,7 @@ #include #include -#include "sparse_hash.hpp" +#include "internal/sparse_hash.hpp" namespace dice::sparse_map { diff --git a/tests/custom_allocator_tests.cpp b/tests/custom_allocator_tests.cpp index ec89b66..99c0e01 100644 --- a/tests/custom_allocator_tests.cpp +++ b/tests/custom_allocator_tests.cpp @@ -24,7 +24,7 @@ #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include -#include +#include #include #include #include diff --git a/tests/fancy_pointer/sparse_array_tests.cpp b/tests/fancy_pointer/sparse_array_tests.cpp index 67dcbec..5d8a6cd 100644 --- a/tests/fancy_pointer/sparse_array_tests.cpp +++ b/tests/fancy_pointer/sparse_array_tests.cpp @@ -5,7 +5,7 @@ #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include -#include +#include #include "CustomAllocator.hpp" // Globals diff --git a/tests/fancy_pointer/sparse_hash_map_tests.cpp b/tests/fancy_pointer/sparse_hash_map_tests.cpp index 1b8a2bb..f341536 100644 --- a/tests/fancy_pointer/sparse_hash_map_tests.cpp +++ b/tests/fancy_pointer/sparse_hash_map_tests.cpp @@ -6,8 +6,8 @@ #include #include -#include -#include +#include +#include #include "CustomAllocator.hpp" /* Tests are analogous to the tests in sparse_array_tests.cpp. diff --git a/tests/fancy_pointer/sparse_hash_set_tests.cpp b/tests/fancy_pointer/sparse_hash_set_tests.cpp index 28cc09f..ff6368e 100644 --- a/tests/fancy_pointer/sparse_hash_set_tests.cpp +++ b/tests/fancy_pointer/sparse_hash_set_tests.cpp @@ -5,8 +5,8 @@ #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include -#include -#include +#include +#include #include "CustomAllocator.hpp" /* Tests are analogous to the tests in sparse_array_tests.cpp. diff --git a/tests/policy_tests.cpp b/tests/policy_tests.cpp index 74947b0..fc7acde 100644 --- a/tests/policy_tests.cpp +++ b/tests/policy_tests.cpp @@ -24,7 +24,7 @@ #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include -#include +#include #include #include diff --git a/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp b/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp index 82e9aaf..76869f0 100644 --- a/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp +++ b/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp @@ -1,7 +1,7 @@ #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include -#include +#include #include namespace details { diff --git a/tests/sparse_map_tests.cpp b/tests/sparse_map_tests.cpp index 3336fcc..c5092dd 100644 --- a/tests/sparse_map_tests.cpp +++ b/tests/sparse_map_tests.cpp @@ -24,7 +24,7 @@ #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include -#include +#include #include #include @@ -1243,25 +1243,24 @@ TEST_SUITE("sparse map") { CHECK_EQ(map.erase(3, map.hash_function()(3)), 1); } - TEST_CASE("insert iterate then remove 100M ints") { + TEST_CASE("insert iterate then remove 10M ints") { dice::sparse_map::sparse_map m; std::default_random_engine rng{std::random_device{}()}; - for (size_t ix = 0; ix < 100'000'000; ++ix) { + for (size_t ix = 0; ix < 10'000'000; ++ix) { (void) m[static_cast(rng())]; } std::cout << "map size: " << m.size() << std::endl; - /*size_t sum = 0; - for (auto it = m.begin(); it != m.end(); ++it) { - sum += it->second; + size_t sum = 0; + for (auto [x, _] : m) { + sum += x; } std::cout << sum << std::endl; - */ - /*for (auto it = m.begin(); it != m.end(); ) { + for (auto it = m.begin(); it != m.end(); ) { it = m.erase(it); - }*/ + } } } diff --git a/tests/sparse_set_tests.cpp b/tests/sparse_set_tests.cpp index 487d76d..638d5e9 100644 --- a/tests/sparse_set_tests.cpp +++ b/tests/sparse_set_tests.cpp @@ -24,7 +24,7 @@ #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include -#include +#include #include #include @@ -52,7 +52,11 @@ dice::sparse_map::mod_growth_policy<>> TEST_SUITE("sparse set") { - TEST_CASE_TEMPLATE("insert", HSet, TEST_SETS) { + TEST_CASE_TEMPLATE("insert", HSet, dice::sparse_map::sparse_set, + std::equal_to, + std::allocator, + dice::sparse_map::mod_growth_policy<>>) { // insert x values, insert them again, check values using key_t = typename HSet::key_type; @@ -60,7 +64,8 @@ TEST_SUITE("sparse set") { HSet set; for (std::size_t i = 0; i < nb_values; i++) { - auto [it, inserted] = set.insert(utils::get_key(i)); + auto k = utils::get_key(i); + auto [it, inserted] = set.insert(std::move(k)); CHECK_EQ(*it, utils::get_key(i)); CHECK(inserted); From 0d3cab7652e198dc28d5be9d10b3ec448a1d89ca Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Mon, 7 Aug 2023 07:53:39 +0200 Subject: [PATCH 21/41] fix bugs --- .../internal/sparse_bucket_array.hpp | 4 ++++ .../dice/sparse_map/internal/sparse_hash.hpp | 22 +++++++++++-------- include/dice/sparse_map/sparse_map.hpp | 4 ++-- tests/sparse_set_tests.cpp | 6 +---- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/include/dice/sparse_map/internal/sparse_bucket_array.hpp b/include/dice/sparse_map/internal/sparse_bucket_array.hpp index 1547209..bb1f6a3 100644 --- a/include/dice/sparse_map/internal/sparse_bucket_array.hpp +++ b/include/dice/sparse_map/internal/sparse_bucket_array.hpp @@ -94,6 +94,10 @@ namespace dice::sparse_map::detail { size_{0}, bucket_alloc_{alloc}, elem_alloc_{alloc} { + if (size > max_size()) [[unlikely]] { + throw std::length_error{"Maximum sparse_bucket_array length exceeded"}; + } + if (size == 0) { return; } diff --git a/include/dice/sparse_map/internal/sparse_hash.hpp b/include/dice/sparse_map/internal/sparse_hash.hpp index 0bc5c52..6ad04e7 100644 --- a/include/dice/sparse_map/internal/sparse_hash.hpp +++ b/include/dice/sparse_map/internal/sparse_hash.hpp @@ -157,6 +157,7 @@ namespace dice::sparse_map::detail { private: sparse_bucket_array_type buckets_; + size_type bucket_count_; size_type n_elements_; size_type n_deleted_elements_; @@ -288,6 +289,7 @@ namespace dice::sparse_map::detail { hasher const &hash, key_equal const &equal, allocator_type const &alloc) : buckets_{bucket_count, alloc}, + bucket_count_{bucket_count}, n_elements_{0}, n_deleted_elements_{0}, load_threshold_rehash_{calc_load_threshold_rehash(bucket_count)}, @@ -312,6 +314,7 @@ namespace dice::sparse_map::detail { && std::is_nothrow_move_constructible_v && std::is_nothrow_move_constructible_v) : buckets_{std::move(other.buckets_)}, + bucket_count_{std::exchange(other.bucket_count_, 0)}, n_elements_{std::exchange(other.n_elements_, 0)}, n_deleted_elements_{std::exchange(other.n_deleted_elements_, 0)}, load_threshold_rehash_{std::exchange(other.load_threshold_rehash_, 0)}, @@ -326,6 +329,7 @@ namespace dice::sparse_map::detail { assert(this != &other); buckets_ = std::move(other.buckets_); + bucket_count_ = std::exchange(other.bucket_count_, 0); n_elements_ = std::exchange(other.n_elements_, 0); n_deleted_elements_ = std::exchange(other.n_deleted_elements_, 0); load_threshold_rehash_ = std::exchange(other.load_threshold_rehash_, 0); @@ -412,8 +416,7 @@ namespace dice::sparse_map::detail { template iterator insert_hint(const_iterator hint, P &&value) { - if (hint != cend() && - keq_(KeyValueSelect::key(*hint), KeyValueSelect::key(value))) { + if (hint != cend() && keq_(KeyValueSelect::key(*hint), KeyValueSelect::key(value))) { return mutable_iterator(hint); } @@ -557,6 +560,7 @@ namespace dice::sparse_map::detail { using std::swap; swap(buckets_, other.buckets_); + swap(bucket_count_, other.bucket_count_); swap(n_elements_, other.n_elements_); swap(n_deleted_elements_, other.n_deleted_elements_); swap(load_threshold_rehash_, other.load_threshold_rehash_); @@ -658,7 +662,7 @@ namespace dice::sparse_map::detail { return std::make_pair(it, (it == cend()) ? it : std::next(it)); } - size_type bucket_count() const { return buckets_.size(); } + size_type bucket_count() const { return bucket_count_; } size_type max_bucket_count() const { return buckets_.max_size(); @@ -705,11 +709,11 @@ namespace dice::sparse_map::detail { size_type next_bucket(size_type ibucket, [[maybe_unused]] size_type iprobe) const requires (!is_power_of_two_policy::value) { if constexpr (Probing == probing::linear) { ibucket++; - return (ibucket != bucket_count()) ? ibucket : 0; + return ibucket != bucket_count_ ? ibucket : 0; } else { assert(Probing == probing::quadratic); ibucket += iprobe; - return (ibucket < bucket_count()) ? ibucket : ibucket % bucket_count(); + return ibucket < bucket_count_ ? ibucket : ibucket % bucket_count_; } } @@ -752,7 +756,7 @@ namespace dice::sparse_map::detail { value_it}, false); } - } else if (buckets_[sparse_ibucket].has_deleted_value(index_in_sparse_bucket) && probe < buckets_.size()) { + } else if (buckets_[sparse_ibucket].has_deleted_value(index_in_sparse_bucket) && probe < bucket_count_) { if (!found_first_deleted_bucket) { found_first_deleted_bucket = true; sparse_ibucket_first_deleted = sparse_ibucket; @@ -820,7 +824,7 @@ namespace dice::sparse_map::detail { return 1; } - } else if (!bucket.has_deleted_value(index_in_sparse_bucket) || probe >= buckets_.size()) { + } else if (!bucket.has_deleted_value(index_in_sparse_bucket) || probe >= bucket_count_) { return 0; } @@ -853,7 +857,7 @@ namespace dice::sparse_map::detail { self.buckets_.end(), value_it}; } - } else if (!bucket.has_deleted_value(index_in_sparse_bucket) || probe >= self.buckets_.size()) { + } else if (!bucket.has_deleted_value(index_in_sparse_bucket) || probe >= self.bucket_count_) { return self.end(); } @@ -874,7 +878,7 @@ namespace dice::sparse_map::detail { void clear_deleted_buckets() { // TODO could be optimized, we could do it in-place instead of allocating a // new bucket array. - rehash_impl(buckets_.size()); + rehash_impl(bucket_count_); assert(n_deleted_elements_ == 0); } diff --git a/include/dice/sparse_map/sparse_map.hpp b/include/dice/sparse_map/sparse_map.hpp index 58815f8..73314f9 100644 --- a/include/dice/sparse_map/sparse_map.hpp +++ b/include/dice/sparse_map/sparse_map.hpp @@ -423,8 +423,8 @@ namespace dice::sparse_map { return m_ht.at(key, precalculated_hash); } - [[nodiscard]] T &operator[](const Key &key) { return m_ht[key]; } - [[nodiscard]] T &operator[](Key &&key) { return m_ht[std::move(key)]; } + T &operator[](const Key &key) { return m_ht[key]; } + T &operator[](Key &&key) { return m_ht[std::move(key)]; } [[nodiscard]] size_type count(const Key &key) const { return m_ht.count(key); } diff --git a/tests/sparse_set_tests.cpp b/tests/sparse_set_tests.cpp index 638d5e9..5a51c9a 100644 --- a/tests/sparse_set_tests.cpp +++ b/tests/sparse_set_tests.cpp @@ -52,11 +52,7 @@ dice::sparse_map::mod_growth_policy<>> TEST_SUITE("sparse set") { - TEST_CASE_TEMPLATE("insert", HSet, dice::sparse_map::sparse_set, - std::equal_to, - std::allocator, - dice::sparse_map::mod_growth_policy<>>) { + TEST_CASE_TEMPLATE("insert", HSet, TEST_SETS) { // insert x values, insert them again, check values using key_t = typename HSet::key_type; From 6eeb9d2c78d3783168a071a48700fb0943fd88ca Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Mon, 7 Aug 2023 09:10:42 +0200 Subject: [PATCH 22/41] prettify --- .../sparse_map/internal/sparse_bucket.hpp | 308 ++++++------- .../internal/sparse_bucket_array.hpp | 12 +- .../dice/sparse_map/internal/sparse_hash.hpp | 247 +++++------ include/dice/sparse_map/sparse_map.hpp | 409 +++++++++--------- include/dice/sparse_map/sparse_set.hpp | 323 +++++++------- tests/fancy_pointer/sparse_array_tests.cpp | 6 +- tests/fancy_pointer/sparse_hash_map_tests.cpp | 2 +- tests/fancy_pointer/sparse_hash_set_tests.cpp | 2 +- .../sparse_hash_set_tests.cpp | 2 +- tests/sparse_map_tests.cpp | 55 ++- 10 files changed, 696 insertions(+), 670 deletions(-) diff --git a/include/dice/sparse_map/internal/sparse_bucket.hpp b/include/dice/sparse_map/internal/sparse_bucket.hpp index 1745e5a..a0d5b22 100644 --- a/include/dice/sparse_map/internal/sparse_bucket.hpp +++ b/include/dice/sparse_map/internal/sparse_bucket.hpp @@ -10,7 +10,7 @@ #include "../sparse_props.hpp" -namespace dice::sparse_map::detail { +namespace dice::sparse_map::internal { /** * WARNING: the sparse_bucket class doesn't free the resources allocated through @@ -23,13 +23,13 @@ namespace dice::sparse_map::detail { * * * - * Index denotes a value between [0, BITMAP_NB_BITS), it is an index similar to - * std::vector. Offset denotes the real position in `m_values` corresponding to + * Index denotes a value between [0, discriminant_bits), it is an index similar to + * std::vector. Offset denotes the real position in `values_` corresponding to * an index. * * We are using raw pointers instead of std::vector to avoid loosing * 2*sizeof(size_t) bytes to store the capacity and size of the vector in each - * sparse_array_type. We know we can only store up to BITMAP_NB_BITS elements in the + * sparse_array_type. We know we can only store up to discriminant_bits elements in the * array, we don't need such big types. * * @@ -53,7 +53,7 @@ namespace dice::sparse_map::detail { using iterator = pointer; using const_iterator = const_pointer; - static constexpr size_type CAPACITY_GROWTH_STEP = []() { + static constexpr size_type capacity_growth_step = []() { switch (Sparsity) { case sparsity::high: return 2; case sparsity::medium: return 4; @@ -61,32 +61,32 @@ namespace dice::sparse_map::detail { } }(); - using bitmap_type = std::uint_least64_t; - static constexpr std::size_t BITMAP_NB_BITS = 64; - static constexpr std::size_t BUCKET_SHIFT = 6; + using discriminant_type = std::uint_least64_t; + static constexpr std::size_t discriminant_bits = 64; - static constexpr std::size_t BUCKET_MASK = BITMAP_NB_BITS - 1; + static constexpr std::size_t bucket_shift = 6; + static constexpr std::size_t bucket_mask = discriminant_bits - 1; - static_assert(std::has_single_bit(BITMAP_NB_BITS) == 1, - "BITMAP_NB_BITS must be a power of two."); - static_assert(std::numeric_limits::digits >= BITMAP_NB_BITS, - "bitmap_type must be able to hold at least BITMAP_NB_BITS."); - static_assert((std::size_t(1) << BUCKET_SHIFT) == BITMAP_NB_BITS, - "(1 << BUCKET_SHIFT) must be equal to BITMAP_NB_BITS."); - static_assert(std::numeric_limits::max() >= BITMAP_NB_BITS, - "size_type must be big enough to hold BITMAP_NB_BITS."); - static_assert(std::is_unsigned::value, - "bitmap_type must be unsigned."); - static_assert((std::numeric_limits::max() & BUCKET_MASK) == BITMAP_NB_BITS - 1); + static_assert(std::has_single_bit(discriminant_bits), + "discriminant_bits must be a power of two."); + static_assert(std::numeric_limits::digits >= discriminant_bits, + "discriminant_type must be able to hold at least discriminant_bits."); + static_assert((std::size_t(1) << bucket_shift) == discriminant_bits, + "(1 << bucket_shift) must be equal to discriminant_bits."); + static_assert(std::numeric_limits::max() >= discriminant_bits, + "size_type must be big enough to hold discriminant_bits."); + static_assert(std::is_unsigned::value, + "discriminant_type must be unsigned."); + static_assert((std::numeric_limits::max() & bucket_mask) == discriminant_bits - 1); private: - pointer m_values = nullptr; + pointer values_ = nullptr; - bitmap_type m_bitmap_vals = 0; - bitmap_type m_bitmap_deleted_vals = 0; + discriminant_type value_discriminant_ = 0; + discriminant_type deleted_discriminant_ = 0; - size_type m_nb_elements = 0; - size_type m_capacity = 0; + size_type size_ = 0; + size_type capacity_ = 0; public: /** @@ -98,8 +98,8 @@ namespace dice::sparse_map::detail { * m_sparse_buckets[sparse_ibucket(ibucket)][index_in_sparse_bucket(ibucket)] * instead of something like m_buckets[ibucket] in a classical hash table. */ - static constexpr std::size_t sparse_ibucket(std::size_t ibucket) noexcept { - return ibucket >> BUCKET_SHIFT; + [[nodiscard]] static constexpr std::size_t sparse_ibucket(std::size_t ibucket) noexcept { + return ibucket >> bucket_shift; } /** @@ -110,11 +110,11 @@ namespace dice::sparse_map::detail { * m_sparse_buckets[sparse_ibucket(ibucket)][index_in_sparse_bucket(ibucket)] * instead of something like m_buckets[ibucket] in a classical hash table. */ - static constexpr size_type index_in_sparse_bucket(std::size_t ibucket) noexcept { - return static_cast(ibucket & BUCKET_MASK); + [[nodiscard]] static constexpr size_type index_in_sparse_bucket(std::size_t ibucket) noexcept { + return static_cast(ibucket & bucket_mask); } - static constexpr std::size_t nb_sparse_buckets(std::size_t bucket_count) noexcept { + [[nodiscard]] static constexpr std::size_t nb_sparse_buckets(std::size_t bucket_count) noexcept { if (bucket_count == 0) { return 0; } @@ -143,32 +143,32 @@ namespace dice::sparse_map::detail { // destruction. See documentation of sparse_array_type for more details. ~sparse_bucket() noexcept = default; - sparse_bucket(size_type capacity, allocator_type &alloc) : m_capacity{capacity} { - if (m_capacity == 0) { + sparse_bucket(size_type capacity, allocator_type &alloc) : capacity_{capacity} { + if (capacity_ == 0) { return; } - m_values = alloc_traits::allocate(alloc, m_capacity); - assert(m_values != nullptr);// allocate should throw if there is a failure + values_ = alloc_traits::allocate(alloc, capacity_); + assert(values_ != nullptr);// allocate should throw if there is a failure } - sparse_bucket(sparse_bucket const &other, allocator_type &alloc) : m_values{nullptr}, - m_bitmap_vals{other.m_bitmap_vals}, - m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, - m_nb_elements{0}, - m_capacity{other.m_capacity} { + sparse_bucket(sparse_bucket const &other, allocator_type &alloc) : values_{nullptr}, + value_discriminant_{other.value_discriminant_}, + deleted_discriminant_{other.deleted_discriminant_}, + size_{0}, + capacity_{other.capacity_} { - assert(other.m_capacity >= other.m_nb_elements); - if (m_capacity == 0) { + assert(other.capacity_ >= other.size_); + if (capacity_ == 0) { return; } - m_values = alloc_traits::allocate(alloc, m_capacity); - assert(m_values != nullptr);// allocate should throw if there is a failure + values_ = alloc_traits::allocate(alloc, capacity_); + assert(values_ != nullptr);// allocate should throw if there is a failure try { - for (; m_nb_elements < other.m_nb_elements; ++m_nb_elements) { - construct_at(alloc, m_values + m_nb_elements, other.m_values[m_nb_elements]); + for (; size_ < other.size_; ++size_) { + construct_at(alloc, values_ + size_, other.values_[size_]); } } catch (...) { clear(alloc); @@ -176,35 +176,35 @@ namespace dice::sparse_map::detail { } } - sparse_bucket(sparse_bucket &&other, allocator_type &alloc) : m_bitmap_vals{other.m_bitmap_vals}, - m_bitmap_deleted_vals{other.m_bitmap_deleted_vals}, - m_nb_elements{0}, - m_capacity{other.m_capacity} { + sparse_bucket(sparse_bucket &&other, allocator_type &alloc) : value_discriminant_{other.value_discriminant_}, + deleted_discriminant_{other.deleted_discriminant_}, + size_{0}, + capacity_{other.capacity_} { // this ctor must only be called when the allocator is actually different // cannot check if the allocators were actually different, but the static_assert helps static_assert(!alloc_traits::is_always_equal::value); - assert(other.m_capacity >= other.m_nb_elements); - if (m_capacity == 0) { + assert(other.capacity_ >= other.size_); + if (capacity_ == 0) { return; } - m_values = alloc_traits::allocate(alloc, m_capacity); - assert(m_values != nullptr); // allocate should throw if there is a failure + values_ = alloc_traits::allocate(alloc, capacity_); + assert(values_ != nullptr); // allocate should throw if there is a failure if constexpr (std::is_trivially_copyable_v) { - std::memcpy(&m_values[0], &other.m_values[0], other.m_nb_elements * sizeof(value_type)); - m_nb_elements = other.m_nb_elements; + std::memcpy(&values_[0], &other.values_[0], other.size_ * sizeof(value_type)); + size_ = other.size_; } else if constexpr (std::is_nothrow_move_constructible_v) { - for (size_type i = 0; i < other.m_nb_elements; i++) { - construct_at(alloc, &m_values[i], std::move(other.m_values[i])); + for (size_type i = 0; i < other.size_; i++) { + construct_at(alloc, &values_[i], std::move(other.values_[i])); } - m_nb_elements = other.m_nb_elements; + size_ = other.size_; } else { try { - for (; m_nb_elements < other.m_nb_elements; ++m_nb_elements) { - construct_at(alloc, &m_values[m_nb_elements], std::move(other.m_values[m_nb_elements])); + for (; size_ < other.size_; ++size_) { + construct_at(alloc, &values_[size_], std::move(other.values_[size_])); } } catch (...) { clear(alloc); @@ -226,49 +226,49 @@ namespace dice::sparse_map::detail { } } - [[nodiscard]] constexpr iterator begin() noexcept { return m_values; } - [[nodiscard]] constexpr iterator end() noexcept { return m_values + m_nb_elements; } + [[nodiscard]] constexpr iterator begin() noexcept { return values_; } + [[nodiscard]] constexpr iterator end() noexcept { return values_ + size_; } [[nodiscard]] constexpr const_iterator begin() const noexcept { return cbegin(); } [[nodiscard]] constexpr const_iterator end() const noexcept { return cend(); } - [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return m_values; } - [[nodiscard]] constexpr const_iterator cend() const noexcept { return m_values + m_nb_elements; } + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return values_; } + [[nodiscard]] constexpr const_iterator cend() const noexcept { return values_ + size_; } - [[nodiscard]] constexpr bool empty() const noexcept { return m_nb_elements == 0; } + [[nodiscard]] constexpr bool empty() const noexcept { return size_ == 0; } - [[nodiscard]] constexpr size_type size() const noexcept { return m_nb_elements; } + [[nodiscard]] constexpr size_type size() const noexcept { return size_; } void destroy_deallocate(allocator_type &alloc) noexcept(std::is_nothrow_destructible_v) { - destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); + destroy_and_deallocate_values(alloc, values_, size_, capacity_); } void clear(allocator_type &alloc) noexcept(std::is_nothrow_destructible_v) { destroy_deallocate(alloc); - m_values = nullptr; - m_bitmap_vals = 0; - m_bitmap_deleted_vals = 0; - m_nb_elements = 0; - m_capacity = 0; + values_ = nullptr; + value_discriminant_ = 0; + deleted_discriminant_ = 0; + size_ = 0; + capacity_ = 0; } [[nodiscard]] constexpr bool has_value(size_type index) const noexcept { - assert(index < BITMAP_NB_BITS); - return (m_bitmap_vals & (bitmap_type{1} << index)) != 0; + assert(index < discriminant_bits); + return (value_discriminant_ & (discriminant_type{1} << index)) != 0; } [[nodiscard]] constexpr bool has_deleted_value(size_type index) const noexcept { - assert(index < BITMAP_NB_BITS); - return (m_bitmap_deleted_vals & (bitmap_type{1} << index)) != 0; + assert(index < discriminant_bits); + return (deleted_discriminant_ & (discriminant_type{1} << index)) != 0; } - iterator value(size_type index) noexcept { + [[nodiscard]] iterator value(size_type index) noexcept { assert(has_value(index)); - return m_values + index_to_offset(index); + return values_ + index_to_offset(index); } - const_iterator value(size_type index) const noexcept { + [[nodiscard]] const_iterator value(size_type index) const noexcept { assert(has_value(index)); - return m_values + index_to_offset(index); + return values_ + index_to_offset(index); } /** @@ -281,15 +281,15 @@ namespace dice::sparse_map::detail { const size_type offset = index_to_offset(index); insert_at_offset(alloc, offset, std::forward(value_args)...); - m_bitmap_vals |= bitmap_type{1} << index; - m_bitmap_deleted_vals &= ~(bitmap_type{1} << index); + value_discriminant_ |= discriminant_type{1} << index; + deleted_discriminant_ &= ~(discriminant_type{1} << index); - m_nb_elements += 1; + size_ += 1; assert(has_value(index)); assert(!has_deleted_value(index)); - return m_values + offset; + return values_ + offset; } iterator erase(allocator_type &alloc, iterator position) { @@ -305,25 +305,25 @@ namespace dice::sparse_map::detail { auto const offset = static_cast(std::distance(begin(), position)); erase_at_offset(alloc, offset); - m_bitmap_vals &= ~(bitmap_type{1} << index); - m_bitmap_deleted_vals |= bitmap_type{1} << index; + value_discriminant_ &= ~(discriminant_type{1} << index); + deleted_discriminant_ |= discriminant_type{1} << index; - m_nb_elements -= 1; + size_ -= 1; assert(!has_value(index)); assert(has_deleted_value(index)); - return m_values + offset; + return values_ + offset; } void swap(sparse_bucket &other) noexcept { using std::swap; - swap(m_values, other.m_values); - swap(m_bitmap_vals, other.m_bitmap_vals); - swap(m_bitmap_deleted_vals, other.m_bitmap_deleted_vals); - swap(m_nb_elements, other.m_nb_elements); - swap(m_capacity, other.m_capacity); + swap(values_, other.values_); + swap(value_discriminant_, other.value_discriminant_); + swap(deleted_discriminant_, other.deleted_discriminant_); + swap(size_, other.size_); + swap(capacity_, other.capacity_); } private: @@ -341,15 +341,15 @@ namespace dice::sparse_map::detail { } [[nodiscard]] constexpr size_type index_to_offset(size_type index) const noexcept { - assert(index < BITMAP_NB_BITS); - return std::popcount(m_bitmap_vals & ((bitmap_type{1} << index) - bitmap_type{1})); + assert(index < discriminant_bits); + return std::popcount(value_discriminant_ & ((discriminant_type{1} << index) - discriminant_type{1})); } [[nodiscard]] constexpr size_t offset_to_index(size_t offset) const noexcept { - assert(offset < static_cast(std::popcount(m_bitmap_vals))); + assert(offset < static_cast(std::popcount(value_discriminant_))); size_type index = 0; - bitmap_type acc = m_bitmap_vals; + discriminant_type acc = value_discriminant_; while (true) { size_t const ones = std::countr_one(acc); @@ -370,7 +370,7 @@ namespace dice::sparse_map::detail { } [[nodiscard]] constexpr size_type next_capacity() const noexcept { - return static_cast(m_capacity + CAPACITY_GROWTH_STEP); + return static_cast(capacity_ + capacity_growth_step); } /** @@ -379,20 +379,20 @@ namespace dice::sparse_map::detail { * Two situations: * - Either we are in a situation where * std::is_nothrow_move_constructible::value is true. In this - * case, on insertion we just reallocate m_values when we reach its capacity - * (i.e. n_elements_ == m_capacity), otherwise we just put the new value at + * case, on insertion we just reallocate values_ when we reach its capacity + * (i.e. size_ == capacity_), otherwise we just put the new value at * its appropriate place. We can easily keep the strong exception guarantee as * moving the values around is safe. * - Otherwise we are in a situation where * std::is_nothrow_move_constructible::value is false. In this - * case on EACH insertion we allocate a new area of n_elements_ + 1 where we - * copy the values of m_values into it and put the new value there. On - * success, we set m_values to this new area. Even if slower, it's the only + * case on EACH insertion we allocate a new area of size_ + 1 where we + * copy the values of values_ into it and put the new value there. On + * success, we set values_ to this new area. Even if slower, it's the only * way to preserve to strong exception guarantee. */ template requires (std::is_nothrow_move_constructible_v) void insert_at_offset(allocator_type &alloc, size_type offset, Args &&...value_args) { - if (m_nb_elements < m_capacity) { + if (size_ < capacity_) { insert_at_offset_no_realloc(alloc, offset, std::forward(value_args)...); } else { insert_at_offset_realloc(alloc, offset, next_capacity(), std::forward(value_args)...); @@ -401,33 +401,33 @@ namespace dice::sparse_map::detail { template requires (!std::is_nothrow_move_constructible_v) void insert_at_offset(allocator_type &alloc, size_type offset, Args &&...value_args) { - insert_at_offset_realloc(alloc, offset, m_nb_elements + 1, std::forward(value_args)...); + insert_at_offset_realloc(alloc, offset, size_ + 1, std::forward(value_args)...); } template requires (std::is_nothrow_move_constructible_v) void insert_at_offset_no_realloc(allocator_type &alloc, size_type offset, Args &&...value_args) { - assert(offset <= m_nb_elements); - assert(m_nb_elements < m_capacity); + assert(offset <= size_); + assert(size_ < capacity_); if constexpr (std::is_trivially_copyable_v) { - std::memmove(&m_values[offset + 1], &m_values[offset], (m_nb_elements - offset) * sizeof(value_type)); + std::memmove(&values_[offset + 1], &values_[offset], (size_ - offset) * sizeof(value_type)); } else { - for (size_type i = m_nb_elements; i > offset; i--) { - construct_at(alloc, &m_values[i], std::move(m_values[i - 1])); - destroy_at(alloc, &m_values[i - 1]); + for (size_type i = size_; i > offset; i--) { + construct_at(alloc, &values_[i], std::move(values_[i - 1])); + destroy_at(alloc, &values_[i - 1]); } } try { - construct_at(alloc, &m_values[offset], std::forward(value_args)...); + construct_at(alloc, &values_[offset], std::forward(value_args)...); } catch (...) { // revert if constexpr (std::is_trivially_copyable_v) { - std::memmove(&m_values[offset], &m_values[offset + 1], (m_nb_elements - offset) * sizeof(value_type)); + std::memmove(&values_[offset], &values_[offset + 1], (size_ - offset) * sizeof(value_type)); } else { - for (size_type i = offset; i < m_nb_elements; i++) { - construct_at(alloc, &m_values[i], std::move(m_values[i + 1])); - destroy_at(alloc, &m_values[i + 1]); + for (size_type i = offset; i < size_; i++) { + construct_at(alloc, &values_[i], std::move(values_[i + 1])); + destroy_at(alloc, &values_[i + 1]); } } @@ -438,7 +438,7 @@ namespace dice::sparse_map::detail { template requires (std::is_nothrow_move_constructible_v) void insert_at_offset_realloc(allocator_type &alloc, size_type offset, size_type new_capacity, Args &&...value_args) { - assert(new_capacity > m_nb_elements); + assert(new_capacity > size_); pointer new_values = alloc_traits::allocate(alloc, new_capacity); assert(new_values != nullptr); // Allocate should throw if there is a failure @@ -451,30 +451,30 @@ namespace dice::sparse_map::detail { } if constexpr (std::is_trivially_copyable_v) { - if (m_values != nullptr) { - std::memcpy(&new_values[0], &m_values[0], offset * sizeof(value_type)); - std::memcpy(&new_values[offset + 1], &m_values[offset], (m_nb_elements - offset) * sizeof(value_type)); + if (values_ != nullptr) { + std::memcpy(&new_values[0], &values_[0], offset * sizeof(value_type)); + std::memcpy(&new_values[offset + 1], &values_[offset], (size_ - offset) * sizeof(value_type)); } } else { // Cannot throw here as per requires clause for (size_type i = 0; i < offset; i++) { - construct_at(alloc, &new_values[i], std::move(m_values[i])); + construct_at(alloc, &new_values[i], std::move(values_[i])); } - for (size_type i = offset; i < m_nb_elements; i++) { - construct_at(alloc, &new_values[i + 1], std::move(m_values[i])); + for (size_type i = offset; i < size_; i++) { + construct_at(alloc, &new_values[i + 1], std::move(values_[i])); } } - destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); + destroy_and_deallocate_values(alloc, values_, size_, capacity_); - m_values = new_values; - m_capacity = new_capacity; + values_ = new_values; + capacity_ = new_capacity; } template requires (!std::is_nothrow_move_constructible_v) void insert_at_offset_realloc(allocator_type &alloc, size_type offset, size_type new_capacity, Args &&...value_args) { - assert(new_capacity > m_nb_elements); + assert(new_capacity > size_); pointer new_values = alloc_traits::allocate(alloc, new_capacity); assert(new_values != nullptr); // Allocate should throw if there is a failure @@ -482,15 +482,15 @@ namespace dice::sparse_map::detail { size_type nb_new_values = 0; try { for (size_type i = 0; i < offset; i++) { - construct_at(alloc, &new_values[i], m_values[i]); + construct_at(alloc, &new_values[i], values_[i]); nb_new_values++; } construct_at(alloc, &new_values[offset], std::forward(value_args)...); nb_new_values++; - for (size_type i = offset; i < m_nb_elements; i++) { - construct_at(alloc, &new_values[i + 1], m_values[i]); + for (size_type i = offset; i < size_; i++) { + construct_at(alloc, &new_values[i + 1], values_[i]); nb_new_values++; } } catch (...) { @@ -498,12 +498,12 @@ namespace dice::sparse_map::detail { throw; } - assert(nb_new_values == m_nb_elements + 1); + assert(nb_new_values == size_ + 1); - destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); + destroy_and_deallocate_values(alloc, values_, size_, capacity_); - m_values = new_values; - m_capacity = new_capacity; + values_ = new_values; + capacity_ = new_capacity; } /** @@ -516,37 +516,37 @@ namespace dice::sparse_map::detail { * - Otherwise we are in a situation where * std::is_nothrow_move_constructible::value is false. Copy all * the values except the one at offset into a new heap area. On success, we - * set m_values to this new area. Even if slower, it's the only way to + * set values_ to this new area. Even if slower, it's the only way to * preserve to strong exception guarantee. */ template requires (std::is_nothrow_move_constructible_v) void erase_at_offset([[maybe_unused]] allocator_type &alloc, size_type offset) noexcept { - assert(offset < m_nb_elements); + assert(offset < size_); - destroy_at(alloc, &m_values[offset]); + destroy_at(alloc, &values_[offset]); if constexpr (std::is_trivially_copyable_v) { - std::memmove(&m_values[offset], &m_values[offset + 1], (m_nb_elements - offset - 1) * sizeof(value_type)); + std::memmove(&values_[offset], &values_[offset + 1], (size_ - offset - 1) * sizeof(value_type)); } else { - for (size_type i = offset + 1; i < m_nb_elements; ++i) { - construct_at(alloc, &m_values[i - 1], std::move(m_values[i])); - destroy_at(alloc, &m_values[i]); + for (size_type i = offset + 1; i < size_; ++i) { + construct_at(alloc, &values_[i - 1], std::move(values_[i])); + destroy_at(alloc, &values_[i]); } } } template requires (!std::is_nothrow_move_constructible_v) void erase_at_offset(allocator_type &alloc, size_type offset) { - assert(offset < m_nb_elements); + assert(offset < size_); - if (offset + 1 == m_nb_elements) { + if (offset + 1 == size_) { // Erasing the last element, don't need to reallocate. We keep the capacity. - destroy_at(alloc, &m_values[offset]); + destroy_at(alloc, &values_[offset]); return; } - assert(m_nb_elements > 1); - auto const new_capacity = m_nb_elements - 1; + assert(size_ > 1); + auto const new_capacity = size_ - 1; pointer new_values = alloc_traits::allocate(alloc, new_capacity); assert(new_values != nullptr); // Allocate should throw if there is a failure @@ -554,12 +554,12 @@ namespace dice::sparse_map::detail { size_type nb_new_values = 0; try { for (size_type i = 0; i < offset; ++i) { - construct_at(alloc, &new_values[i], m_values[i]); + construct_at(alloc, &new_values[i], values_[i]); nb_new_values++; } - for (size_type i = offset + 1; i < m_nb_elements; ++i) { - construct_at(alloc, &new_values[i - 1], m_values[i]); + for (size_type i = offset + 1; i < size_; ++i) { + construct_at(alloc, &new_values[i - 1], values_[i]); nb_new_values++; } } catch (...) { @@ -567,15 +567,15 @@ namespace dice::sparse_map::detail { throw; } - assert(nb_new_values == m_nb_elements - 1); + assert(nb_new_values == size_ - 1); - destroy_and_deallocate_values(alloc, m_values, m_nb_elements, m_capacity); + destroy_and_deallocate_values(alloc, values_, size_, capacity_); - m_values = new_values; - m_capacity = new_capacity; + values_ = new_values; + capacity_ = new_capacity; } }; -} // namespace dice::sparse_map::detail +} // namespace dice::sparse_map::internal #endif//DICE_SPARSE_MAP_SPARSE_BUCKET_HPP diff --git a/include/dice/sparse_map/internal/sparse_bucket_array.hpp b/include/dice/sparse_map/internal/sparse_bucket_array.hpp index bb1f6a3..596d5ff 100644 --- a/include/dice/sparse_map/internal/sparse_bucket_array.hpp +++ b/include/dice/sparse_map/internal/sparse_bucket_array.hpp @@ -4,7 +4,7 @@ #include "../sparse_props.hpp" #include "sparse_bucket.hpp" -namespace dice::sparse_map::detail { +namespace dice::sparse_map::internal { template struct sparse_bucket_array { @@ -210,25 +210,25 @@ namespace dice::sparse_map::detail { [[nodiscard]] constexpr size_type size() const noexcept { return size_; } [[nodiscard]] constexpr size_type max_size() const noexcept { return bucket_alloc_traits::max_size(bucket_alloc_); }; - reference operator[](size_type const ix) noexcept { + [[nodiscard]] reference operator[](size_type const ix) noexcept { assert(ix < size_); return buckets_[ix]; } - const_reference operator[](size_type const ix) const noexcept { + [[nodiscard]] const_reference operator[](size_type const ix) const noexcept { assert(ix < size_); return buckets_[ix]; } - element_allocator_type &element_allocator() noexcept { + [[nodiscard]] element_allocator_type &element_allocator() noexcept { return elem_alloc_; } - element_allocator_type const &element_allocator() const noexcept { + [[nodiscard]] element_allocator_type const &element_allocator() const noexcept { return elem_alloc_; } }; -} // namespace dice::sparse_map::detail +} // namespace dice::sparse_map::internal #endif//DICE_SPARSE_MAP_SPARSE_BUCKET_ARRAY_HPP diff --git a/include/dice/sparse_map/internal/sparse_hash.hpp b/include/dice/sparse_map/internal/sparse_hash.hpp index 6ad04e7..b73f936 100644 --- a/include/dice/sparse_map/internal/sparse_hash.hpp +++ b/include/dice/sparse_map/internal/sparse_hash.hpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -38,13 +37,12 @@ #include #include #include -#include #include "sparse_bucket.hpp" #include "sparse_bucket_array.hpp" #include "../sparse_growth_policy.hpp" -namespace dice::sparse_map::detail { +namespace dice::sparse_map::internal { template struct is_power_of_two_policy : std::false_type { }; @@ -59,7 +57,7 @@ namespace dice::sparse_map::detail { * `ValueType` is what will be stored by `sparse_hash` (usually `std::pair` for map and `Key` for set). * - * `KeySelect` should be a `FunctionObject` which takes a `ValueType` in + * `k_select` should be a `FunctionObject` which takes a `ValueType` in * parameter and returns a reference to the key. * * `ValueSelect` should be a `FunctionObject` which takes a `ValueType` in @@ -77,7 +75,7 @@ namespace dice::sparse_map::detail { * linear `std::vector` for [0, bucket_count) where each bucket stores * one value, we have a `std::vector` (buckets_) * where each `sparse_array_type` stores multiple values (up to - * `sparse_array_type::BITMAP_NB_BITS`). To convert a one dimensional `ibucket` + * `sparse_array_type::discriminant_bits`). To convert a one dimensional `ibucket` * position to a position in `std::vector` and a position in * `sparse_array_type`, use respectively the methods * `sparse_array_type::sparse_ibucket(ibucket)` and @@ -93,37 +91,37 @@ namespace dice::sparse_map::detail { sparsity Sparsity, probing Probing, ratio MaxLoadFactor> - class sparse_hash { + struct sparse_hash { private: template - struct GetMappedType { + struct get_mapped_type { using type = void; using const_reference = void; using reference = void; }; template requires requires { typename VSel::value_type; } - struct GetMappedType { + struct get_mapped_type { using type = typename VSel::value_type; - using const_reference = const type &; + using const_reference = type const &; using reference = type &; }; public: template - class sparse_iterator; + struct sparse_iterator; using key_type = typename KeyValueSelect::key_type; - using mapped_type = typename GetMappedType::type; - using mapped_const_reference = typename GetMappedType::const_reference; - using mapped_reference = typename GetMappedType::reference; + using mapped_type = typename get_mapped_type::type; + using mapped_const_reference = typename get_mapped_type::const_reference; + using mapped_reference = typename get_mapped_type::reference; using value_type = ValueType; using hasher = Hash; using key_equal = KeyEqual; using allocator_type = Allocator; using growth_policy = GrowthPolicy; using reference = value_type &; - using const_reference = const value_type &; + using const_reference = value_type const &; using size_type = typename std::allocator_traits::size_type; using pointer = typename std::allocator_traits::pointer; using const_pointer = typename std::allocator_traits::const_pointer; @@ -156,6 +154,10 @@ namespace dice::sparse_map::detail { using sparse_bucket_type = typename sparse_bucket_array_type::bucket_type; private: + [[no_unique_address]] hasher h_; + [[no_unique_address]] key_equal keq_; + [[no_unique_address]] growth_policy gpol_; + sparse_bucket_array_type buckets_; size_type bucket_count_; @@ -163,26 +165,22 @@ namespace dice::sparse_map::detail { size_type n_deleted_elements_; /** - * Maximum that n_elements_ can reach before a rehash occurs automatically + * Maximum that size_ can reach before a rehash occurs automatically * to grow the hash table. */ size_type load_threshold_rehash_; /** - * Maximum that n_elements_ + n_deleted_elements_ can reach before cleaning + * Maximum that size_ + n_deleted_elements_ can reach before cleaning * up the buckets marked as deleted. */ size_type load_threshold_clear_deleted_; - [[no_unique_address]] hasher h_; - [[no_unique_address]] key_equal keq_; - [[no_unique_address]] growth_policy gpol_; - public: template - class sparse_iterator { + struct sparse_iterator { private: - friend class sparse_hash; + friend sparse_hash; using sparse_bucket_array_iterator = std::conditional_t && std::is_nothrow_move_constructible_v && std::is_nothrow_move_constructible_v - && std::is_nothrow_move_constructible_v) - : buckets_{std::move(other.buckets_)}, + && std::is_nothrow_move_constructible_v) + : h_{std::move(other.h_)}, + keq_{std::move(other.keq_)}, + gpol_{std::move(other.gpol_)}, + buckets_{std::move(other.buckets_)}, bucket_count_{std::exchange(other.bucket_count_, 0)}, n_elements_{std::exchange(other.n_elements_, 0)}, n_deleted_elements_{std::exchange(other.n_deleted_elements_, 0)}, load_threshold_rehash_{std::exchange(other.load_threshold_rehash_, 0)}, - load_threshold_clear_deleted_{std::exchange(other.load_threshold_clear_deleted_, 0)}, - h_{std::move(other.h_)}, - keq_{std::move(other.keq_)}, - gpol_{std::move(other.gpol_)} { + load_threshold_clear_deleted_{std::exchange(other.load_threshold_clear_deleted_, 0)} { other.gpol_.clear(); } sparse_hash &operator=(sparse_hash &&other) noexcept { assert(this != &other); + h_ = std::move(other.h_); + keq_ = std::move(other.keq_); + gpol_ = std::move(other.gpol_); + other.gpol_.clear(); + buckets_ = std::move(other.buckets_); bucket_count_ = std::exchange(other.bucket_count_, 0); n_elements_ = std::exchange(other.n_elements_, 0); @@ -335,11 +338,6 @@ namespace dice::sparse_map::detail { load_threshold_rehash_ = std::exchange(other.load_threshold_rehash_, 0); load_threshold_clear_deleted_ = std::exchange(other.load_threshold_clear_deleted_, 0); - h_ = std::move(other.h_); - keq_ = std::move(other.keq_); - gpol_ = std::move(other.gpol_); - other.gpol_.clear(); - return *this; } @@ -349,56 +347,53 @@ namespace dice::sparse_map::detail { return buckets_.element_allocator(); } - iterator begin() noexcept { + [[nodiscard]] iterator begin() noexcept { auto begin = buckets_.begin(); - //vector iterator with fancy pointers have a problem with -> - while (begin != buckets_.end() && (*begin).empty()) { + while (begin != buckets_.end() && begin->empty()) { ++begin; } - //vector iterator with fancy pointers have a problem with -> return iterator{begin, buckets_.end(), - begin != buckets_.end() ? (*begin).begin() : nullptr}; + begin != buckets_.end() ? begin->begin() : nullptr}; } - const_iterator begin() const noexcept { + [[nodiscard]] const_iterator begin() const noexcept { return cbegin(); } - const_iterator cbegin() const noexcept { + [[nodiscard]] const_iterator cbegin() const noexcept { auto begin = buckets_.begin(); - //vector iterator with fancy pointers have a problem with -> - while (begin != buckets_.end() && (*begin).empty()) { + while (begin != buckets_.end() && begin->empty()) { ++begin; } return const_iterator{begin, buckets_.end(), - begin != buckets_.end() ? (*begin).begin() : nullptr}; + begin != buckets_.end() ? begin->begin() : nullptr}; } - iterator end() noexcept { + [[nodiscard]] iterator end() noexcept { return iterator{buckets_.end(), buckets_.end(), nullptr}; } - const_iterator end() const noexcept { + [[nodiscard]] const_iterator end() const noexcept { return cend(); } - const_iterator cend() const noexcept { + [[nodiscard]] const_iterator cend() const noexcept { return const_iterator{buckets_.end(), buckets_.end(), nullptr}; } - bool empty() const noexcept { return n_elements_ == 0; } + [[nodiscard]] bool empty() const noexcept { return n_elements_ == 0; } - size_type size() const noexcept { return n_elements_; } + [[nodiscard]] size_type size() const noexcept { return n_elements_; } - size_type max_size() const noexcept { + [[nodiscard]] size_type max_size() const noexcept { return std::min(std::allocator_traits::max_size(), buckets_.max_size()); } @@ -423,7 +418,7 @@ namespace dice::sparse_map::detail { return insert(std::forward

(value)).first; } - template + template void insert(InputIt first, InputIt last) { if (std::is_base_of< std::forward_iterator_tag, @@ -443,7 +438,7 @@ namespace dice::sparse_map::detail { } } - template + template std::pair insert_or_assign(K &&key, M &&obj) { auto it = try_emplace(std::forward(key), std::forward(obj)); if (!it.second) { @@ -453,7 +448,7 @@ namespace dice::sparse_map::detail { return it; } - template + template iterator insert_or_assign(const_iterator hint, K &&key, M &&obj) { if (hint != cend() && keq_(KeyValueSelect::key(*hint), key)) { auto it = mutable_iterator(hint); @@ -465,24 +460,24 @@ namespace dice::sparse_map::detail { return insert_or_assign(std::forward(key), std::forward(obj)).first; } - template + template std::pair emplace(Args &&...args) { return insert(value_type(std::forward(args)...)); } - template + template iterator emplace_hint(const_iterator hint, Args &&...args) { return insert_hint(hint, value_type(std::forward(args)...)); } - template + template std::pair try_emplace(K &&key, Args &&...args) { return insert_impl(key, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), std::forward_as_tuple(std::forward(args)...)); } - template + template iterator try_emplace_hint(const_iterator hint, K &&key, Args &&...args) { if (hint != cend() && keq_(KeyValueSelect::key(*hint), key)) { return mutable_iterator(hint); @@ -546,17 +541,17 @@ namespace dice::sparse_map::detail { return to_delete; } - template - size_type erase(const K &key) { + template + size_type erase(K const &key) { return erase(key, h_(key)); } - template - size_type erase(const K &key, std::size_t hash) { + template + size_type erase(K const &key, std::size_t hash) { return erase_impl(key, hash); } - void swap(sparse_hash &other) { + void swap(sparse_hash &other) noexcept { using std::swap; swap(buckets_, other.buckets_); @@ -570,110 +565,107 @@ namespace dice::sparse_map::detail { swap(gpol_, other.gpol_); } - template requires (has_mapped_type) - mapped_reference at(const K &key) { + template requires (has_mapped_type) + mapped_reference at(K const &key) { return at_impl(*this, key, h_(key)); } - template requires (has_mapped_type) - mapped_reference at(const K &key, std::size_t hash) { + template requires (has_mapped_type) + mapped_reference at(K const &key, std::size_t hash) { return at_impl(*this, key, hash); } - template requires (has_mapped_type) - mapped_const_reference at(const K &key) const { + template requires (has_mapped_type) + mapped_const_reference at(K const &key) const { return at_impl(*this, key, h_(key)); } - template requires (has_mapped_type) - mapped_const_reference at(const K &key, std::size_t hash) const { + template requires (has_mapped_type) + mapped_const_reference at(K const &key, std::size_t hash) const { return at_impl(*this, key, hash); } - template requires (has_mapped_type) + template requires (has_mapped_type) mapped_reference operator[](K &&key) { return try_emplace(std::forward(key)).first->second; } - template - bool contains(const K &key) const { - return contains(key, h_(key)); + template + [[nodiscard]] bool contains(K const &key) const noexcept { + return find(key, h_(key)) != cend(); } - template - bool contains(const K &key, std::size_t hash) const { - return count(key, hash) != 0; + template + [[nodiscard]] bool contains(K const &key, std::size_t hash) const noexcept { + return find(key, hash) != cend(); } - template - size_type count(const K &key) const { + template + [[nodiscard]] size_type count(K const &key) const noexcept { return count(key, h_(key)); } - template - size_type count(const K &key, std::size_t hash) const { - if (find(key, hash) != cend()) { - return 1; - } else { - return 0; - } + template + [[nodiscard]] size_type count(K const &key, std::size_t hash) const noexcept { + return static_cast(find(key, hash) != cend()); } - template - iterator find(const K &key) { + template + [[nodiscard]] iterator find(K const &key) noexcept { return find_impl(*this, key, h_(key)); } - template - iterator find(const K &key, std::size_t hash) { + template + [[nodiscard]] iterator find(K const &key, std::size_t hash) noexcept { return find_impl(*this, key, hash); } - template - const_iterator find(const K &key) const { + template + [[nodiscard]] const_iterator find(K const &key) const noexcept { return find_impl(*this, key, h_(key)); } - template - const_iterator find(const K &key, std::size_t hash) const { + template + [[nodiscard]] const_iterator find(K const &key, std::size_t hash) const noexcept { return find_impl(*this, key, hash); } - template - std::pair equal_range(const K &key) { + template + std::pair equal_range(K const &key) noexcept { return equal_range(key, h_(key)); } - template - std::pair equal_range(const K &key, std::size_t hash) { + template + std::pair equal_range(K const &key, std::size_t hash) noexcept { iterator it = find(key, hash); - return std::make_pair(it, (it == end()) ? it : std::next(it)); + return std::make_pair(it, it == end() ? it : std::next(it)); } - template - std::pair equal_range(const K &key) const { + template + std::pair equal_range(K const &key) const noexcept { return equal_range(key, h_(key)); } - template - std::pair equal_range( - const K &key, std::size_t hash) const { + template + std::pair equal_range(K const &key, std::size_t hash) const noexcept { const_iterator it = find(key, hash); return std::make_pair(it, (it == cend()) ? it : std::next(it)); } - size_type bucket_count() const { return bucket_count_; } + [[nodiscard]] size_type bucket_count() const noexcept { + return bucket_count_; + } - size_type max_bucket_count() const { + [[nodiscard]] size_type max_bucket_count() const noexcept { return buckets_.max_size(); } - float load_factor() const { - if (bucket_count() == 0) { + [[nodiscard]] float load_factor() const noexcept { + if (bucket_count_ == 0) { return 0; } - return float(n_elements_) / float(bucket_count()); + return static_cast(n_elements_) / static_cast(bucket_count_); } void rehash(size_type count) { @@ -689,7 +681,7 @@ namespace dice::sparse_map::detail { [[nodiscard]] key_equal key_eq() const { return keq_; } private: - size_type bucket_for_hash(std::size_t hash) const { + [[nodiscard]] size_type bucket_for_hash(std::size_t hash) const noexcept { auto const bucket = gpol_.bucket_for_hash(hash); assert(sparse_bucket_type::sparse_ibucket(bucket) < buckets_.size() || (bucket == 0 && buckets_.empty())); @@ -697,7 +689,7 @@ namespace dice::sparse_map::detail { return bucket; } - size_type next_bucket(size_type ibucket, [[maybe_unused]] size_type iprobe) const requires (is_power_of_two_policy::value) { + [[nodiscard]] size_type next_bucket(size_type ibucket, [[maybe_unused]] size_type iprobe) const noexcept requires (is_power_of_two_policy::value) { if constexpr (Probing == probing::linear) { return (ibucket + 1) & gpol_.mask(); } else { @@ -706,7 +698,7 @@ namespace dice::sparse_map::detail { } } - size_type next_bucket(size_type ibucket, [[maybe_unused]] size_type iprobe) const requires (!is_power_of_two_policy::value) { + [[nodiscard]] size_type next_bucket(size_type ibucket, [[maybe_unused]] size_type iprobe) const noexcept requires (!is_power_of_two_policy::value) { if constexpr (Probing == probing::linear) { ibucket++; return ibucket != bucket_count_ ? ibucket : 0; @@ -717,8 +709,8 @@ namespace dice::sparse_map::detail { } } - template - std::pair insert_impl(const K &key, + template + std::pair insert_impl(K const &key, Args &&...value_type_args) { if (size() >= load_threshold_rehash_) { rehash_impl(gpol_.next_bucket_count()); @@ -783,7 +775,7 @@ namespace dice::sparse_map::detail { } } - template + template std::pair insert_in_bucket(std::size_t sparse_ibucket, typename sparse_bucket_type::size_type index_in_sparse_bucket, Args &&...value_type_args) { @@ -799,7 +791,7 @@ namespace dice::sparse_map::detail { true); } - template + template size_type erase_impl(K const &key, std::size_t hash) { if (buckets_.empty()) { return 0; @@ -833,8 +825,8 @@ namespace dice::sparse_map::detail { } } - template - static auto find_impl(Self &&self, K const &key, std::size_t hash) { + template + [[nodiscard]] static auto find_impl(Self &&self, K const &key, std::size_t hash) noexcept { if (self.buckets_.empty()) { return self.end(); } @@ -905,8 +897,8 @@ namespace dice::sparse_map::detail { void rehash_impl(size_type count) requires (ExceptionSafety == exception_safety::strong) { sparse_hash new_table(count, h_, keq_, buckets_.element_allocator()); - for (const auto &bucket : buckets_) { - for (const auto &val : bucket) { + for (auto const &bucket : buckets_) { + for (auto const &val : bucket) { new_table.insert_on_rehash(val); } } @@ -916,7 +908,7 @@ namespace dice::sparse_map::detail { template void insert_on_rehash(K &&key_value) { - const key_type &key = KeyValueSelect::key(key_value); + key_type const &key = KeyValueSelect::key(key_value); std::size_t const hash = h_(key); std::size_t ibucket = bucket_for_hash(hash); @@ -942,6 +934,7 @@ namespace dice::sparse_map::detail { } } }; -}// namespace dice::sparse_map -#endif +} // namespace dice::sparse_map::internal + +#endif//DICE_SPARSE_MAP_SPARSE_HASH_HPP diff --git a/include/dice/sparse_map/sparse_map.hpp b/include/dice/sparse_map/sparse_map.hpp index 73314f9..f314993 100644 --- a/include/dice/sparse_map/sparse_map.hpp +++ b/include/dice/sparse_map/sparse_map.hpp @@ -87,12 +87,13 @@ namespace dice::sparse_map { exception_safety ExceptionSafety = exception_safety::basic, sparsity Sparsity = sparsity::medium, ratio MaxLoadFactor = std::ratio<1, 2>> - class sparse_map { + struct sparse_map { + private: static constexpr bool key_equal_is_transparent = requires { typename KeyEqual::is_transparent; }; - struct KVSelect { + struct kv_select { using key_type = Key; using value_type = T; using both_type = std::pair; @@ -103,7 +104,7 @@ namespace dice::sparse_map { } template - static const value_type &value(std::pair const &key_value) noexcept { + static value_type const &value(std::pair const &key_value) noexcept { return key_value.second; } @@ -113,7 +114,7 @@ namespace dice::sparse_map { } template - static const both_type &both(std::pair const &key_value) noexcept { + static both_type const &both(std::pair const &key_value) noexcept { return reinterpret_cast(key_value); } @@ -123,8 +124,10 @@ namespace dice::sparse_map { } }; - using ht = detail::sparse_hash, KVSelect, Hash, KeyEqual, Allocator, - GrowthPolicy, ExceptionSafety, Sparsity, probing::quadratic, MaxLoadFactor>; + using ht = internal::sparse_hash, kv_select, Hash, KeyEqual, Allocator, + GrowthPolicy, ExceptionSafety, Sparsity, probing::quadratic, MaxLoadFactor>; + + ht ht_; public: using key_type = typename ht::key_type; @@ -144,136 +147,145 @@ namespace dice::sparse_map { static constexpr float max_load_factor = ht::max_load_factor; public: - sparse_map() : sparse_map(ht::default_init_bucket_count) {} + sparse_map() noexcept(ht::default_init_bucket_count == 0) : ht_{ht::default_init_bucket_count, {}, {}, {}} {} - explicit sparse_map(size_type bucket_count, const Hash &hash = Hash(), - const KeyEqual &equal = KeyEqual(), - const Allocator &alloc = Allocator()) - : m_ht(bucket_count, hash, equal, alloc) {} + explicit sparse_map(size_type bucket_count, + hasher const &hash = {}, + key_equal const &equal = {}, + allocator_type const &alloc = {}) : ht_{bucket_count, hash, equal, alloc} { + } - sparse_map(size_type bucket_count, const Allocator &alloc) - : sparse_map(bucket_count, Hash(), KeyEqual(), alloc) {} + sparse_map(size_type bucket_count, allocator_type const &alloc) : ht_{bucket_count, {}, {}, alloc} { + } - sparse_map(size_type bucket_count, const Hash &hash, const Allocator &alloc) - : sparse_map(bucket_count, hash, KeyEqual(), alloc) {} + sparse_map(size_type bucket_count, hasher const &hash, allocator_type const &alloc) : ht_{bucket_count, hash, {}, alloc} { + } - explicit sparse_map(const Allocator &alloc) - : sparse_map(ht::default_init_bucket_count, alloc) {} + explicit sparse_map(allocator_type const &alloc) noexcept(ht::default_init_bucket_count == 0) : ht_{ht::default_init_bucket_count, {}, {}, alloc} { + } - template - sparse_map(InputIt first, InputIt last, + template + sparse_map(InputIt first, + InputIt last, size_type bucket_count = ht::default_init_bucket_count, - const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), - const Allocator &alloc = Allocator()) - : sparse_map(bucket_count, hash, equal, alloc) { + hasher const &hash = {}, + key_equal const &equal = {}, + allocator_type const &alloc = {}) : ht_{bucket_count, hash, equal, alloc} { insert(first, last); } - template - sparse_map(InputIt first, InputIt last, size_type bucket_count, - const Allocator &alloc) - : sparse_map(first, last, bucket_count, Hash(), KeyEqual(), alloc) {} + template + sparse_map(InputIt first, + InputIt last, + size_type bucket_count, + allocator_type const &alloc) : sparse_map{first, last, bucket_count, {}, {}, alloc} { + } - template - sparse_map(InputIt first, InputIt last, size_type bucket_count, - const Hash &hash, const Allocator &alloc) - : sparse_map(first, last, bucket_count, hash, KeyEqual(), alloc) {} + template + sparse_map(InputIt first, + InputIt last, + size_type bucket_count, + hasher const &hash, allocator_type const &alloc) : sparse_map{first, last, bucket_count, hash, {}, alloc} { + } sparse_map(std::initializer_list init, size_type bucket_count = ht::default_init_bucket_count, - const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), - const Allocator &alloc = Allocator()) - : sparse_map(init.begin(), init.end(), bucket_count, hash, equal, alloc) { + hasher const &hash = {}, + key_equal const &equal = {}, + allocator_type const &alloc = {}) : sparse_map{init.begin(), init.end(), bucket_count, hash, equal, alloc} { } - sparse_map(std::initializer_list init, size_type bucket_count, - const Allocator &alloc) - : sparse_map(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), - alloc) {} + sparse_map(std::initializer_list init, + size_type bucket_count, + allocator_type const &alloc) : sparse_map(init.begin(), init.end(), bucket_count, {}, {}, alloc) { + } - sparse_map(std::initializer_list init, size_type bucket_count, - const Hash &hash, const Allocator &alloc) - : sparse_map(init.begin(), init.end(), bucket_count, hash, KeyEqual(), - alloc) {} + sparse_map(std::initializer_list init, + size_type bucket_count, + hasher const &hash, + allocator_type const &alloc) : sparse_map{init.begin(), init.end(), bucket_count, hash, {}, alloc} { + } sparse_map &operator=(std::initializer_list ilist) { - m_ht.clear(); + ht_.clear(); - m_ht.reserve(ilist.size()); - m_ht.insert(ilist.begin(), ilist.end()); + ht_.reserve(ilist.size()); + ht_.insert(ilist.begin(), ilist.end()); return *this; } - [[nodiscard]] allocator_type get_allocator() const { return m_ht.get_allocator(); } + [[nodiscard]] allocator_type get_allocator() const { return ht_.get_allocator(); } - [[nodiscard]] iterator begin() noexcept { return m_ht.begin(); } - [[nodiscard]] const_iterator begin() const noexcept { return m_ht.begin(); } - [[nodiscard]] const_iterator cbegin() const noexcept { return m_ht.cbegin(); } + [[nodiscard]] iterator begin() noexcept { return ht_.begin(); } + [[nodiscard]] const_iterator begin() const noexcept { return ht_.begin(); } + [[nodiscard]] const_iterator cbegin() const noexcept { return ht_.cbegin(); } - [[nodiscard]] iterator end() noexcept { return m_ht.end(); } - [[nodiscard]] const_iterator end() const noexcept { return m_ht.end(); } - [[nodiscard]] const_iterator cend() const noexcept { return m_ht.cend(); } + [[nodiscard]] iterator end() noexcept { return ht_.end(); } + [[nodiscard]] const_iterator end() const noexcept { return ht_.end(); } + [[nodiscard]] const_iterator cend() const noexcept { return ht_.cend(); } - [[nodiscard]] bool empty() const noexcept { return m_ht.empty(); } - [[nodiscard]] size_type size() const noexcept { return m_ht.size(); } - [[nodiscard]] size_type max_size() const noexcept { return m_ht.max_size(); } + [[nodiscard]] bool empty() const noexcept { return ht_.empty(); } + [[nodiscard]] size_type size() const noexcept { return ht_.size(); } + [[nodiscard]] size_type max_size() const noexcept { return ht_.max_size(); } - void clear() noexcept { m_ht.clear(); } + void clear() noexcept { + ht_.clear(); + } - std::pair insert(const value_type &value) { - return m_ht.insert(value); + std::pair insert(value_type const &value) { + return ht_.insert(value); } - template requires (std::is_constructible_v) + template requires (std::is_constructible_v) std::pair insert(P &&value) { - return m_ht.emplace(std::forward

(value)); + return ht_.emplace(std::forward

(value)); } std::pair insert(value_type &&value) { - return m_ht.insert(std::move(value)); + return ht_.insert(std::move(value)); } - iterator insert(const_iterator hint, const value_type &value) { - return m_ht.insert_hint(hint, value); + iterator insert(const_iterator hint, value_type const &value) { + return ht_.insert_hint(hint, value); } - template requires (std::is_constructible_v) + template requires (std::is_constructible_v) iterator insert(const_iterator hint, P &&value) { - return m_ht.emplace_hint(hint, std::forward

(value)); + return ht_.emplace_hint(hint, std::forward

(value)); } iterator insert(const_iterator hint, value_type &&value) { - return m_ht.insert_hint(hint, std::move(value)); + return ht_.insert_hint(hint, std::move(value)); } - template + template void insert(InputIt first, InputIt last) { - m_ht.insert(first, last); + ht_.insert(first, last); } void insert(std::initializer_list ilist) { - m_ht.insert(ilist.begin(), ilist.end()); + ht_.insert(ilist.begin(), ilist.end()); } - template - std::pair insert_or_assign(const key_type &k, M &&obj) { - return m_ht.insert_or_assign(k, std::forward(obj)); + template + std::pair insert_or_assign(key_type const &k, M &&obj) { + return ht_.insert_or_assign(k, std::forward(obj)); } - template + template std::pair insert_or_assign(key_type &&k, M &&obj) { - return m_ht.insert_or_assign(std::move(k), std::forward(obj)); + return ht_.insert_or_assign(std::move(k), std::forward(obj)); } - template - iterator insert_or_assign(const_iterator hint, const key_type &k, M &&obj) { - return m_ht.insert_or_assign(hint, k, std::forward(obj)); + template + iterator insert_or_assign(const_iterator hint, key_type const &k, M &&obj) { + return ht_.insert_or_assign(hint, k, std::forward(obj)); } - template + template iterator insert_or_assign(const_iterator hint, key_type &&k, M &&obj) { - return m_ht.insert_or_assign(hint, std::move(k), std::forward(obj)); + return ht_.insert_or_assign(hint, std::move(k), std::forward(obj)); } /** @@ -283,9 +295,9 @@ namespace dice::sparse_map { * * Mainly here for compatibility with the `std::unordered_map` interface. */ - template + template std::pair emplace(Args &&...args) { - return m_ht.emplace(std::forward(args)...); + return ht_.emplace(std::forward(args)...); } /** @@ -295,38 +307,38 @@ namespace dice::sparse_map { * * Mainly here for compatibility with the `std::unordered_map` interface. */ - template + template iterator emplace_hint(const_iterator hint, Args &&...args) { - return m_ht.emplace_hint(hint, std::forward(args)...); + return ht_.emplace_hint(hint, std::forward(args)...); } - template - std::pair try_emplace(const key_type &k, Args &&...args) { - return m_ht.try_emplace(k, std::forward(args)...); + template + std::pair try_emplace(key_type const &k, Args &&...args) { + return ht_.try_emplace(k, std::forward(args)...); } - template + template std::pair try_emplace(key_type &&k, Args &&...args) { - return m_ht.try_emplace(std::move(k), std::forward(args)...); + return ht_.try_emplace(std::move(k), std::forward(args)...); } - template - iterator try_emplace(const_iterator hint, const key_type &k, Args &&...args) { - return m_ht.try_emplace_hint(hint, k, std::forward(args)...); + template + iterator try_emplace(const_iterator hint, key_type const &k, Args &&...args) { + return ht_.try_emplace_hint(hint, k, std::forward(args)...); } - template + template iterator try_emplace(const_iterator hint, key_type &&k, Args &&...args) { - return m_ht.try_emplace_hint(hint, std::move(k), + return ht_.try_emplace_hint(hint, std::move(k), std::forward(args)...); } - iterator erase(iterator pos) { return m_ht.erase(pos); } - iterator erase(const_iterator pos) { return m_ht.erase(pos); } + iterator erase(iterator pos) { return ht_.erase(pos); } + iterator erase(const_iterator pos) { return ht_.erase(pos); } iterator erase(const_iterator first, const_iterator last) { - return m_ht.erase(first, last); + return ht_.erase(first, last); } - size_type erase(const key_type &key) { return m_ht.erase(key); } + size_type erase(key_type const &key) { return ht_.erase(key); } /** * Use the hash value `precalculated_hash` instead of hashing the key. The @@ -334,8 +346,8 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - size_type erase(const key_type &key, std::size_t precalculated_hash) { - return m_ht.erase(key, precalculated_hash); + size_type erase(key_type const &key, std::size_t precalculated_hash) { + return ht_.erase(key, precalculated_hash); } /** @@ -343,9 +355,9 @@ namespace dice::sparse_map { * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and * comparable to `Key`. */ - template requires (key_equal_is_transparent) - size_type erase(const K &key) { - return m_ht.erase(key); + template requires (key_equal_is_transparent) + size_type erase(K const &key) { + return ht_.erase(key); } /** @@ -356,14 +368,16 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template requires (key_equal_is_transparent) - size_type erase(const K &key, std::size_t precalculated_hash) { - return m_ht.erase(key, precalculated_hash); + template requires (key_equal_is_transparent) + size_type erase(K const &key, std::size_t precalculated_hash) { + return ht_.erase(key, precalculated_hash); } - void swap(sparse_map &other) { other.m_ht.swap(m_ht); } + void swap(sparse_map &other) noexcept { + other.ht_.swap(ht_); + } - [[nodiscard]] T &at(const Key &key) { return m_ht.at(key); } + T &at(Key const &key) { return ht_.at(key); } /** * Use the hash value `precalculated_hash` instead of hashing the key. The @@ -371,17 +385,17 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - [[nodiscard]] T &at(const Key &key, std::size_t precalculated_hash) { - return m_ht.at(key, precalculated_hash); + T &at(Key const &key, std::size_t precalculated_hash) { + return ht_.at(key, precalculated_hash); } - [[nodiscard]] const T &at(const Key &key) const { return m_ht.at(key); } + T const &at(Key const &key) const { return ht_.at(key); } /** * @copydoc at(const Key& key, std::size_t precalculated_hash) */ - [[nodiscard]] const T &at(const Key &key, std::size_t precalculated_hash) const { - return m_ht.at(key, precalculated_hash); + T const &at(Key const &key, std::size_t precalculated_hash) const { + return ht_.at(key, precalculated_hash); } /** @@ -389,9 +403,9 @@ namespace dice::sparse_map { * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and * comparable to `Key`. */ - template requires (key_equal_is_transparent) - [[nodiscard]] T &at(const K &key) { - return m_ht.at(key); + template requires (key_equal_is_transparent) + T &at(K const &key) { + return ht_.at(key); } /** @@ -402,31 +416,33 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template requires (key_equal_is_transparent) - [[nodiscard]] T &at(const K &key, std::size_t precalculated_hash) { - return m_ht.at(key, precalculated_hash); + template requires (key_equal_is_transparent) + T &at(K const &key, std::size_t precalculated_hash) { + return ht_.at(key, precalculated_hash); } /** * @copydoc at(const K& key) */ - template requires (key_equal_is_transparent) - [[nodiscard]] const T &at(const K &key) const { - return m_ht.at(key); + template requires (key_equal_is_transparent) + T const &at(K const &key) const { + return ht_.at(key); } /** * @copydoc at(const K& key, std::size_t precalculated_hash) */ - template requires (key_equal_is_transparent) - [[nodiscard]] const T &at(const K &key, std::size_t precalculated_hash) const { - return m_ht.at(key, precalculated_hash); + template requires (key_equal_is_transparent) + T const &at(K const &key, std::size_t precalculated_hash) const { + return ht_.at(key, precalculated_hash); } - T &operator[](const Key &key) { return m_ht[key]; } - T &operator[](Key &&key) { return m_ht[std::move(key)]; } + T &operator[](Key const &key) { return ht_[key]; } + T &operator[](Key &&key) { return ht_[std::move(key)]; } - [[nodiscard]] size_type count(const Key &key) const { return m_ht.count(key); } + [[nodiscard]] size_type count(Key const &key) const noexcept { + return ht_.count(key); + } /** * Use the hash value `precalculated_hash` instead of hashing the key. The @@ -434,8 +450,8 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - [[nodiscard]] size_type count(const Key &key, std::size_t precalculated_hash) const { - return m_ht.count(key, precalculated_hash); + [[nodiscard]] size_type count(Key const &key, std::size_t precalculated_hash) const noexcept { + return ht_.count(key, precalculated_hash); } /** @@ -443,9 +459,9 @@ namespace dice::sparse_map { * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and * comparable to `Key`. */ - template requires (key_equal_is_transparent) - [[nodiscard]] size_type count(const K &key) const { - return m_ht.count(key); + template requires (key_equal_is_transparent) + [[nodiscard]] size_type count(K const &key) const noexcept { + return ht_.count(key); } /** @@ -456,12 +472,14 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template requires (key_equal_is_transparent) - [[nodiscard]] size_type count(const K &key, std::size_t precalculated_hash) const { - return m_ht.count(key, precalculated_hash); + template requires (key_equal_is_transparent) + [[nodiscard]] size_type count(K const &key, std::size_t precalculated_hash) const noexcept { + return ht_.count(key, precalculated_hash); } - [[nodiscard]] iterator find(const Key &key) { return m_ht.find(key); } + [[nodiscard]] iterator find(Key const &key) noexcept { + return ht_.find(key); + } /** * Use the hash value `precalculated_hash` instead of hashing the key. The @@ -469,17 +487,19 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - [[nodiscard]] iterator find(const Key &key, std::size_t precalculated_hash) { - return m_ht.find(key, precalculated_hash); + [[nodiscard]] iterator find(Key const &key, std::size_t precalculated_hash) noexcept { + return ht_.find(key, precalculated_hash); } - [[nodiscard]] const_iterator find(const Key &key) const { return m_ht.find(key); } + [[nodiscard]] const_iterator find(Key const &key) const noexcept { + return ht_.find(key); + } /** * @copydoc find(const Key& key, std::size_t precalculated_hash) */ - [[nodiscard]] const_iterator find(const Key &key, std::size_t precalculated_hash) const { - return m_ht.find(key, precalculated_hash); + [[nodiscard]] const_iterator find(Key const &key, std::size_t precalculated_hash) const noexcept { + return ht_.find(key, precalculated_hash); } /** @@ -487,9 +507,9 @@ namespace dice::sparse_map { * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and * comparable to `Key`. */ - template requires (key_equal_is_transparent) - [[nodiscard]] iterator find(const K &key) { - return m_ht.find(key); + template requires (key_equal_is_transparent) + [[nodiscard]] iterator find(K const &key) noexcept { + return ht_.find(key); } /** @@ -500,17 +520,17 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template requires (key_equal_is_transparent) - [[nodiscard]] iterator find(const K &key, std::size_t precalculated_hash) { - return m_ht.find(key, precalculated_hash); + template requires (key_equal_is_transparent) + [[nodiscard]] iterator find(K const &key, std::size_t precalculated_hash) noexcept { + return ht_.find(key, precalculated_hash); } /** * @copydoc find(const K& key) */ - template requires (key_equal_is_transparent) - [[nodiscard]] const_iterator find(const K &key) const { - return m_ht.find(key); + template requires (key_equal_is_transparent) + [[nodiscard]] const_iterator find(K const &key) const noexcept { + return ht_.find(key); } /** @@ -521,20 +541,22 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template requires (key_equal_is_transparent) - [[nodiscard]] const_iterator find(const K &key, std::size_t precalculated_hash) const { - return m_ht.find(key, precalculated_hash); + template requires (key_equal_is_transparent) + [[nodiscard]] const_iterator find(K const &key, std::size_t precalculated_hash) const noexcept { + return ht_.find(key, precalculated_hash); } - [[nodiscard]] bool contains(const Key &key) const { return m_ht.contains(key); } + [[nodiscard]] bool contains(Key const &key) const noexcept { + return ht_.contains(key); + } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The * hash value should be the same as hash_function()(key). Useful to speed-up * the lookup if you already have the hash. */ - [[nodiscard]] bool contains(const Key &key, std::size_t precalculated_hash) const { - return m_ht.contains(key, precalculated_hash); + [[nodiscard]] bool contains(Key const &key, std::size_t precalculated_hash) const noexcept { + return ht_.contains(key, precalculated_hash); } /** @@ -542,9 +564,9 @@ namespace dice::sparse_map { * KeyEqual::is_transparent exists. If so, K must be hashable and comparable * to Key. */ - template requires (key_equal_is_transparent) - [[nodiscard]] bool contains(const K &key) const { - return m_ht.contains(key); + template requires (key_equal_is_transparent) + [[nodiscard]] bool contains(K const &key) const noexcept { + return ht_.contains(key); } /** @@ -554,13 +576,13 @@ namespace dice::sparse_map { * hash value should be the same as hash_function()(key). Useful to speed-up * the lookup if you already have the hash. */ - template requires (key_equal_is_transparent) - [[nodiscard]] bool contains(const K &key, std::size_t precalculated_hash) const { - return m_ht.contains(key, precalculated_hash); + template requires (key_equal_is_transparent) + [[nodiscard]] bool contains(K const &key, std::size_t precalculated_hash) const noexcept { + return ht_.contains(key, precalculated_hash); } - [[nodiscard]] std::pair equal_range(const Key &key) { - return m_ht.equal_range(key); + [[nodiscard]] std::pair equal_range(Key const &key) { + return ht_.equal_range(key); } /** @@ -569,21 +591,21 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - [[nodiscard]] std::pair equal_range(const Key &key, + [[nodiscard]] std::pair equal_range(Key const &key, std::size_t precalculated_hash) { - return m_ht.equal_range(key, precalculated_hash); + return ht_.equal_range(key, precalculated_hash); } - [[nodiscard]] std::pair equal_range(const Key &key) const { - return m_ht.equal_range(key); + [[nodiscard]] std::pair equal_range(Key const &key) const { + return ht_.equal_range(key); } /** * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) */ - [[nodiscard]] std::pair equal_range(const Key &key, + [[nodiscard]] std::pair equal_range(Key const &key, std::size_t precalculated_hash) const { - return m_ht.equal_range(key, precalculated_hash); + return ht_.equal_range(key, precalculated_hash); } /** @@ -591,9 +613,9 @@ namespace dice::sparse_map { * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and * comparable to `Key`. */ - template requires (key_equal_is_transparent) - [[nodiscard]] std::pair equal_range(const K &key) { - return m_ht.equal_range(key); + template requires (key_equal_is_transparent) + [[nodiscard]] std::pair equal_range(K const &key) { + return ht_.equal_range(key); } /** @@ -604,46 +626,46 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template requires (key_equal_is_transparent) - [[nodiscard]] std::pair equal_range(const K &key, + template requires (key_equal_is_transparent) + [[nodiscard]] std::pair equal_range(K const &key, std::size_t precalculated_hash) { - return m_ht.equal_range(key, precalculated_hash); + return ht_.equal_range(key, precalculated_hash); } /** * @copydoc equal_range(const K& key) */ - template requires (key_equal_is_transparent) - [[nodiscard]] std::pair equal_range(const K &key) const { - return m_ht.equal_range(key); + template requires (key_equal_is_transparent) + [[nodiscard]] std::pair equal_range(K const &key) const { + return ht_.equal_range(key); } /** * @copydoc equal_range(const K& key, std::size_t precalculated_hash) */ - template requires (key_equal_is_transparent) - [[nodiscard]] std::pair equal_range(const K &key, + template requires (key_equal_is_transparent) + [[nodiscard]] std::pair equal_range(K const &key, std::size_t precalculated_hash) const { - return m_ht.equal_range(key, precalculated_hash); + return ht_.equal_range(key, precalculated_hash); } - [[nodiscard]] size_type bucket_count() const { return m_ht.bucket_count(); } - [[nodiscard]] size_type max_bucket_count() const { return m_ht.max_bucket_count(); } + [[nodiscard]] size_type bucket_count() const noexcept { return ht_.bucket_count(); } + [[nodiscard]] size_type max_bucket_count() const noexcept { return ht_.max_bucket_count(); } - [[nodiscard]] float load_factor() const { return m_ht.load_factor(); } + [[nodiscard]] float load_factor() const noexcept { return ht_.load_factor(); } - void rehash(size_type count) { m_ht.rehash(count); } - void reserve(size_type count) { m_ht.reserve(count); } + void rehash(size_type count) { ht_.rehash(count); } + void reserve(size_type count) { ht_.reserve(count); } - [[nodiscard]] hasher hash_function() const { return m_ht.hash_function(); } - [[nodiscard]] key_equal key_eq() const { return m_ht.key_eq(); } + [[nodiscard]] hasher hash_function() const { return ht_.hash_function(); } + [[nodiscard]] key_equal key_eq() const { return ht_.key_eq(); } - friend bool operator==(const sparse_map &lhs, const sparse_map &rhs) { + friend bool operator==(sparse_map const &lhs, sparse_map const &rhs) { if (lhs.size() != rhs.size()) { return false; } - for (const auto &element_lhs : lhs) { + for (auto const &element_lhs : lhs) { const auto it_element_rhs = rhs.find(element_lhs.first); if (it_element_rhs == rhs.cend() || element_lhs.second != it_element_rhs->second) { @@ -654,14 +676,11 @@ namespace dice::sparse_map { return true; } - friend bool operator!=(const sparse_map &lhs, const sparse_map &rhs) { + friend bool operator!=(sparse_map const &lhs, sparse_map const &rhs) { return !operator==(lhs, rhs); } friend void swap(sparse_map &lhs, sparse_map &rhs) { lhs.swap(rhs); } - - private: - ht m_ht; }; /** @@ -676,4 +695,4 @@ namespace dice::sparse_map { }// namespace dice::sparse_map -#endif +#endif // DICE_SPARSE_MAP_SPARSE_MAP_HPP diff --git a/include/dice/sparse_map/sparse_set.hpp b/include/dice/sparse_map/sparse_set.hpp index 8562761..19ad83a 100644 --- a/include/dice/sparse_map/sparse_set.hpp +++ b/include/dice/sparse_map/sparse_set.hpp @@ -87,12 +87,13 @@ namespace dice::sparse_map { exception_safety ExceptionSafety = exception_safety::basic, sparsity Sparsity = sparsity::medium, ratio MaxLoadFactor = std::ratio<1, 2>> - class sparse_set { + struct sparse_set { + private: static constexpr bool key_equal_is_transparent = requires { typename KeyEqual::is_transparent; }; - struct KeySelect { + struct k_select { using key_type = Key; using both_type = Key const; @@ -105,9 +106,11 @@ namespace dice::sparse_map { } }; - using ht = detail::sparse_hash; + using ht = internal::sparse_hash; + + ht ht_; public: using key_type = typename ht::key_type; @@ -126,104 +129,110 @@ namespace dice::sparse_map { sparse_set() : sparse_set(ht::default_init_bucket_count) {} - explicit sparse_set(size_type bucket_count, const Hash &hash = Hash(), - const KeyEqual &equal = KeyEqual(), - const Allocator &alloc = Allocator()) - : m_ht(bucket_count, hash, equal, alloc) {} + explicit sparse_set(size_type bucket_count, + hasher const &hash = {}, + key_equal const &equal = {}, + allocator_type const &alloc = {}) : ht_{bucket_count, hash, equal, alloc} { + } - sparse_set(size_type bucket_count, const Allocator &alloc) - : sparse_set(bucket_count, Hash(), KeyEqual(), alloc) {} + sparse_set(size_type bucket_count, allocator_type const &alloc) : sparse_set{bucket_count, {}, {}, alloc} { + } - sparse_set(size_type bucket_count, const Hash &hash, const Allocator &alloc) - : sparse_set(bucket_count, hash, KeyEqual(), alloc) {} + sparse_set(size_type bucket_count, hasher const &hash, allocator_type const &alloc) : sparse_set{bucket_count, hash, KeyEqual(), alloc} { + } - explicit sparse_set(const Allocator &alloc) - : sparse_set(ht::default_init_bucket_count, alloc) {} + explicit sparse_set(allocator_type const &alloc) : sparse_set{ht::default_init_bucket_count, alloc} { + } - template - sparse_set(InputIt first, InputIt last, + template + sparse_set(InputIt first, + InputIt last, size_type bucket_count = ht::default_init_bucket_count, - const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), - const Allocator &alloc = Allocator()) - : sparse_set(bucket_count, hash, equal, alloc) { + hasher const &hash = {}, + key_equal const &equal = {}, + allocator_type const &alloc = {}) : sparse_set{bucket_count, hash, equal, alloc} { insert(first, last); } - template - sparse_set(InputIt first, InputIt last, size_type bucket_count, - const Allocator &alloc) - : sparse_set(first, last, bucket_count, Hash(), KeyEqual(), alloc) {} + template + sparse_set(InputIt first, + InputIt last, + size_type bucket_count, + allocator_type const &alloc) : sparse_set{first, last, bucket_count, {}, {}, alloc} { + } - template - sparse_set(InputIt first, InputIt last, size_type bucket_count, - const Hash &hash, const Allocator &alloc) - : sparse_set(first, last, bucket_count, hash, KeyEqual(), alloc) {} + template + sparse_set(InputIt first, InputIt last, + size_type bucket_count, + hasher const &hash, + allocator_type const &alloc) : sparse_set{first, last, bucket_count, hash, {}, alloc} { + } sparse_set(std::initializer_list init, size_type bucket_count = ht::default_init_bucket_count, - const Hash &hash = Hash(), const KeyEqual &equal = KeyEqual(), - const Allocator &alloc = Allocator()) - : sparse_set(init.begin(), init.end(), bucket_count, hash, equal, alloc) { + hasher const &hash = {}, + key_equal const &equal = {}, + allocator_type const &alloc = {}) : sparse_set{init.begin(), init.end(), bucket_count, hash, equal, alloc} { } - sparse_set(std::initializer_list init, size_type bucket_count, - const Allocator &alloc) - : sparse_set(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), - alloc) {} + sparse_set(std::initializer_list init, + size_type bucket_count, + allocator_type const &alloc) : sparse_set(init.begin(), init.end(), bucket_count, {}, {}, alloc) { + } - sparse_set(std::initializer_list init, size_type bucket_count, - const Hash &hash, const Allocator &alloc) - : sparse_set(init.begin(), init.end(), bucket_count, hash, KeyEqual(), - alloc) {} + sparse_set(std::initializer_list init, + size_type bucket_count, + hasher const &hash, allocator_type const &alloc) : sparse_set{init.begin(), init.end(), bucket_count, hash, {}, alloc} { + } sparse_set &operator=(std::initializer_list ilist) { - m_ht.clear(); + ht_.clear(); - m_ht.reserve(ilist.size()); - m_ht.insert(ilist.begin(), ilist.end()); + ht_.reserve(ilist.size()); + ht_.insert(ilist.begin(), ilist.end()); return *this; } - [[nodiscard]] allocator_type get_allocator() const { return m_ht.get_allocator(); } + [[nodiscard]] allocator_type get_allocator() const { return ht_.get_allocator(); } - [[nodiscard]] iterator begin() noexcept { return m_ht.begin(); } - [[nodiscard]] const_iterator begin() const noexcept { return m_ht.begin(); } - [[nodiscard]] const_iterator cbegin() const noexcept { return m_ht.cbegin(); } + [[nodiscard]] iterator begin() noexcept { return ht_.begin(); } + [[nodiscard]] const_iterator begin() const noexcept { return ht_.begin(); } + [[nodiscard]] const_iterator cbegin() const noexcept { return ht_.cbegin(); } - [[nodiscard]] iterator end() noexcept { return m_ht.end(); } - [[nodiscard]] const_iterator end() const noexcept { return m_ht.end(); } - [[nodiscard]] const_iterator cend() const noexcept { return m_ht.cend(); } + [[nodiscard]] iterator end() noexcept { return ht_.end(); } + [[nodiscard]] const_iterator end() const noexcept { return ht_.end(); } + [[nodiscard]] const_iterator cend() const noexcept { return ht_.cend(); } - [[nodiscard]] bool empty() const noexcept { return m_ht.empty(); } - [[nodiscard]] size_type size() const noexcept { return m_ht.size(); } - [[nodiscard]] size_type max_size() const noexcept { return m_ht.max_size(); } + [[nodiscard]] bool empty() const noexcept { return ht_.empty(); } + [[nodiscard]] size_type size() const noexcept { return ht_.size(); } + [[nodiscard]] size_type max_size() const noexcept { return ht_.max_size(); } - void clear() noexcept { m_ht.clear(); } + void clear() noexcept { ht_.clear(); } - std::pair insert(const value_type &value) { - return m_ht.insert(value); + std::pair insert(value_type const &value) { + return ht_.insert(value); } std::pair insert(value_type &&value) { - return m_ht.insert(std::move(value)); + return ht_.insert(std::move(value)); } - iterator insert(const_iterator hint, const value_type &value) { - return m_ht.insert_hint(hint, value); + iterator insert(const_iterator hint, value_type const &value) { + return ht_.insert_hint(hint, value); } iterator insert(const_iterator hint, value_type &&value) { - return m_ht.insert_hint(hint, std::move(value)); + return ht_.insert_hint(hint, std::move(value)); } - template + template void insert(InputIt first, InputIt last) { - m_ht.insert(first, last); + ht_.insert(first, last); } void insert(std::initializer_list ilist) { - m_ht.insert(ilist.begin(), ilist.end()); + ht_.insert(ilist.begin(), ilist.end()); } /** @@ -233,9 +242,9 @@ namespace dice::sparse_map { * * Mainly here for compatibility with the `std::unordered_map` interface. */ - template + template std::pair emplace(Args &&...args) { - return m_ht.emplace(std::forward(args)...); + return ht_.emplace(std::forward(args)...); } /** @@ -245,17 +254,17 @@ namespace dice::sparse_map { * * Mainly here for compatibility with the `std::unordered_map` interface. */ - template + template iterator emplace_hint(const_iterator hint, Args &&...args) { - return m_ht.emplace_hint(hint, std::forward(args)...); + return ht_.emplace_hint(hint, std::forward(args)...); } - iterator erase(iterator pos) { return m_ht.erase(pos); } - iterator erase(const_iterator pos) { return m_ht.erase(pos); } + iterator erase(iterator pos) { return ht_.erase(pos); } + iterator erase(const_iterator pos) { return ht_.erase(pos); } iterator erase(const_iterator first, const_iterator last) { - return m_ht.erase(first, last); + return ht_.erase(first, last); } - size_type erase(const key_type &key) { return m_ht.erase(key); } + size_type erase(key_type const &key) { return ht_.erase(key); } /** * Use the hash value `precalculated_hash` instead of hashing the key. The @@ -263,8 +272,8 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - size_type erase(const key_type &key, std::size_t precalculated_hash) { - return m_ht.erase(key, precalculated_hash); + size_type erase(key_type const &key, std::size_t precalculated_hash) { + return ht_.erase(key, precalculated_hash); } /** @@ -272,9 +281,9 @@ namespace dice::sparse_map { * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and * comparable to `Key`. */ - template requires (key_equal_is_transparent) - size_type erase(const K &key) { - return m_ht.erase(key); + template requires (key_equal_is_transparent) + size_type erase(K const &key) { + return ht_.erase(key); } /** @@ -285,14 +294,18 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template requires (key_equal_is_transparent) - size_type erase(const K &key, std::size_t precalculated_hash) { - return m_ht.erase(key, precalculated_hash); + template requires (key_equal_is_transparent) + size_type erase(K const &key, std::size_t precalculated_hash) { + return ht_.erase(key, precalculated_hash); } - void swap(sparse_set &other) { other.m_ht.swap(m_ht); } + void swap(sparse_set &other) noexcept { + other.ht_.swap(ht_); + } - [[nodiscard]] size_type count(const Key &key) const { return m_ht.count(key); } + [[nodiscard]] size_type count(Key const &key) const noexcept { + return ht_.count(key); + } /** * Use the hash value `precalculated_hash` instead of hashing the key. The @@ -300,8 +313,8 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - [[nodiscard]] size_type count(const Key &key, std::size_t precalculated_hash) const { - return m_ht.count(key, precalculated_hash); + [[nodiscard]] size_type count(Key const &key, std::size_t precalculated_hash) const noexcept { + return ht_.count(key, precalculated_hash); } /** @@ -309,9 +322,9 @@ namespace dice::sparse_map { * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and * comparable to `Key`. */ - template requires (key_equal_is_transparent) - [[nodiscard]] size_type count(const K &key) const { - return m_ht.count(key); + template requires (key_equal_is_transparent) + [[nodiscard]] size_type count(K const &key) const noexcept { + return ht_.count(key); } /** @@ -322,12 +335,14 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template requires (key_equal_is_transparent) - [[nodiscard]] size_type count(const K &key, std::size_t precalculated_hash) const { - return m_ht.count(key, precalculated_hash); + template requires (key_equal_is_transparent) + [[nodiscard]] size_type count(K const &key, std::size_t precalculated_hash) const noexcept { + return ht_.count(key, precalculated_hash); } - [[nodiscard]] iterator find(const Key &key) { return m_ht.find(key); } + [[nodiscard]] iterator find(Key const &key) noexcept { + return ht_.find(key); + } /** * Use the hash value `precalculated_hash` instead of hashing the key. The @@ -335,17 +350,19 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - [[nodiscard]] iterator find(const Key &key, std::size_t precalculated_hash) { - return m_ht.find(key, precalculated_hash); + [[nodiscard]] iterator find(Key const &key, std::size_t precalculated_hash) noexcept { + return ht_.find(key, precalculated_hash); } - [[nodiscard]] const_iterator find(const Key &key) const { return m_ht.find(key); } + [[nodiscard]] const_iterator find(Key const &key) const noexcept { + return ht_.find(key); + } /** * @copydoc find(const Key& key, std::size_t precalculated_hash) */ - [[nodiscard]] const_iterator find(const Key &key, std::size_t precalculated_hash) const { - return m_ht.find(key, precalculated_hash); + [[nodiscard]] const_iterator find(Key const &key, std::size_t precalculated_hash) const noexcept { + return ht_.find(key, precalculated_hash); } /** @@ -353,9 +370,9 @@ namespace dice::sparse_map { * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and * comparable to `Key`. */ - template requires (key_equal_is_transparent) - [[nodiscard]] iterator find(const K &key) { - return m_ht.find(key); + template requires (key_equal_is_transparent) + [[nodiscard]] iterator find(K const &key) noexcept { + return ht_.find(key); } /** @@ -366,17 +383,17 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template requires (key_equal_is_transparent) - [[nodiscard]] iterator find(const K &key, std::size_t precalculated_hash) { - return m_ht.find(key, precalculated_hash); + template requires (key_equal_is_transparent) + [[nodiscard]] iterator find(K const &key, std::size_t precalculated_hash) noexcept { + return ht_.find(key, precalculated_hash); } /** * @copydoc find(const K& key) */ - template requires (key_equal_is_transparent) - [[nodiscard]] const_iterator find(const K &key) const { - return m_ht.find(key); + template requires (key_equal_is_transparent) + [[nodiscard]] const_iterator find(K const &key) const noexcept { + return ht_.find(key); } /** @@ -387,20 +404,22 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template requires (key_equal_is_transparent) - [[nodiscard]] const_iterator find(const K &key, std::size_t precalculated_hash) const { - return m_ht.find(key, precalculated_hash); + template requires (key_equal_is_transparent) + [[nodiscard]] const_iterator find(K const &key, std::size_t precalculated_hash) const noexcept { + return ht_.find(key, precalculated_hash); } - [[nodiscard]] bool contains(const Key &key) const { return m_ht.contains(key); } + [[nodiscard]] bool contains(Key const &key) const noexcept { + return ht_.contains(key); + } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The * hash value should be the same as hash_function()(key). Useful to speed-up * the lookup if you already have the hash. */ - [[nodiscard]] bool contains(const Key &key, std::size_t precalculated_hash) const { - return m_ht.contains(key, precalculated_hash); + [[nodiscard]] bool contains(Key const &key, std::size_t precalculated_hash) const noexcept { + return ht_.contains(key, precalculated_hash); } /** @@ -408,9 +427,9 @@ namespace dice::sparse_map { * KeyEqual::is_transparent exists. If so, K must be hashable and comparable * to Key. */ - template requires (key_equal_is_transparent) - [[nodiscard]] bool contains(const K &key) const { - return m_ht.contains(key); + template requires (key_equal_is_transparent) + [[nodiscard]] bool contains(K const &key) const noexcept { + return ht_.contains(key); } /** @@ -420,13 +439,13 @@ namespace dice::sparse_map { * hash value should be the same as hash_function()(key). Useful to speed-up * the lookup if you already have the hash. */ - template requires (key_equal_is_transparent) - [[nodiscard]] bool contains(const K &key, std::size_t precalculated_hash) const { - return m_ht.contains(key, precalculated_hash); + template requires (key_equal_is_transparent) + [[nodiscard]] bool contains(K const &key, std::size_t precalculated_hash) const noexcept { + return ht_.contains(key, precalculated_hash); } - [[nodiscard]] std::pair equal_range(const Key &key) { - return m_ht.equal_range(key); + [[nodiscard]] std::pair equal_range(Key const &key) noexcept { + return ht_.equal_range(key); } /** @@ -435,21 +454,21 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - [[nodiscard]] std::pair equal_range(const Key &key, - std::size_t precalculated_hash) { - return m_ht.equal_range(key, precalculated_hash); + [[nodiscard]] std::pair equal_range(Key const &key, + std::size_t precalculated_hash) noexcept { + return ht_.equal_range(key, precalculated_hash); } - [[nodiscard]] std::pair equal_range(const Key &key) const { - return m_ht.equal_range(key); + [[nodiscard]] std::pair equal_range(Key const &key) const noexcept { + return ht_.equal_range(key); } /** * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) */ - [[nodiscard]] std::pair equal_range(const Key &key, - std::size_t precalculated_hash) const { - return m_ht.equal_range(key, precalculated_hash); + [[nodiscard]] std::pair equal_range(Key const &key, + std::size_t precalculated_hash) const noexcept { + return ht_.equal_range(key, precalculated_hash); } /** @@ -457,9 +476,9 @@ namespace dice::sparse_map { * `KeyEqual::is_transparent` exists. If so, `K` must be hashable and * comparable to `Key`. */ - template requires (key_equal_is_transparent) - [[nodiscard]] std::pair equal_range(const K &key) { - return m_ht.equal_range(key); + template requires (key_equal_is_transparent) + [[nodiscard]] std::pair equal_range(K const &key) noexcept { + return ht_.equal_range(key); } /** @@ -470,48 +489,47 @@ namespace dice::sparse_map { * behaviour is undefined. Useful to speed-up the lookup if you already have * the hash. */ - template requires (key_equal_is_transparent) - [[nodiscard]] std::pair equal_range(const K &key, - std::size_t precalculated_hash) { - return m_ht.equal_range(key, precalculated_hash); + template requires (key_equal_is_transparent) + [[nodiscard]] std::pair equal_range(K const &key, + std::size_t precalculated_hash) noexcept { + return ht_.equal_range(key, precalculated_hash); } /** * @copydoc equal_range(const K& key) */ - template requires (key_equal_is_transparent) - [[nodiscard]] std::pair equal_range(const K &key) const { - return m_ht.equal_range(key); + template requires (key_equal_is_transparent) + [[nodiscard]] std::pair equal_range(K const &key) const noexcept { + return ht_.equal_range(key); } /** * @copydoc equal_range(const K& key, std::size_t precalculated_hash) */ - template requires (key_equal_is_transparent) - [[nodiscard]] std::pair equal_range(const K &key, - std::size_t precalculated_hash) const { - return m_ht.equal_range(key, precalculated_hash); + template requires (key_equal_is_transparent) + [[nodiscard]] std::pair equal_range(K const &key, + std::size_t precalculated_hash) const noexcept { + return ht_.equal_range(key, precalculated_hash); } - [[nodiscard]] size_type bucket_count() const { return m_ht.bucket_count(); } - [[nodiscard]] size_type max_bucket_count() const { return m_ht.max_bucket_count(); } + [[nodiscard]] size_type bucket_count() const noexcept { return ht_.bucket_count(); } + [[nodiscard]] size_type max_bucket_count() const noexcept { return ht_.max_bucket_count(); } - [[nodiscard]] float load_factor() const { return m_ht.load_factor(); } - [[nodiscard]] float max_load_factor() const { return m_ht.max_load_factor(); } - void max_load_factor(float ml) { m_ht.max_load_factor(ml); } + [[nodiscard]] float load_factor() const noexcept { return ht_.load_factor(); } + [[nodiscard]] float max_load_factor() const noexcept { return ht_.max_load_factor(); } - void rehash(size_type count) { m_ht.rehash(count); } - void reserve(size_type count) { m_ht.reserve(count); } + void rehash(size_type count) { ht_.rehash(count); } + void reserve(size_type count) { ht_.reserve(count); } - [[nodiscard]] hasher hash_function() const { return m_ht.hash_function(); } - [[nodiscard]] key_equal key_eq() const { return m_ht.key_eq(); } + [[nodiscard]] hasher hash_function() const { return ht_.hash_function(); } + [[nodiscard]] key_equal key_eq() const { return ht_.key_eq(); } - friend bool operator==(const sparse_set &lhs, const sparse_set &rhs) { + friend bool operator==(sparse_set const &lhs, sparse_set const &rhs) { if (lhs.size() != rhs.size()) { return false; } - for (const auto &element_lhs : lhs) { + for (auto const &element_lhs : lhs) { const auto it_element_rhs = rhs.find(element_lhs); if (it_element_rhs == rhs.cend()) { return false; @@ -521,14 +539,11 @@ namespace dice::sparse_map { return true; } - friend bool operator!=(const sparse_set &lhs, const sparse_set &rhs) { + friend bool operator!=(sparse_set const &lhs, sparse_set const &rhs) { return !operator==(lhs, rhs); } friend void swap(sparse_set &lhs, sparse_set &rhs) { lhs.swap(rhs); } - - private: - ht m_ht; }; /** diff --git a/tests/fancy_pointer/sparse_array_tests.cpp b/tests/fancy_pointer/sparse_array_tests.cpp index 5d8a6cd..924d48b 100644 --- a/tests/fancy_pointer/sparse_array_tests.cpp +++ b/tests/fancy_pointer/sparse_array_tests.cpp @@ -9,7 +9,7 @@ #include "CustomAllocator.hpp" // Globals -constexpr auto MAX_INDEX = 32; //BITMAP_NB_BITS +constexpr auto MAX_INDEX = 32; //discriminant_bits namespace details { template @@ -33,7 +33,7 @@ namespace details { template struct STD { using Allocator = std::allocator; - using Array = dice::sparse_map::detail::sparse_bucket, Sparsity>; + using Array = dice::sparse_map::internal::sparse_bucket, Sparsity>; using Const_Iterator = T const*; using Value_Type = T; }; @@ -41,7 +41,7 @@ struct STD { template struct CUSTOM { using Allocator = OffsetAllocator; - using Array = dice::sparse_map::detail::sparse_bucket, Sparsity>; + using Array = dice::sparse_map::internal::sparse_bucket, Sparsity>; using Const_Iterator = boost::interprocess::offset_ptr; using Value_Type = T; }; diff --git a/tests/fancy_pointer/sparse_hash_map_tests.cpp b/tests/fancy_pointer/sparse_hash_map_tests.cpp index f341536..6a2321d 100644 --- a/tests/fancy_pointer/sparse_hash_map_tests.cpp +++ b/tests/fancy_pointer/sparse_hash_map_tests.cpp @@ -48,7 +48,7 @@ namespace details { template - using sparse_map = dice::sparse_map::detail::sparse_hash< + using sparse_map = dice::sparse_map::internal::sparse_hash< std::pair, KeyValueSelect, std::hash, std::equal_to, Alloc, dice::sparse_map::power_of_two_growth_policy<2>, dice::sparse_map::exception_safety::basic, diff --git a/tests/fancy_pointer/sparse_hash_set_tests.cpp b/tests/fancy_pointer/sparse_hash_set_tests.cpp index ff6368e..c7c7b86 100644 --- a/tests/fancy_pointer/sparse_hash_set_tests.cpp +++ b/tests/fancy_pointer/sparse_hash_set_tests.cpp @@ -23,7 +23,7 @@ namespace details { }; template - using sparse_set = dice::sparse_map::detail::sparse_hash< + using sparse_set = dice::sparse_map::internal::sparse_hash< T, KeySelect, std::hash, std::equal_to, Alloc, dice::sparse_map::power_of_two_growth_policy<2>, dice::sparse_map::exception_safety::basic, diff --git a/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp b/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp index 76869f0..d078f2a 100644 --- a/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp +++ b/tests/scoped_allocator_adaptor/sparse_hash_set_tests.cpp @@ -26,7 +26,7 @@ struct Hash { }; template -using sparse_set = dice::sparse_map::detail::sparse_hash< +using sparse_set = dice::sparse_map::internal::sparse_hash< T, details::KeySelect, Hash, std::equal_to, Alloc, dice::sparse_map::power_of_two_growth_policy<2>, dice::sparse_map::exception_safety::basic, diff --git a/tests/sparse_map_tests.cpp b/tests/sparse_map_tests.cpp index c5092dd..2f19252 100644 --- a/tests/sparse_map_tests.cpp +++ b/tests/sparse_map_tests.cpp @@ -153,13 +153,13 @@ TEST_SUITE("sparse map") { dice::sparse_map::sparse_map map{{1, 0}, {2, 1}, {3, 2}}; // Wrong hint - CHECK(map.insert(map.find(2), std::make_pair(3, 4)) == map.find(3)); + CHECK_EQ(map.insert(map.find(2), std::make_pair(3, 4)), map.find(3)); // Good hint - CHECK(map.insert(map.find(2), std::make_pair(2, 4)) == map.find(2)); + CHECK_EQ(map.insert(map.find(2), std::make_pair(2, 4)), map.find(2)); // end() hint - CHECK(map.insert(map.find(10), std::make_pair(2, 4)) == map.find(2)); + CHECK_EQ(map.insert(map.find(10), std::make_pair(2, 4)), map.find(2)); CHECK_EQ(map.size(), 3); @@ -179,35 +179,34 @@ TEST_SUITE("sparse map") { dice::sparse_map::sparse_map map{{1, 0}, {2, 1}, {3, 2}}; // Wrong hint - CHECK(map.emplace_hint(map.find(2), std::piecewise_construct, - std::forward_as_tuple(3), - std::forward_as_tuple(4)) == map.find(3)); + CHECK_EQ(map.emplace_hint(map.find(2), std::piecewise_construct, + std::forward_as_tuple(3), + std::forward_as_tuple(4)), + map.find(3)); // Good hint - CHECK(map.emplace_hint(map.find(2), std::piecewise_construct, - std::forward_as_tuple(2), - std::forward_as_tuple(4)) == map.find(2)); + CHECK_EQ(map.emplace_hint(map.find(2), std::piecewise_construct, + std::forward_as_tuple(2), + std::forward_as_tuple(4)), + map.find(2)); // end() hint - CHECK(map.emplace_hint(map.find(10), std::piecewise_construct, - std::forward_as_tuple(2), - std::forward_as_tuple(4)) == map.find(2)); + CHECK_EQ(map.emplace_hint(map.find(10), std::piecewise_construct, + std::forward_as_tuple(2), + std::forward_as_tuple(4)), + map.find(2)); CHECK_EQ(map.size(), 3); // end() hint, new value - CHECK_EQ( - map.emplace_hint(map.find(10), std::piecewise_construct, - std::forward_as_tuple(4), std::forward_as_tuple(3)) - ->first, - 4); + CHECK_EQ(map.emplace_hint(map.find(10), std::piecewise_construct, + std::forward_as_tuple(4), std::forward_as_tuple(3))->first, + 4); // Wrong hint, new value - CHECK_EQ( - map.emplace_hint(map.find(2), std::piecewise_construct, - std::forward_as_tuple(5), std::forward_as_tuple(4)) - ->first, - 5); + CHECK_EQ(map.emplace_hint(map.find(2), std::piecewise_construct, + std::forward_as_tuple(5), std::forward_as_tuple(4))->first, + 5); CHECK_EQ(map.size(), 5); } @@ -215,16 +214,16 @@ TEST_SUITE("sparse map") { TEST_CASE("emplace") { dice::sparse_map::sparse_map map; - auto [it, inserted] = - map.emplace(std::piecewise_construct, std::forward_as_tuple(10), - std::forward_as_tuple(1)); + auto [it, inserted] = map.emplace(std::piecewise_construct, + std::forward_as_tuple(10), + std::forward_as_tuple(1)); CHECK_EQ(it->first, 10); CHECK_EQ(it->second, move_only_test(1)); CHECK(inserted); - std::tie(it, inserted) = - map.emplace(std::piecewise_construct, std::forward_as_tuple(10), - std::forward_as_tuple(3)); + std::tie(it, inserted) = map.emplace(std::piecewise_construct, + std::forward_as_tuple(10), + std::forward_as_tuple(3)); CHECK_EQ(it->first, 10); CHECK_EQ(it->second, move_only_test(1)); CHECK(!inserted); From e6c42adbd23b9d9f612ff13189020261696eb680 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Mon, 7 Aug 2023 09:43:16 +0200 Subject: [PATCH 23/41] Avoid iterator invalidation without effective insert There is an issue with the implementation of potentially modifying methods: accessing or reassigning an existing value (means no insertion) may trigger a hash table rehash. This is because load factor thresholds are checked unconfitionally in the common insertion routine. Such behaviour leads to a violation of the following iterator invalidation contract: > - insert, emplace, emplace_hint, operator[]: if there is an effective > insert, invalidate the iterators. User code, being unaware of this bug, may suffer from use-after-free. Fix the issue by moving threshold checks inside the insertion subroutine. --- .../dice/sparse_map/internal/sparse_hash.hpp | 89 ++++++++++--------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/include/dice/sparse_map/internal/sparse_hash.hpp b/include/dice/sparse_map/internal/sparse_hash.hpp index b73f936..9c8acf6 100644 --- a/include/dice/sparse_map/internal/sparse_hash.hpp +++ b/include/dice/sparse_map/internal/sparse_hash.hpp @@ -269,6 +269,11 @@ namespace dice::sparse_map::internal { bool operator!=(sparse_iterator const &other) const noexcept { return cur_bucket_ != other.cur_bucket_ || bucket_it_ != other.bucket_it_; } + + template + bool operator<(sparse_iterator const &other) const noexcept { + return cur_bucket_ < other.cur_bucket_ || (cur_bucket_ == other.cur_bucket_ && bucket_it_ < other.bucket_it_); + } }; iterator mutable_iterator(const_iterator pos) noexcept { @@ -523,18 +528,9 @@ namespace dice::sparse_map::internal { } iterator erase(const_iterator first, const_iterator last) { - //TODO why doesn't this work - /*auto it = mutable_iterator(first); - while (it != last) { - it = erase(it); - } - - return it;*/ - - // TODO Optimize, could avoid the call to std::distance. auto const nb_elements_to_erase = static_cast(std::distance(first, last)); auto to_delete = mutable_iterator(first); - for (size_type i = 0; i < nb_elements_to_erase; i++) { + for (size_type i = 0; i < nb_elements_to_erase; ++i) { to_delete = erase(to_delete); } @@ -710,14 +706,10 @@ namespace dice::sparse_map::internal { } template - std::pair insert_impl(K const &key, - Args &&...value_type_args) { - if (size() >= load_threshold_rehash_) { + std::pair insert_impl(K const &key, Args &&...value_type_args) { + if (buckets_.empty()) { rehash_impl(gpol_.next_bucket_count()); - } else if (size() + n_deleted_elements_ >= load_threshold_clear_deleted_) { - clear_deleted_buckets(); } - assert(!buckets_.empty()); /** * We must insert the value in the first empty or deleted bucket we find. If @@ -731,41 +723,54 @@ namespace dice::sparse_map::internal { std::size_t sparse_ibucket_first_deleted = 0; typename sparse_bucket_type::size_type index_in_sparse_bucket_first_deleted = 0; - const std::size_t hash = h_(key); - std::size_t ibucket = bucket_for_hash(hash); + auto const hash = h_(key); + auto ibucket = bucket_for_hash(hash); std::size_t probe = 0; while (true) { auto const sparse_ibucket = sparse_bucket_type::sparse_ibucket(ibucket); auto const index_in_sparse_bucket = sparse_bucket_type::index_in_sparse_bucket(ibucket); - if (!buckets_.empty()) { - if (buckets_[sparse_ibucket].has_value(index_in_sparse_bucket)) { - auto value_it = buckets_[sparse_ibucket].value(index_in_sparse_bucket); - if (keq_(key, KeyValueSelect::key(*value_it))) { - return std::make_pair(iterator{std::next(buckets_.begin(), sparse_ibucket), - buckets_.end(), - value_it}, - false); - } - } else if (buckets_[sparse_ibucket].has_deleted_value(index_in_sparse_bucket) && probe < bucket_count_) { - if (!found_first_deleted_bucket) { - found_first_deleted_bucket = true; - sparse_ibucket_first_deleted = sparse_ibucket; - index_in_sparse_bucket_first_deleted = index_in_sparse_bucket; - } - } else if (found_first_deleted_bucket) { + if (buckets_[sparse_ibucket].has_value(index_in_sparse_bucket)) { + auto value_it = buckets_[sparse_ibucket].value(index_in_sparse_bucket); + if (keq_(key, KeyValueSelect::key(*value_it))) { + return std::make_pair(iterator{std::next(buckets_.begin(), sparse_ibucket), + buckets_.end(), + value_it}, + false); + } + } else if (buckets_[sparse_ibucket].has_deleted_value(index_in_sparse_bucket) && probe < bucket_count_) { + if (!found_first_deleted_bucket) { + found_first_deleted_bucket = true; + sparse_ibucket_first_deleted = sparse_ibucket; + index_in_sparse_bucket_first_deleted = index_in_sparse_bucket; + } + } else { + /** + * At this point we are sure that the value does not exist + * in the hash table. + * First check if we satisfy load and delete thresholds, and if not, + * rehash the hash table (and therefore start over). Otherwise, just + * insert the value into the appropriate bucket. + */ + if (size() >= load_threshold_rehash_) { + rehash_impl(gpol_.next_bucket_count()); + return insert_impl(key, std::forward(value_type_args)...); + } + + if (size() + n_deleted_elements_ >= load_threshold_clear_deleted_) { + clear_deleted_buckets(); + return insert_impl(key, std::forward(value_type_args)...); + } + + if (found_first_deleted_bucket) { auto it = insert_in_bucket(sparse_ibucket_first_deleted, index_in_sparse_bucket_first_deleted, std::forward(value_type_args)...); - n_deleted_elements_--; - + n_deleted_elements_ -= 1; return it; - } else { - return insert_in_bucket(sparse_ibucket, index_in_sparse_bucket, - std::forward(value_type_args)...); } - } else { + return insert_in_bucket(sparse_ibucket, index_in_sparse_bucket, std::forward(value_type_args)...); } @@ -811,8 +816,8 @@ namespace dice::sparse_map::internal { if (keq_(key, KeyValueSelect::key(*value_it))) { bucket.erase(buckets_.element_allocator(), value_it, index_in_sparse_bucket); - n_elements_--; - n_deleted_elements_++; + n_elements_ -= 1; + n_deleted_elements_ += 1; return 1; } From db1246b12900721c54f58c65995228de45836042 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Mon, 7 Aug 2023 09:49:52 +0200 Subject: [PATCH 24/41] if constexpr --- include/dice/sparse_map/internal/sparse_hash.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/dice/sparse_map/internal/sparse_hash.hpp b/include/dice/sparse_map/internal/sparse_hash.hpp index 9c8acf6..666a1e0 100644 --- a/include/dice/sparse_map/internal/sparse_hash.hpp +++ b/include/dice/sparse_map/internal/sparse_hash.hpp @@ -425,9 +425,7 @@ namespace dice::sparse_map::internal { template void insert(InputIt first, InputIt last) { - if (std::is_base_of< - std::forward_iterator_tag, - typename std::iterator_traits::iterator_category>::value) { + if constexpr (std::is_base_of_v::iterator_category>) { const auto nb_elements_insert = std::distance(first, last); const size_type nb_free_buckets = load_threshold_rehash_ - size(); assert(load_threshold_rehash_ >= size()); From 8e48c6233bb78380ce38306cc44d6f135d03a4af Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Mon, 7 Aug 2023 10:01:58 +0200 Subject: [PATCH 25/41] try fix ci --- .github/workflows/ci.yml | 138 +++++++++++++++++++++------------------ 1 file changed, 74 insertions(+), 64 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b2742fa..c3c1e10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,97 +8,107 @@ jobs: fail-fast: false matrix: config: - - { - name: linux-x64-clang-9, - os: ubuntu-18.04, - cxx: clang++-9, - cmake-build-type: Release - } - { name: macos-x64-gcc, os: macos-10.15, cxx: g++, - cmake-build-type: Release } - { name: macos-x64-clang, os: macos-10.15, cxx: clang++, - cmake-build-type: Release - } - - { - name: linux-x64-clang-12-sanitize, - os: ubuntu-20.04, - cxx: clang++-12, - cxx-flags: "-fsanitize=address,undefined", - cmake-build-type: Release - } - - { - name: linux-x64-gcc-10-coverage, - os: ubuntu-20.04, - cxx: g++-10, - cxx-flags: --coverage, - gcov-tool: gcov-10, - cmake-build-type: Debug } - { - name: linux-x64-clang-11, - os: ubuntu-20.04, - cxx: clang++-11, - cmake-build-type: Release + name: linux-x64-clang-14, + os: ubuntu-22.04, + cxx: clang++-14, } - { - name: linux-x64-clang-12, + name: linux-x64-clang-15, os: ubuntu-22.04, - cxx: clang++-12, - cmake-build-type: Release + cxx: clang++-15, } - { - name: linux-x64-clang-13, - os: ubuntu-22.04, - cxx: clang++-13, - cmake-build-type: Release + name: linux-x64-clang-16-sanitize, + os: ubuntu-20.04, + cxx: clang++-16, + cxx-flags: "-fsanitize=address,undefined", } - { - name: linux-x64-clang-14, + name: linux-x64-gcc-11-coverage, + os: ubuntu-20.04, + cxx: g++-11, + cxx-flags: --coverage, + gcov-tool: gcov-11, + } + - { + name: linux-x64-gcc-12, os: ubuntu-22.04, - cxx: clang++-14, - cmake-build-type: Release + cxx: g++-12, } - { - name: linux-x64-gcc-11, + name: linux-x64-gcc-13, os: ubuntu-22.04, - cxx: g++-11, - cmake-build-type: Release + cxx: g++-13, } - name: ${{matrix.config.name}} + runs-on: ${{matrix.config.os}} steps: - - uses: actions/checkout@v2 + - name: Add Repos for for gcc-13 and clang-16 + run: | + # gcc-13 + sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y + + # clang-16 + source /etc/os-release + echo "deb http://apt.llvm.org/${UBUNTU_CODENAME}/ llvm-toolchain-${UBUNTU_CODENAME}-16 main" | sudo tee /etc/apt/sources.list.d/llvm-16.list + curl https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/llvm-16.gpg > /dev/null + + sudo apt-get update -y + if: runner.os == 'Linux' + + - name: Get minimum cmake version + uses: lukka/get-cmake@v3.24.3 + with: + cmakeVersion: ${{ inputs.cmake-version }} + if: runner.os == 'Linux' + + - name: Install compiler + id: install_cc + uses: rlalik/setup-cpp-compiler@v1.2 + with: + compiler: ${{ inputs.compiler }} + if: runner.os == 'Linux' + + - name: Check out sources + uses: actions/checkout@v3 + + - name: Install boost (Linux) + run: apt-get install -y boost-dev + if: runner.os == 'Linux' - # Linux or macOS - - name: Install boost (Linux or macOS) - run: vcpkg install boost-test boost-container boost-interprocess - if: runner.os == 'Linux' || runner.os == 'macOS' + - name: Install boost (macOS) + run: vcpkg install boost-interprocess + if: runner.os == 'macOS' - - name: Configure CMake (Linux or macOS) - run: cmake -DCMAKE_BUILD_TYPE=${{matrix.config.cmake-build-type}} -DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -S ${{github.workspace}}/tests -B ${{github.workspace}}/build - env: - CXX: ${{matrix.config.cxx}} - CXXFLAGS: ${{matrix.config.cxx-flags}} - if: runner.os == 'Linux' || runner.os == 'macOS' + - name: Configure CMake (Linux or macOS) + run: cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -B build + env: + CXX: ${{matrix.config.cxx}} + CXXFLAGS: ${{matrix.config.cxx-flags}} + if: runner.os == 'Linux' || runner.os == 'macOS' - - name: Build (Linux or macOS) - run: cmake --build ${{github.workspace}}/build --verbose - if: runner.os == 'Linux' || runner.os == 'macOS' + - name: Build (Linux or macOS) + run: cmake --build ${{github.workspace}}/build --verbose + if: runner.os == 'Linux' || runner.os == 'macOS' - - name: Test (Linux or macOS) - run: ${{github.workspace}}/build/tsl_sparse_map_tests - if: runner.os == 'Linux' || runner.os == 'macOS' + - name: Test (Linux or macOS) + run: ${{github.workspace}}/build/tsl_sparse_map_tests + if: runner.os == 'Linux' || runner.os == 'macOS' - - name: Coverage - run: | - sudo apt-get install -y lcov - lcov -c -b ${{github.workspace}}/include -d ${{github.workspace}}/build -o ${{github.workspace}}/coverage.info --no-external --gcov-tool ${{matrix.config.gcov-tool}} - bash <(curl -s https://codecov.io/bash) -f ${{github.workspace}}/coverage.info - if: ${{matrix.config.name == 'linux-x64-gcc-10-coverage'}} + - name: Coverage + run: | + sudo apt-get install -y lcov + lcov -c -b ${{github.workspace}}/include -d ${{github.workspace}}/build -o ${{github.workspace}}/coverage.info --no-external --gcov-tool ${{matrix.config.gcov-tool}} + bash <(curl -s https://codecov.io/bash) -f ${{github.workspace}}/coverage.info + if: ${{matrix.config.name == 'linux-x64-gcc-10-coverage'}} From 24ad3b08628cc67acf120e93cfabd2ccdd8cb663 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Mon, 7 Aug 2023 11:09:14 +0200 Subject: [PATCH 26/41] try fix ci --- .github/workflows/ci.yml | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3c1e10..ce7956b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,12 +10,12 @@ jobs: config: - { name: macos-x64-gcc, - os: macos-10.15, + os: macos-13.5, cxx: g++, } - { name: macos-x64-clang, - os: macos-10.15, + os: macos-13.5, cxx: clang++, } - { @@ -91,19 +91,28 @@ jobs: run: vcpkg install boost-interprocess if: runner.os == 'macOS' - - name: Configure CMake (Linux or macOS) + - name: Configure CMake (Linux) + run: cmake -DCMAKE_BUILD_TYPE=Debug -B build + env: + CXX: ${{matrix.config.cxx}} + CXXFLAGS: ${{matrix.config.cxx-flags}} + if: runner.os == 'Linux' + + - name: Configure CMake (macOS) run: cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -B build env: CXX: ${{matrix.config.cxx}} CXXFLAGS: ${{matrix.config.cxx-flags}} - if: runner.os == 'Linux' || runner.os == 'macOS' + if: runner.os == 'macOS' - name: Build (Linux or macOS) - run: cmake --build ${{github.workspace}}/build --verbose + working-directory: build + run: cmake --build . --verbose --parallel 2 if: runner.os == 'Linux' || runner.os == 'macOS' - name: Test (Linux or macOS) - run: ${{github.workspace}}/build/tsl_sparse_map_tests + working-directory: build + run: ctest --parallel 2 --verbose if: runner.os == 'Linux' || runner.os == 'macOS' - name: Coverage From 36be0fc163b67f88f6f3381bff40f00907884d69 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Mon, 7 Aug 2023 11:10:18 +0200 Subject: [PATCH 27/41] try fix ci --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ce7956b..a23e408 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,7 +77,7 @@ jobs: id: install_cc uses: rlalik/setup-cpp-compiler@v1.2 with: - compiler: ${{ inputs.compiler }} + compiler: ${{ inputs.cxx }} if: runner.os == 'Linux' - name: Check out sources From 47336df69c51b7eb61b126b130bec4311e6df845 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Mon, 7 Aug 2023 11:10:55 +0200 Subject: [PATCH 28/41] try fix ci --- .github/workflows/ci.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a23e408..79fbb3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,12 +67,6 @@ jobs: sudo apt-get update -y if: runner.os == 'Linux' - - name: Get minimum cmake version - uses: lukka/get-cmake@v3.24.3 - with: - cmakeVersion: ${{ inputs.cmake-version }} - if: runner.os == 'Linux' - - name: Install compiler id: install_cc uses: rlalik/setup-cpp-compiler@v1.2 From 77aae68aa639dca1d1f5d03f909d699957e31949 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Mon, 7 Aug 2023 11:12:05 +0200 Subject: [PATCH 29/41] try fix ci --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79fbb3a..96211fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,7 +71,7 @@ jobs: id: install_cc uses: rlalik/setup-cpp-compiler@v1.2 with: - compiler: ${{ inputs.cxx }} + compiler: ${{ matrix.config.cxx }} if: runner.os == 'Linux' - name: Check out sources From 40d1524527e84e96f9aa79e97281dee679fd8a85 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Mon, 7 Aug 2023 11:13:20 +0200 Subject: [PATCH 30/41] try fix ci --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96211fa..33b0dae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,7 +78,7 @@ jobs: uses: actions/checkout@v3 - name: Install boost (Linux) - run: apt-get install -y boost-dev + run: sudo apt-get install -y boost-dev if: runner.os == 'Linux' - name: Install boost (macOS) From 03844a47762b40c60f38c040baff66eb3e953d57 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Mon, 7 Aug 2023 11:14:55 +0200 Subject: [PATCH 31/41] try fix ci --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33b0dae..4777091 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,7 +78,7 @@ jobs: uses: actions/checkout@v3 - name: Install boost (Linux) - run: sudo apt-get install -y boost-dev + run: sudo apt-get install -y libboost-dev if: runner.os == 'Linux' - name: Install boost (macOS) From 7e65d7f714db0c442f69ee01e1e6ae4bb2c0af62 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Mon, 7 Aug 2023 11:16:22 +0200 Subject: [PATCH 32/41] try fix ci --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4777091..74e994e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,18 +93,18 @@ jobs: if: runner.os == 'Linux' - name: Configure CMake (macOS) - run: cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -B build + run: cmake -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -B build env: CXX: ${{matrix.config.cxx}} CXXFLAGS: ${{matrix.config.cxx-flags}} if: runner.os == 'macOS' - - name: Build (Linux or macOS) + - name: Build working-directory: build run: cmake --build . --verbose --parallel 2 if: runner.os == 'Linux' || runner.os == 'macOS' - - name: Test (Linux or macOS) + - name: Test working-directory: build run: ctest --parallel 2 --verbose if: runner.os == 'Linux' || runner.os == 'macOS' From 502eff3b60053f60fb6204ce8a8537dbba5efb45 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Mon, 7 Aug 2023 11:17:37 +0200 Subject: [PATCH 33/41] try fix ci --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 74e994e..e3e05dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,7 +86,7 @@ jobs: if: runner.os == 'macOS' - name: Configure CMake (Linux) - run: cmake -DCMAKE_BUILD_TYPE=Debug -B build + run: cmake -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Debug -B build env: CXX: ${{matrix.config.cxx}} CXXFLAGS: ${{matrix.config.cxx-flags}} @@ -114,4 +114,4 @@ jobs: sudo apt-get install -y lcov lcov -c -b ${{github.workspace}}/include -d ${{github.workspace}}/build -o ${{github.workspace}}/coverage.info --no-external --gcov-tool ${{matrix.config.gcov-tool}} bash <(curl -s https://codecov.io/bash) -f ${{github.workspace}}/coverage.info - if: ${{matrix.config.name == 'linux-x64-gcc-10-coverage'}} + if: ${{matrix.config.name == 'linux-x64-gcc-11-coverage'}} From aa995f9f640cf5bf64a8b0978851a237dac096d4 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Mon, 7 Aug 2023 11:20:03 +0200 Subject: [PATCH 34/41] missing typename --- include/dice/sparse_map/internal/sparse_bucket_array.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/dice/sparse_map/internal/sparse_bucket_array.hpp b/include/dice/sparse_map/internal/sparse_bucket_array.hpp index 596d5ff..4509345 100644 --- a/include/dice/sparse_map/internal/sparse_bucket_array.hpp +++ b/include/dice/sparse_map/internal/sparse_bucket_array.hpp @@ -10,7 +10,7 @@ namespace dice::sparse_map::internal { struct sparse_bucket_array { private: using element_alloc_traits = std::allocator_traits; - using bucket_alloc_traits = std::allocator_traits::template rebind_traits>; + using bucket_alloc_traits = typename std::allocator_traits::template rebind_traits>; using bucket_allocator_type = typename bucket_alloc_traits::allocator_type; using element_allocator_type = typename element_alloc_traits::allocator_type; From a43a320b9a76c3d28fe243df0cc4649404ccae1f Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Mon, 7 Aug 2023 11:22:41 +0200 Subject: [PATCH 35/41] missing include --- include/dice/sparse_map/sparse_growth_policy.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/dice/sparse_map/sparse_growth_policy.hpp b/include/dice/sparse_map/sparse_growth_policy.hpp index 42f2b6a..ba296c9 100644 --- a/include/dice/sparse_map/sparse_growth_policy.hpp +++ b/include/dice/sparse_map/sparse_growth_policy.hpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include From 7bbc7cd10848a46ffd8df14b53696af9319d5ffe Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Mon, 7 Aug 2023 11:25:05 +0200 Subject: [PATCH 36/41] fix test package --- test_package/example.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_package/example.cpp b/test_package/example.cpp index d8618b5..0440ef5 100644 --- a/test_package/example.cpp +++ b/test_package/example.cpp @@ -1,4 +1,4 @@ -#include +#include int main() { dice::sparse_map::sparse_map x; From 5add00e8a139b3f4e37277f807812408d572fe40 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Mon, 7 Aug 2023 11:31:08 +0200 Subject: [PATCH 37/41] comment out macos runners and only run on pr --- .github/workflows/ci.yml | 79 ++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 44 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3e05dc..a6de19f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ name: CI -on: [push, pull_request, release] +on: [ pull_request ] jobs: build: @@ -8,49 +8,40 @@ jobs: fail-fast: false matrix: config: - - { - name: macos-x64-gcc, - os: macos-13.5, - cxx: g++, - } - - { - name: macos-x64-clang, - os: macos-13.5, - cxx: clang++, - } - - { - name: linux-x64-clang-14, - os: ubuntu-22.04, - cxx: clang++-14, - } - - { - name: linux-x64-clang-15, - os: ubuntu-22.04, - cxx: clang++-15, - } - - { - name: linux-x64-clang-16-sanitize, - os: ubuntu-20.04, - cxx: clang++-16, - cxx-flags: "-fsanitize=address,undefined", - } - - { - name: linux-x64-gcc-11-coverage, - os: ubuntu-20.04, - cxx: g++-11, - cxx-flags: --coverage, - gcov-tool: gcov-11, - } - - { - name: linux-x64-gcc-12, - os: ubuntu-22.04, - cxx: g++-12, - } - - { - name: linux-x64-gcc-13, - os: ubuntu-22.04, - cxx: g++-13, - } + # These get queued but never actually run + #- name: macos-x64-gcc, + # os: macos-13.5, + # cxx: g++, + #- name: macos-x64-clang, + # os: macos-13.5, + # cxx: clang++, + + - name: linux-x64-clang-14 + os: ubuntu-22.04 + cxx: clang++-14 + + - name: linux-x64-clang-15 + os: ubuntu-22.04 + cxx: clang++-15 + + - name: linux-x64-clang-16-sanitize + os: ubuntu-22.04 + cxx: clang++-16 + cxx-flags: -fsanitize=undefined -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls + + - name: linux-x64-gcc-11-coverage + os: ubuntu-22.04 + cxx: g++-11 + cxx-flags: --coverage + gcov-tool: gcov-11 + + - name: linux-x64-gcc-12 + os: ubuntu-22.04 + cxx: g++-12 + + - name: linux-x64-gcc-13 + os: ubuntu-22.04 + cxx: g++-13 runs-on: ${{matrix.config.os}} steps: From 49941e0ae380877912e0d20cd1555bd57f0ac164 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Mon, 7 Aug 2023 11:35:02 +0200 Subject: [PATCH 38/41] no tsl debug --- tests/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e93393e..bbb0490 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -23,9 +23,9 @@ macro(make_test DIR NAME) add_test(NAME ${TARGET} COMMAND ${TARGET}) if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") - target_compile_options(${TARGET} PRIVATE -Werror -Wall -Wextra -Wold-style-cast -DTSL_DEBUG -UNDEBUG) + target_compile_options(${TARGET} PRIVATE -Werror -Wall -Wextra -Wold-style-cast) elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - target_compile_options(${TARGET} PRIVATE /bigobj /WX /W3 /DTSL_DEBUG /UNDEBUG) + target_compile_options(${TARGET} PRIVATE /bigobj /WX /W3) endif() endmacro () From fb4447a081ccb6a7f3f998e497aa6b5ed0937e5f Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Tue, 8 Aug 2023 11:51:28 +0200 Subject: [PATCH 39/41] address review 1 --- .../dice/sparse_map/internal/sparse_hash.hpp | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/include/dice/sparse_map/internal/sparse_hash.hpp b/include/dice/sparse_map/internal/sparse_hash.hpp index 666a1e0..cc2ef5d 100644 --- a/include/dice/sparse_map/internal/sparse_hash.hpp +++ b/include/dice/sparse_map/internal/sparse_hash.hpp @@ -154,9 +154,7 @@ namespace dice::sparse_map::internal { using sparse_bucket_type = typename sparse_bucket_array_type::bucket_type; private: - [[no_unique_address]] hasher h_; - [[no_unique_address]] key_equal keq_; - [[no_unique_address]] growth_policy gpol_; + growth_policy gpol_; sparse_bucket_array_type buckets_; size_type bucket_count_; @@ -176,6 +174,9 @@ namespace dice::sparse_map::internal { */ size_type load_threshold_clear_deleted_; + [[no_unique_address]] hasher h_; + [[no_unique_address]] key_equal keq_; + public: template struct sparse_iterator { @@ -291,15 +292,15 @@ namespace dice::sparse_map::internal { sparse_hash(size_type bucket_count, hasher const &hash, key_equal const &equal, - allocator_type const &alloc) : h_{hash}, - keq_{equal}, - gpol_{bucket_count}, + allocator_type const &alloc) : gpol_{bucket_count}, buckets_{bucket_count, alloc}, bucket_count_{bucket_count}, n_elements_{0}, n_deleted_elements_{0}, load_threshold_rehash_{calc_load_threshold_rehash(bucket_count)}, - load_threshold_clear_deleted_{calc_load_threshold_clear_deleted(bucket_count)} { + load_threshold_clear_deleted_{calc_load_threshold_clear_deleted(bucket_count)}, + h_{hash}, + keq_{equal} { // Check in the constructor instead of outside of a function to avoid // compilation issues when value_type is not complete. @@ -312,27 +313,21 @@ namespace dice::sparse_map::internal { sparse_hash(sparse_hash const &other) = default; sparse_hash &operator=(sparse_hash const &other) = default; - sparse_hash(sparse_hash &&other) noexcept(std::is_nothrow_move_constructible_v - && std::is_nothrow_move_constructible_v - && std::is_nothrow_move_constructible_v - && std::is_nothrow_move_constructible_v) - : h_{std::move(other.h_)}, - keq_{std::move(other.keq_)}, - gpol_{std::move(other.gpol_)}, - buckets_{std::move(other.buckets_)}, - bucket_count_{std::exchange(other.bucket_count_, 0)}, - n_elements_{std::exchange(other.n_elements_, 0)}, - n_deleted_elements_{std::exchange(other.n_deleted_elements_, 0)}, - load_threshold_rehash_{std::exchange(other.load_threshold_rehash_, 0)}, - load_threshold_clear_deleted_{std::exchange(other.load_threshold_clear_deleted_, 0)} { + sparse_hash(sparse_hash &&other) noexcept : gpol_{std::move(other.gpol_)}, + buckets_{std::move(other.buckets_)}, + bucket_count_{std::exchange(other.bucket_count_, 0)}, + n_elements_{std::exchange(other.n_elements_, 0)}, + n_deleted_elements_{std::exchange(other.n_deleted_elements_, 0)}, + load_threshold_rehash_{std::exchange(other.load_threshold_rehash_, 0)}, + load_threshold_clear_deleted_{std::exchange(other.load_threshold_clear_deleted_, 0)}, + h_{std::move(other.h_)}, + keq_{std::move(other.keq_)} { other.gpol_.clear(); } sparse_hash &operator=(sparse_hash &&other) noexcept { assert(this != &other); - h_ = std::move(other.h_); - keq_ = std::move(other.keq_); gpol_ = std::move(other.gpol_); other.gpol_.clear(); @@ -343,6 +338,9 @@ namespace dice::sparse_map::internal { load_threshold_rehash_ = std::exchange(other.load_threshold_rehash_, 0); load_threshold_clear_deleted_ = std::exchange(other.load_threshold_clear_deleted_, 0); + h_ = std::move(other.h_); + keq_ = std::move(other.keq_); + return *this; } From d20ef3aeb47da59cc7d75080609303cbf50ee41f Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Tue, 8 Aug 2023 15:48:29 +0200 Subject: [PATCH 40/41] deprecate range erase --- .../dice/sparse_map/internal/sparse_hash.hpp | 4 ++++ tests/sparse_map_tests.cpp | 22 +++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/include/dice/sparse_map/internal/sparse_hash.hpp b/include/dice/sparse_map/internal/sparse_hash.hpp index cc2ef5d..e70bc0d 100644 --- a/include/dice/sparse_map/internal/sparse_hash.hpp +++ b/include/dice/sparse_map/internal/sparse_hash.hpp @@ -523,6 +523,10 @@ namespace dice::sparse_map::internal { return erase(mutable_iterator(pos)); } + [[deprecated("This function is originally from tsl::sparse_hash but it doesn't really make much sense to use, because\n" + "the items between two iterators are effectively random (they are not sorted or anything) so you don't know what you were deleting " + "(except if you manually checked all of them, in which case you could have just deleted them then)\n" + "So this function is effectively just: 'please erase distance(first, last) number of random elements'")]] iterator erase(const_iterator first, const_iterator last) { auto const nb_elements_to_erase = static_cast(std::distance(first, last)); auto to_delete = mutable_iterator(first); diff --git a/tests/sparse_map_tests.cpp b/tests/sparse_map_tests.cpp index 2f19252..c53d550 100644 --- a/tests/sparse_map_tests.cpp +++ b/tests/sparse_map_tests.cpp @@ -325,7 +325,7 @@ TEST_SUITE("sparse map") { CHECK_EQ(it->second, move_only_test(3)); } - TEST_CASE("range erase all") { + /*TEST_CASE("range erase all") { // insert x values, delete all using HMap = dice::sparse_map::sparse_map; @@ -335,9 +335,9 @@ TEST_SUITE("sparse map") { auto it = map.erase(map.begin(), map.end()); CHECK(it == map.end()); CHECK(map.empty()); - } + }*/ - TEST_CASE("range erase") { + /*TEST_CASE("range erase") { // insert x values, delete all except 10 first and 780 last values using HMap = dice::sparse_map::sparse_map; @@ -355,7 +355,7 @@ TEST_SUITE("sparse map") { for (auto &val : map) { CHECK_EQ(map.count(val.first), 1); } - } + }*/ TEST_CASE_TEMPLATE("erase loop", HMap, TEST_MAPS) { // insert x values, delete all one by one with iterator @@ -380,7 +380,7 @@ TEST_SUITE("sparse map") { CHECK(map.empty()); } - TEST_CASE_TEMPLATE("erase loop range", HMap, TEST_MAPS) { + /*TEST_CASE_TEMPLATE("erase loop range", HMap, TEST_MAPS) { // insert x values, delete all five by five with iterators const std::size_t hop = 5; std::size_t nb_values = 1000; @@ -398,7 +398,7 @@ TEST_SUITE("sparse map") { } CHECK(map.empty()); - } + }*/ TEST_CASE_TEMPLATE("insert erase insert", HMap, TEST_MAPS) { // insert x/2 values, delete x/4 values, insert x/2 values, find each value @@ -452,7 +452,7 @@ TEST_SUITE("sparse map") { } } - TEST_CASE("range erase same iter") { + /*TEST_CASE("range erase same iter") { // insert x values, test erase with same iterator as each parameter, check if // returned mutable iterator is valid. const std::size_t nb_values = 100; @@ -472,11 +472,11 @@ TEST_SUITE("sparse map") { it_mutable->second = -100; CHECK_EQ(it_const->second, -100); - } + }*/ /** - * rehash - */ + * rehash + */ TEST_CASE("rehash empty") { // test rehash(0), test find/erase/insert on map. const std::size_t nb_values = 100; @@ -1180,7 +1180,7 @@ TEST_SUITE("sparse map") { CHECK(range.first == range.second); CHECK_EQ(map.erase("test"), 0); - CHECK(map.erase(map.begin(), map.end()) == map.end()); + //CHECK(map.erase(map.begin(), map.end()) == map.end()); CHECK_EQ(map["new value"], int{}); } From 73eafa4056fc77403c0fa7385729dab4b027cdb6 Mon Sep 17 00:00:00 2001 From: Liss Heidrich Date: Tue, 15 Aug 2023 08:30:27 +0200 Subject: [PATCH 41/41] small optim --- include/dice/sparse_map/internal/sparse_bucket_array.hpp | 4 ++++ include/dice/sparse_map/internal/sparse_hash.hpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/dice/sparse_map/internal/sparse_bucket_array.hpp b/include/dice/sparse_map/internal/sparse_bucket_array.hpp index 4509345..c1233a0 100644 --- a/include/dice/sparse_map/internal/sparse_bucket_array.hpp +++ b/include/dice/sparse_map/internal/sparse_bucket_array.hpp @@ -169,6 +169,10 @@ namespace dice::sparse_map::internal { } ~sparse_bucket_array() noexcept { + if (buckets_ == nullptr) { + return; + } + for (size_type ix = 0; ix < size_; ++ix) { buckets_[ix].destroy_deallocate(elem_alloc_); } diff --git a/include/dice/sparse_map/internal/sparse_hash.hpp b/include/dice/sparse_map/internal/sparse_hash.hpp index e70bc0d..0afccfd 100644 --- a/include/dice/sparse_map/internal/sparse_hash.hpp +++ b/include/dice/sparse_map/internal/sparse_hash.hpp @@ -707,7 +707,7 @@ namespace dice::sparse_map::internal { template std::pair insert_impl(K const &key, Args &&...value_type_args) { - if (buckets_.empty()) { + if (buckets_.empty()) [[unlikely]] { rehash_impl(gpol_.next_bucket_count()); }