// Copyright Malte Skarupke 2017. // Distributed under the Boost Software License, Version 1.0. // (See http://www.boost.org/LICENSE_1_0.txt) #pragma once #include #include #include #include #include #include #include #include #include "flat_hash_map.hpp" namespace ska { namespace detailv10 { template struct sherwood_v10_entry { sherwood_v10_entry() { } ~sherwood_v10_entry() { } using EntryPointer = typename std::allocator_traits::template rebind_alloc>::pointer; EntryPointer next = nullptr; union { T value; }; static EntryPointer * empty_pointer() { static EntryPointer result[3] = { EntryPointer(nullptr) + ptrdiff_t(1), nullptr, nullptr }; return result + 1; } }; using ska::detailv3::functor_storage; using ska::detailv3::KeyOrValueHasher; using ska::detailv3::KeyOrValueEquality; using ska::detailv3::AssignIfTrue; using ska::detailv3::HashPolicySelector; template class sherwood_v10_table : private EntryAlloc, private Hasher, private Equal, private BucketAllocator { using Entry = detailv10::sherwood_v10_entry; using AllocatorTraits = std::allocator_traits; using BucketAllocatorTraits = std::allocator_traits; using EntryPointer = typename AllocatorTraits::pointer; struct convertible_to_iterator; public: using value_type = T; using size_type = size_t; using difference_type = std::ptrdiff_t; using hasher = ArgumentHash; using key_equal = ArgumentEqual; using allocator_type = EntryAlloc; using reference = value_type &; using const_reference = const value_type &; using pointer = value_type *; using const_pointer = const value_type *; sherwood_v10_table() { } explicit sherwood_v10_table(size_type bucket_count, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc()) : EntryAlloc(alloc), Hasher(hash), Equal(equal), BucketAllocator(alloc) { rehash(bucket_count); } sherwood_v10_table(size_type bucket_count, const ArgumentAlloc & alloc) : sherwood_v10_table(bucket_count, ArgumentHash(), ArgumentEqual(), alloc) { } sherwood_v10_table(size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc) : sherwood_v10_table(bucket_count, hash, ArgumentEqual(), alloc) { } explicit sherwood_v10_table(const ArgumentAlloc & alloc) : EntryAlloc(alloc), BucketAllocator(alloc) { } template sherwood_v10_table(It first, It last, size_type bucket_count = 0, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc()) : sherwood_v10_table(bucket_count, hash, equal, alloc) { insert(first, last); } template sherwood_v10_table(It first, It last, size_type bucket_count, const ArgumentAlloc & alloc) : sherwood_v10_table(first, last, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) { } template sherwood_v10_table(It first, It last, size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc) : sherwood_v10_table(first, last, bucket_count, hash, ArgumentEqual(), alloc) { } sherwood_v10_table(std::initializer_list il, size_type bucket_count = 0, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc()) : sherwood_v10_table(bucket_count, hash, equal, alloc) { if (bucket_count == 0) reserve(il.size()); insert(il.begin(), il.end()); } sherwood_v10_table(std::initializer_list il, size_type bucket_count, const ArgumentAlloc & alloc) : sherwood_v10_table(il, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) { } sherwood_v10_table(std::initializer_list il, size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc) : sherwood_v10_table(il, bucket_count, hash, ArgumentEqual(), alloc) { } sherwood_v10_table(const sherwood_v10_table & other) : sherwood_v10_table(other, AllocatorTraits::select_on_container_copy_construction(other.get_allocator())) { } sherwood_v10_table(const sherwood_v10_table & other, const ArgumentAlloc & alloc) : EntryAlloc(alloc), Hasher(other), Equal(other), BucketAllocator(alloc), _max_load_factor(other._max_load_factor) { try { rehash_for_other_container(other); insert(other.begin(), other.end()); } catch(...) { clear(); deallocate_data(); throw; } } sherwood_v10_table(sherwood_v10_table && other) noexcept : EntryAlloc(std::move(other)), Hasher(std::move(other)), Equal(std::move(other)), BucketAllocator(std::move(other)), _max_load_factor(other._max_load_factor) { swap_pointers(other); } sherwood_v10_table(sherwood_v10_table && other, const ArgumentAlloc & alloc) noexcept : EntryAlloc(alloc), Hasher(std::move(other)), Equal(std::move(other)), BucketAllocator(alloc), _max_load_factor(other._max_load_factor) { swap_pointers(other); } sherwood_v10_table & operator=(const sherwood_v10_table & other) { if (this == std::addressof(other)) return *this; clear(); static_assert(AllocatorTraits::propagate_on_container_copy_assignment::value == BucketAllocatorTraits::propagate_on_container_copy_assignment::value, "The allocators have to behave the same way"); if (AllocatorTraits::propagate_on_container_copy_assignment::value) { if (static_cast(*this) != static_cast(other) || static_cast(*this) != static_cast(other)) { reset_to_empty_state(); } AssignIfTrue()(*this, other); AssignIfTrue()(*this, other); } _max_load_factor = other._max_load_factor; static_cast(*this) = other; static_cast(*this) = other; rehash_for_other_container(other); insert(other.begin(), other.end()); return *this; } sherwood_v10_table & operator=(sherwood_v10_table && other) noexcept { static_assert(AllocatorTraits::propagate_on_container_move_assignment::value == BucketAllocatorTraits::propagate_on_container_move_assignment::value, "The allocators have to behave the same way"); if (this == std::addressof(other)) return *this; else if (AllocatorTraits::propagate_on_container_move_assignment::value) { clear(); reset_to_empty_state(); AssignIfTrue()(*this, std::move(other)); AssignIfTrue()(*this, std::move(other)); swap_pointers(other); } else if (static_cast(*this) == static_cast(other) && static_cast(*this) == static_cast(other)) { swap_pointers(other); } else { clear(); _max_load_factor = other._max_load_factor; rehash_for_other_container(other); for (T & elem : other) emplace(std::move(elem)); other.clear(); } static_cast(*this) = std::move(other); static_cast(*this) = std::move(other); return *this; } ~sherwood_v10_table() { clear(); deallocate_data(); } const allocator_type & get_allocator() const { return static_cast(*this); } const ArgumentEqual & key_eq() const { return static_cast(*this); } const ArgumentHash & hash_function() const { return static_cast(*this); } template struct templated_iterator { templated_iterator() { } templated_iterator(EntryPointer element, EntryPointer * bucket) : current_element(element), current_bucket(bucket) { } EntryPointer current_element = nullptr; EntryPointer * current_bucket = nullptr; using iterator_category = std::forward_iterator_tag; using value_type = ValueType; using difference_type = ptrdiff_t; using pointer = ValueType *; using reference = ValueType &; friend bool operator==(const templated_iterator & lhs, const templated_iterator & rhs) { return lhs.current_element == rhs.current_element; } friend bool operator!=(const templated_iterator & lhs, const templated_iterator & rhs) { return !(lhs == rhs); } templated_iterator & operator++() { if (!current_element->next) { do { --current_bucket; } while (!*current_bucket); current_element = *current_bucket; } else current_element = current_element->next; return *this; } templated_iterator operator++(int) { templated_iterator copy(*this); ++*this; return copy; } ValueType & operator*() const { return current_element->value; } ValueType * operator->() const { return std::addressof(current_element->value); } operator templated_iterator() const { return { current_element, current_bucket }; } }; using iterator = templated_iterator; using const_iterator = templated_iterator; iterator begin() { EntryPointer * end = entries - 1; for (EntryPointer * it = entries + num_slots_minus_one; it != end; --it) { if (*it) return { *it, it }; } return { *end, end }; } const_iterator begin() const { return const_cast(this)->begin(); } const_iterator cbegin() const { return begin(); } iterator end() { EntryPointer * end = entries - 1; return { *end, end }; } const_iterator end() const { EntryPointer * end = entries - 1; return { *end, end }; } const_iterator cend() const { return end(); } iterator find(const FindKey & key) { size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); EntryPointer * bucket = entries + ptrdiff_t(index); for (EntryPointer it = *bucket; it; it = it->next) { if (compares_equal(key, it->value)) return { it, bucket }; } return end(); } const_iterator find(const FindKey & key) const { return const_cast(this)->find(key); } size_t count(const FindKey & key) const { return find(key) == end() ? 0 : 1; } std::pair equal_range(const FindKey & key) { iterator found = find(key); if (found == end()) return { found, found }; else return { found, std::next(found) }; } std::pair equal_range(const FindKey & key) const { const_iterator found = find(key); if (found == end()) return { found, found }; else return { found, std::next(found) }; } template std::pair emplace(Key && key, Args &&... args) { size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); EntryPointer * bucket = entries + ptrdiff_t(index); for (EntryPointer it = *bucket; it; it = it->next) { if (compares_equal(key, it->value)) return { { it, bucket }, false }; } return emplace_new_key(bucket, std::forward(key), std::forward(args)...); } std::pair insert(const value_type & value) { return emplace(value); } std::pair insert(value_type && value) { return emplace(std::move(value)); } template iterator emplace_hint(const_iterator, Args &&... args) { return emplace(std::forward(args)...).first; } iterator insert(const_iterator, const value_type & value) { return emplace(value).first; } iterator insert(const_iterator, value_type && value) { return emplace(std::move(value)).first; } template void insert(It begin, It end) { for (; begin != end; ++begin) { emplace(*begin); } } void insert(std::initializer_list il) { insert(il.begin(), il.end()); } void rehash(size_t num_buckets) { num_buckets = std::max(num_buckets, static_cast(std::ceil(num_elements / static_cast(_max_load_factor)))); if (num_buckets == 0) { reset_to_empty_state(); return; } auto new_prime_index = hash_policy.next_size_over(num_buckets); if (num_buckets == bucket_count()) return; EntryPointer * new_buckets(&*BucketAllocatorTraits::allocate(*this, num_buckets + 1)); EntryPointer * end_it = new_buckets + static_cast(num_buckets + 1); *new_buckets = EntryPointer(nullptr) + ptrdiff_t(1); ++new_buckets; std::fill(new_buckets, end_it, nullptr); std::swap(entries, new_buckets); std::swap(num_slots_minus_one, num_buckets); --num_slots_minus_one; hash_policy.commit(new_prime_index); if (!num_buckets) return; for (EntryPointer * it = new_buckets, * end = it + static_cast(num_buckets + 1); it != end; ++it) { for (EntryPointer e = *it; e;) { EntryPointer next = e->next; size_t index = hash_policy.index_for_hash(hash_object(e->value), num_slots_minus_one); EntryPointer & new_slot = entries[index]; e->next = new_slot; new_slot = e; e = next; } } BucketAllocatorTraits::deallocate(*this, new_buckets - 1, num_buckets + 2); } void reserve(size_t num_elements) { if (!num_elements) return; num_elements = static_cast(std::ceil(num_elements / static_cast(_max_load_factor))); if (num_elements > bucket_count()) rehash(num_elements); } // the return value is a type that can be converted to an iterator // the reason for doing this is that it's not free to find the // iterator pointing at the next element. if you care about the // next iterator, turn the return value into an iterator convertible_to_iterator erase(const_iterator to_erase) { --num_elements; AllocatorTraits::destroy(*this, std::addressof(to_erase.current_element->value)); EntryPointer * previous = to_erase.current_bucket; while (*previous != to_erase.current_element) { previous = &(*previous)->next; } *previous = to_erase.current_element->next; AllocatorTraits::deallocate(*this, to_erase.current_element, 1); return { *previous, to_erase.current_bucket }; } convertible_to_iterator erase(const_iterator begin_it, const_iterator end_it) { while (begin_it.current_bucket != end_it.current_bucket) { begin_it = erase(begin_it); } EntryPointer * bucket = begin_it.current_bucket; EntryPointer * previous = bucket; while (*previous != begin_it.current_element) previous = &(*previous)->next; while (*previous != end_it.current_element) { --num_elements; EntryPointer entry = *previous; AllocatorTraits::destroy(*this, std::addressof(entry->value)); *previous = entry->next; AllocatorTraits::deallocate(*this, entry, 1); } return { *previous, bucket }; } size_t erase(const FindKey & key) { auto found = find(key); if (found == end()) return 0; else { erase(found); return 1; } } void clear() { if (!num_slots_minus_one) return; for (EntryPointer * it = entries, * end = it + static_cast(num_slots_minus_one + 1); it != end; ++it) { for (EntryPointer e = *it; e;) { EntryPointer next = e->next; AllocatorTraits::destroy(*this, std::addressof(e->value)); AllocatorTraits::deallocate(*this, e, 1); e = next; } *it = nullptr; } num_elements = 0; } void swap(sherwood_v10_table & other) { using std::swap; swap_pointers(other); swap(static_cast(*this), static_cast(other)); swap(static_cast(*this), static_cast(other)); if (AllocatorTraits::propagate_on_container_swap::value) swap(static_cast(*this), static_cast(other)); if (BucketAllocatorTraits::propagate_on_container_swap::value) swap(static_cast(*this), static_cast(other)); } size_t size() const { return num_elements; } size_t max_size() const { return (AllocatorTraits::max_size(*this)) / sizeof(Entry); } size_t bucket_count() const { return num_slots_minus_one + 1; } size_type max_bucket_count() const { return (AllocatorTraits::max_size(*this) - 1) / sizeof(Entry); } size_t bucket(const FindKey & key) const { return hash_policy.template index_for_hash<0>(hash_object(key), num_slots_minus_one); } float load_factor() const { size_t buckets = bucket_count(); if (buckets) return static_cast(num_elements) / bucket_count(); else return 0; } void max_load_factor(float value) { _max_load_factor = value; } float max_load_factor() const { return _max_load_factor; } bool empty() const { return num_elements == 0; } private: EntryPointer * entries = Entry::empty_pointer(); size_t num_slots_minus_one = 0; typename HashPolicySelector::type hash_policy; float _max_load_factor = 1.0f; size_t num_elements = 0; void rehash_for_other_container(const sherwood_v10_table & other) { reserve(other.size()); } void swap_pointers(sherwood_v10_table & other) { using std::swap; swap(hash_policy, other.hash_policy); swap(entries, other.entries); swap(num_slots_minus_one, other.num_slots_minus_one); swap(num_elements, other.num_elements); swap(_max_load_factor, other._max_load_factor); } template SKA_NOINLINE(std::pair) emplace_new_key(EntryPointer * bucket, Args &&... args) { using std::swap; if (is_full()) { grow(); return emplace(std::forward(args)...); } else { EntryPointer new_entry = AllocatorTraits::allocate(*this, 1); try { AllocatorTraits::construct(*this, std::addressof(new_entry->value), std::forward(args)...); } catch(...) { AllocatorTraits::deallocate(*this, new_entry, 1); throw; } ++num_elements; new_entry->next = *bucket; *bucket = new_entry; return { { new_entry, bucket }, true }; } } bool is_full() const { if (!num_slots_minus_one) return true; else return num_elements + 1 > (num_slots_minus_one + 1) * static_cast(_max_load_factor); } void grow() { rehash(std::max(size_t(4), 2 * bucket_count())); } void deallocate_data() { if (entries != Entry::empty_pointer()) { BucketAllocatorTraits::deallocate(*this, entries - 1, num_slots_minus_one + 2); } } void reset_to_empty_state() { deallocate_data(); entries = Entry::empty_pointer(); num_slots_minus_one = 0; hash_policy.reset(); } template size_t hash_object(const U & key) { return static_cast(*this)(key); } template size_t hash_object(const U & key) const { return static_cast(*this)(key); } template bool compares_equal(const L & lhs, const R & rhs) { return static_cast(*this)(lhs, rhs); } struct convertible_to_iterator { EntryPointer element; EntryPointer * bucket; operator iterator() { if (element) return { element, bucket }; else { do { --bucket; } while (!*bucket); return { *bucket, bucket }; } } operator const_iterator() { if (element) return { element, bucket }; else { do { --bucket; } while (!*bucket); return { *bucket, bucket }; } } }; }; } template, typename E = std::equal_to, typename A = std::allocator > > class unordered_map : public detailv10::sherwood_v10_table < std::pair, K, H, detailv10::KeyOrValueHasher, H>, E, detailv10::KeyOrValueEquality, E>, A, typename std::allocator_traits::template rebind_alloc, A>>, typename std::allocator_traits::template rebind_alloc::template rebind_traits, A>>::pointer> > { using Table = detailv10::sherwood_v10_table < std::pair, K, H, detailv10::KeyOrValueHasher, H>, E, detailv10::KeyOrValueEquality, E>, A, typename std::allocator_traits::template rebind_alloc, A>>, typename std::allocator_traits::template rebind_alloc::template rebind_traits, A>>::pointer> >; public: using key_type = K; using mapped_type = V; using Table::Table; unordered_map() { } V & operator[](const K & key) { return emplace(key, convertible_to_value()).first->second; } V & operator[](K && key) { return emplace(std::move(key), convertible_to_value()).first->second; } V & at(const K & key) { auto found = this->find(key); if (found == this->end()) throw std::out_of_range("Argument passed to at() was not in the map."); return found->second; } const V & at(const K & key) const { auto found = this->find(key); if (found == this->end()) throw std::out_of_range("Argument passed to at() was not in the map."); return found->second; } using Table::emplace; std::pair emplace() { return emplace(key_type(), convertible_to_value()); } friend bool operator==(const unordered_map & lhs, const unordered_map & rhs) { if (lhs.size() != rhs.size()) return false; for (const typename Table::value_type & value : lhs) { auto found = rhs.find(value.first); if (found == rhs.end()) return false; else if (value.second != found->second) return false; } return true; } friend bool operator!=(const unordered_map & lhs, const unordered_map & rhs) { return !(lhs == rhs); } private: struct convertible_to_value { operator V() const { return V(); } }; }; template, typename E = std::equal_to, typename A = std::allocator > class unordered_set : public detailv10::sherwood_v10_table < T, T, H, detailv10::functor_storage, E, detailv10::functor_storage, A, typename std::allocator_traits::template rebind_alloc>, typename std::allocator_traits::template rebind_alloc::template rebind_traits>::pointer> > { using Table = detailv10::sherwood_v10_table < T, T, H, detailv10::functor_storage, E, detailv10::functor_storage, A, typename std::allocator_traits::template rebind_alloc>, typename std::allocator_traits::template rebind_alloc::template rebind_traits>::pointer> >; public: using key_type = T; using Table::Table; unordered_set() { } template std::pair emplace(Args &&... args) { return Table::emplace(T(std::forward(args)...)); } std::pair emplace(const key_type & arg) { return Table::emplace(arg); } std::pair emplace(key_type & arg) { return Table::emplace(arg); } std::pair emplace(const key_type && arg) { return Table::emplace(std::move(arg)); } std::pair emplace(key_type && arg) { return Table::emplace(std::move(arg)); } friend bool operator==(const unordered_set & lhs, const unordered_set & rhs) { if (lhs.size() != rhs.size()) return false; for (const T & value : lhs) { if (rhs.find(value) == rhs.end()) return false; } return true; } friend bool operator!=(const unordered_set & lhs, const unordered_set & rhs) { return !(lhs == rhs); } }; } // end namespace ska