container: added dense_hash_set
This commit is contained in:
@@ -121,6 +121,7 @@ if(ENTT_INCLUDE_HEADERS)
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/config/config.h>
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/config/version.h>
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/container/dense_hash_map.hpp>
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/container/dense_hash_set.hpp>
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/container/fwd.hpp>
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/core/algorithm.hpp>
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/core/any.hpp>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* [Introduction](#introduction)
|
||||
* [Containers](#containers)
|
||||
* [Dense hash map](#dense-hash-map)
|
||||
* [Dense hash set](#dense-hash-set)
|
||||
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
@@ -41,3 +42,15 @@ implicit list within the packed array itself.
|
||||
The interface is in all respects similar to its counterpart in the standard
|
||||
library, that is, `std::unordered_map`.<br/>
|
||||
Therefore, there is no need to go into the API description.
|
||||
|
||||
## Dense hash set
|
||||
|
||||
The dense hash set made available in `EnTT` is a set that aims to return a
|
||||
packed array of elements, so as to reduce the number of jumps in memory during
|
||||
the iteration.<br/>
|
||||
The implementation is based on _sparse sets_ and each bucket is identified by an
|
||||
implicit list within the packed array itself.
|
||||
|
||||
The interface is in all respects similar to its counterpart in the standard
|
||||
library, that is, `std::unordered_set`.<br/>
|
||||
Therefore, there is no need to go into the API description.
|
||||
|
||||
846
src/entt/container/dense_hash_set.hpp
Normal file
846
src/entt/container/dense_hash_set.hpp
Normal file
@@ -0,0 +1,846 @@
|
||||
#ifndef ENTT_CONTAINER_DENSE_HASH_SET_HPP
|
||||
#define ENTT_CONTAINER_DENSE_HASH_SET_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "../config/config.h"
|
||||
#include "../core/compressed_pair.hpp"
|
||||
#include "../core/memory.hpp"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
namespace entt {
|
||||
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
namespace internal {
|
||||
|
||||
template<typename Type>
|
||||
struct dense_hash_set_node final {
|
||||
template<typename... Args>
|
||||
dense_hash_set_node(const std::size_t pos, Args &&...args)
|
||||
: next{pos},
|
||||
element{std::forward<Args>(args)...} {}
|
||||
|
||||
std::size_t next;
|
||||
Type element;
|
||||
};
|
||||
|
||||
template<typename It>
|
||||
class dense_hash_set_iterator {
|
||||
friend dense_hash_set_iterator<const std::remove_pointer_t<It> *>;
|
||||
|
||||
using iterator_traits = std::iterator_traits<decltype(std::addressof(std::as_const(std::declval<It>()->element)))>;
|
||||
|
||||
public:
|
||||
using iterator_type = It;
|
||||
using value_type = typename iterator_traits::value_type;
|
||||
using pointer = typename iterator_traits::pointer;
|
||||
using reference = typename iterator_traits::reference;
|
||||
using difference_type = typename iterator_traits::difference_type;
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
|
||||
dense_hash_set_iterator() ENTT_NOEXCEPT = default;
|
||||
|
||||
dense_hash_set_iterator(const iterator_type iter) ENTT_NOEXCEPT
|
||||
: it{iter} {}
|
||||
|
||||
template<bool Const = std::is_const_v<std::remove_pointer_t<iterator_type>>, typename = std::enable_if_t<Const>>
|
||||
dense_hash_set_iterator(const dense_hash_set_iterator<std::remove_const_t<std::remove_pointer_t<iterator_type>> *> &other)
|
||||
: it{other.it} {}
|
||||
|
||||
dense_hash_set_iterator &operator++() ENTT_NOEXCEPT {
|
||||
return ++it, *this;
|
||||
}
|
||||
|
||||
dense_hash_set_iterator operator++(int) ENTT_NOEXCEPT {
|
||||
dense_hash_set_iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
dense_hash_set_iterator &operator--() ENTT_NOEXCEPT {
|
||||
return --it, *this;
|
||||
}
|
||||
|
||||
dense_hash_set_iterator operator--(int) ENTT_NOEXCEPT {
|
||||
dense_hash_set_iterator orig = *this;
|
||||
return operator--(), orig;
|
||||
}
|
||||
|
||||
dense_hash_set_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT {
|
||||
it += value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
dense_hash_set_iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
|
||||
dense_hash_set_iterator copy = *this;
|
||||
return (copy += value);
|
||||
}
|
||||
|
||||
dense_hash_set_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT {
|
||||
return (*this += -value);
|
||||
}
|
||||
|
||||
dense_hash_set_iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
|
||||
return (*this + -value);
|
||||
}
|
||||
|
||||
[[nodiscard]] reference operator[](const difference_type value) const {
|
||||
return it->element;
|
||||
}
|
||||
|
||||
[[nodiscard]] pointer operator->() const {
|
||||
return std::addressof(it->element);
|
||||
}
|
||||
|
||||
[[nodiscard]] reference operator*() const {
|
||||
return *operator->();
|
||||
}
|
||||
|
||||
[[nodiscard]] iterator_type base() const ENTT_NOEXCEPT {
|
||||
return it;
|
||||
}
|
||||
|
||||
private:
|
||||
iterator_type it;
|
||||
};
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] auto operator-(const dense_hash_set_iterator<ILhs> &lhs, const dense_hash_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
|
||||
return lhs.base() - rhs.base();
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] bool operator==(const dense_hash_set_iterator<ILhs> &lhs, const dense_hash_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
|
||||
return lhs.base() == rhs.base();
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] bool operator!=(const dense_hash_set_iterator<ILhs> &lhs, const dense_hash_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] bool operator<(const dense_hash_set_iterator<ILhs> &lhs, const dense_hash_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
|
||||
return lhs.base() < rhs.base();
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] bool operator>(const dense_hash_set_iterator<ILhs> &lhs, const dense_hash_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
|
||||
return lhs.base() > rhs.base();
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] bool operator<=(const dense_hash_set_iterator<ILhs> &lhs, const dense_hash_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
|
||||
return !(lhs > rhs);
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] bool operator>=(const dense_hash_set_iterator<ILhs> &lhs, const dense_hash_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
template<typename It>
|
||||
class dense_hash_set_local_iterator {
|
||||
friend dense_hash_set_local_iterator<const std::remove_pointer_t<It> *>;
|
||||
|
||||
using iterator_traits = std::iterator_traits<decltype(std::addressof(std::as_const(std::declval<It>()->element)))>;
|
||||
|
||||
public:
|
||||
using iterator_type = It;
|
||||
using value_type = typename iterator_traits::value_type;
|
||||
using pointer = typename iterator_traits::pointer;
|
||||
using reference = typename iterator_traits::reference;
|
||||
using difference_type = typename iterator_traits::difference_type;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
dense_hash_set_local_iterator() ENTT_NOEXCEPT = default;
|
||||
|
||||
dense_hash_set_local_iterator(iterator_type iter, const std::size_t pos) ENTT_NOEXCEPT
|
||||
: it{iter},
|
||||
curr{pos} {}
|
||||
|
||||
template<bool Const = std::is_const_v<std::remove_pointer_t<iterator_type>>, typename = std::enable_if_t<Const>>
|
||||
dense_hash_set_local_iterator(const dense_hash_set_local_iterator<std::remove_const_t<std::remove_pointer_t<iterator_type>> *> &other)
|
||||
: it{other.it},
|
||||
curr{other.curr} {}
|
||||
|
||||
dense_hash_set_local_iterator &operator++() ENTT_NOEXCEPT {
|
||||
return curr = it[curr].next, *this;
|
||||
}
|
||||
|
||||
dense_hash_set_local_iterator operator++(int) ENTT_NOEXCEPT {
|
||||
dense_hash_set_local_iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
[[nodiscard]] pointer operator->() const {
|
||||
return std::addressof(it[curr].element);
|
||||
}
|
||||
|
||||
[[nodiscard]] reference operator*() const {
|
||||
return *operator->();
|
||||
}
|
||||
|
||||
[[nodiscard]] iterator_type base() const ENTT_NOEXCEPT {
|
||||
return (it + curr);
|
||||
}
|
||||
|
||||
private:
|
||||
iterator_type it;
|
||||
std::size_t curr;
|
||||
};
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] bool operator==(const dense_hash_set_local_iterator<ILhs> &lhs, const dense_hash_set_local_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
|
||||
return lhs.base() == rhs.base();
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] bool operator!=(const dense_hash_set_local_iterator<ILhs> &lhs, const dense_hash_set_local_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Associative container for unique objects of a given type.
|
||||
*
|
||||
* Internally, elements are organized into buckets. Which bucket an element is
|
||||
* placed into depends entirely on its hash. Elements with the same hash code
|
||||
* appear in the same bucket.
|
||||
*
|
||||
* @tparam Type Value type of the associative container.
|
||||
* @tparam Hash Type of function to use to hash the values.
|
||||
* @tparam KeyEqual Type of function to use to compare the values for equality.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Type, typename Hash, typename KeyEqual, typename Allocator>
|
||||
class dense_hash_set final {
|
||||
static constexpr float default_threshold = 0.875f;
|
||||
static constexpr std::size_t minimum_capacity = 8u;
|
||||
|
||||
using allocator_traits = std::allocator_traits<Allocator>;
|
||||
using alloc = typename allocator_traits::template rebind_alloc<Type>;
|
||||
using alloc_traits = typename std::allocator_traits<alloc>;
|
||||
|
||||
using node_type = internal::dense_hash_set_node<Type>;
|
||||
using sparse_container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>;
|
||||
using packed_container_type = std::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>;
|
||||
|
||||
[[nodiscard]] std::size_t hash_to_bucket(const std::size_t hash) const ENTT_NOEXCEPT {
|
||||
return fast_mod(hash, bucket_count());
|
||||
}
|
||||
|
||||
template<typename Other>
|
||||
[[nodiscard]] auto constrained_find(const Other &value, std::size_t bucket) {
|
||||
for(auto it = begin(bucket), last = end(bucket); it != last; ++it) {
|
||||
if(packed.second()(*it, value)) {
|
||||
return iterator{it.base()};
|
||||
}
|
||||
}
|
||||
|
||||
return end();
|
||||
}
|
||||
|
||||
template<typename Other>
|
||||
[[nodiscard]] auto constrained_find(const Other &value, std::size_t bucket) const {
|
||||
for(auto it = begin(bucket), last = end(bucket); it != last; ++it) {
|
||||
if(packed.second()(*it, value)) {
|
||||
return const_iterator{it.base()};
|
||||
}
|
||||
}
|
||||
|
||||
return cend();
|
||||
}
|
||||
|
||||
template<typename Arg>
|
||||
[[nodiscard]] auto get_or_emplace(Arg &&arg) {
|
||||
const auto hash = sparse.second()(arg);
|
||||
auto index = hash_to_bucket(hash);
|
||||
|
||||
if(auto it = constrained_find(arg, index); it != end()) {
|
||||
return std::make_pair(it, false);
|
||||
}
|
||||
|
||||
if(const auto count = size() + 1u; count > (bucket_count() * max_load_factor())) {
|
||||
rehash(bucket_count() * 2u);
|
||||
index = hash_to_bucket(hash);
|
||||
}
|
||||
|
||||
packed.first().emplace_back(sparse.first()[index], std::forward<Arg>(arg));
|
||||
// update goes after emplace to enforce exception guarantees
|
||||
sparse.first()[index] = size() - 1u;
|
||||
|
||||
return std::make_pair(--end(), true);
|
||||
}
|
||||
|
||||
template<typename Other>
|
||||
bool do_erase(const Other &value) {
|
||||
for(size_type *curr = sparse.first().data() + bucket(value); *curr != std::numeric_limits<size_type>::max(); curr = &packed.first()[*curr].next) {
|
||||
if(packed.second()(packed.first()[*curr].element, value)) {
|
||||
const auto index = *curr;
|
||||
*curr = packed.first()[*curr].next;
|
||||
move_and_pop(index);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void move_and_pop(const std::size_t pos) {
|
||||
if(const auto last = size() - 1u; pos != last) {
|
||||
size_type *curr = sparse.first().data() + bucket(packed.first().back().element);
|
||||
for(; *curr != last; curr = &packed.first()[*curr].next) {}
|
||||
*curr = pos;
|
||||
|
||||
// basic exception guarantees when value type has a throwing move constructor
|
||||
packed.first()[pos] = std::move(packed.first().back());
|
||||
}
|
||||
|
||||
packed.first().pop_back();
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Key type of the container. */
|
||||
using key_type = Type;
|
||||
/*! @brief Value type of the container. */
|
||||
using value_type = Type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Type of function to use to hash the elements. */
|
||||
using hasher = Hash;
|
||||
/*! @brief Type of function to use to compare the elements for equality. */
|
||||
using key_equal = KeyEqual;
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = Allocator;
|
||||
/*! @brief Random access iterator type. */
|
||||
using iterator = internal::dense_hash_set_iterator<typename packed_container_type::pointer>;
|
||||
/*! @brief Constant random access iterator type. */
|
||||
using const_iterator = internal::dense_hash_set_iterator<typename packed_container_type::const_pointer>;
|
||||
/*! @brief Forward iterator type. */
|
||||
using local_iterator = internal::dense_hash_set_local_iterator<typename packed_container_type::pointer>;
|
||||
/*! @brief Constant forward iterator type. */
|
||||
using const_local_iterator = internal::dense_hash_set_local_iterator<typename packed_container_type::const_pointer>;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
dense_hash_set()
|
||||
: dense_hash_set(minimum_capacity) {}
|
||||
|
||||
/**
|
||||
* @brief Constructs an empty container with a given allocator.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
explicit dense_hash_set(const allocator_type &allocator)
|
||||
: dense_hash_set{minimum_capacity, hasher{}, key_equal{}, allocator} {}
|
||||
|
||||
/**
|
||||
* @brief Constructs an empty container with a given allocator and user
|
||||
* supplied minimal number of buckets.
|
||||
* @param bucket_count Minimal number of buckets.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
dense_hash_set(const size_type bucket_count, const allocator_type &allocator)
|
||||
: dense_hash_set{bucket_count, hasher{}, key_equal{}, allocator} {}
|
||||
|
||||
/**
|
||||
* @brief Constructs an empty container with a given allocator, hash
|
||||
* function and user supplied minimal number of buckets.
|
||||
* @param bucket_count Minimal number of buckets.
|
||||
* @param hash Hash function to use.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
dense_hash_set(const size_type bucket_count, const hasher &hash, const allocator_type &allocator)
|
||||
: dense_hash_set{bucket_count, hash, key_equal{}, allocator} {}
|
||||
|
||||
/**
|
||||
* @brief Constructs an empty container with a given allocator, hash
|
||||
* function, compare function and user supplied minimal number of buckets.
|
||||
* @param bucket_count Minimal number of buckets.
|
||||
* @param hash Hash function to use.
|
||||
* @param equal Compare function to use.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
explicit dense_hash_set(const size_type bucket_count, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type())
|
||||
: sparse{allocator, hash},
|
||||
packed{allocator, equal},
|
||||
threshold{default_threshold} {
|
||||
rehash(bucket_count);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
* @param other The instance to copy from.
|
||||
*/
|
||||
dense_hash_set(const dense_hash_set &other)
|
||||
: dense_hash_set{other, alloc_traits::select_on_container_copy_construction(other.get_allocator())} {}
|
||||
|
||||
/**
|
||||
* @brief Allocator-extended copy constructor.
|
||||
* @param other The instance to copy from.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
dense_hash_set(const dense_hash_set &other, const allocator_type &allocator)
|
||||
: sparse{sparse_container_type{other.sparse.first(), allocator}, other.sparse.second()},
|
||||
// cannot copy the container directly due to a nasty issue of apple clang :(
|
||||
packed{packed_container_type{other.packed.first().begin(), other.packed.first().end(), allocator}, other.packed.second()},
|
||||
threshold{other.threshold} {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Default move constructor.
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
dense_hash_set(dense_hash_set &&other) ENTT_NOEXCEPT = default;
|
||||
|
||||
/**
|
||||
* @brief Allocator-extended move constructor.
|
||||
* @param other The instance to move from.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
dense_hash_set(dense_hash_set &&other, const allocator_type &allocator) ENTT_NOEXCEPT
|
||||
: sparse{sparse_container_type{std::move(other.sparse.first()), allocator}, std::move(other.sparse.second())},
|
||||
// cannot move the container directly due to a nasty issue of apple clang :(
|
||||
packed{packed_container_type{std::make_move_iterator(other.packed.first().begin()), std::make_move_iterator(other.packed.first().end()), allocator}, std::move(other.packed.second())},
|
||||
threshold{other.threshold} {}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
~dense_hash_set() = default;
|
||||
|
||||
/**
|
||||
* @brief Copy assignment operator.
|
||||
* @param other The instance to copy from.
|
||||
* @return This container.
|
||||
*/
|
||||
dense_hash_set &operator=(const dense_hash_set &other) {
|
||||
threshold = other.threshold;
|
||||
sparse.first().clear();
|
||||
packed.first().clear();
|
||||
rehash(other.bucket_count());
|
||||
packed.first().reserve(other.packed.first().size());
|
||||
insert(other.cbegin(), other.cend());
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Default move assignment operator.
|
||||
* @param other The instance to move from.
|
||||
* @return This container.
|
||||
*/
|
||||
dense_hash_set &operator=(dense_hash_set &&other) ENTT_NOEXCEPT = default;
|
||||
|
||||
/**
|
||||
* @brief Returns the associated allocator.
|
||||
* @return The associated allocator.
|
||||
*/
|
||||
[[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT {
|
||||
return sparse.first().get_allocator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first instance of the internal array.
|
||||
* If the array is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @return An iterator to the first instance of the internal array.
|
||||
*/
|
||||
[[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT {
|
||||
return packed.first().data();
|
||||
}
|
||||
|
||||
/*! @copydoc cbegin */
|
||||
[[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT {
|
||||
return cbegin();
|
||||
}
|
||||
|
||||
/*! @copydoc begin */
|
||||
[[nodiscard]] iterator begin() ENTT_NOEXCEPT {
|
||||
return packed.first().data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last instance
|
||||
* of the internal array. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @return An iterator to the element following the last instance of the
|
||||
* internal array.
|
||||
*/
|
||||
[[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT {
|
||||
return packed.first().data() + size();
|
||||
}
|
||||
|
||||
/*! @copydoc cend */
|
||||
[[nodiscard]] const_iterator end() const ENTT_NOEXCEPT {
|
||||
return cend();
|
||||
}
|
||||
|
||||
/*! @copydoc end */
|
||||
[[nodiscard]] iterator end() ENTT_NOEXCEPT {
|
||||
return packed.first().data() + size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether a container is empty.
|
||||
* @return True if the container is empty, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool empty() const ENTT_NOEXCEPT {
|
||||
return packed.first().empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of elements in a container.
|
||||
* @return Number of elements in a container.
|
||||
*/
|
||||
[[nodiscard]] size_type size() const ENTT_NOEXCEPT {
|
||||
return packed.first().size();
|
||||
}
|
||||
|
||||
/*! @brief Clears the container. */
|
||||
void clear() ENTT_NOEXCEPT {
|
||||
sparse.first().clear();
|
||||
packed.first().clear();
|
||||
rehash(0u);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Inserts an element into the container, if it does not exist.
|
||||
* @param value An element to insert into the container.
|
||||
* @return A pair consisting of an iterator to the inserted element (or to
|
||||
* the element that prevented the insertion) and a bool denoting whether the
|
||||
* insertion took place.
|
||||
*/
|
||||
std::pair<iterator, bool> insert(const value_type &value) {
|
||||
return emplace(value);
|
||||
}
|
||||
|
||||
/*! @copydoc insert */
|
||||
std::pair<iterator, bool> insert(value_type &&value) {
|
||||
return emplace(std::move(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Inserts elements into the container, if they do not exist.
|
||||
* @tparam It Type of input iterator.
|
||||
* @param first An iterator to the first element of the range of elements.
|
||||
* @param last An iterator past the last element of the range of elements.
|
||||
*/
|
||||
template<typename It>
|
||||
void insert(It first, It last) {
|
||||
for(; first != last; ++first) {
|
||||
emplace(*first);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructs an element in-place, if it does not exist.
|
||||
* @tparam Args Types of arguments to forward to the constructor of the
|
||||
* element.
|
||||
* @param args Arguments to forward to the constructor of the element.
|
||||
* @return A pair consisting of an iterator to the inserted element (or to
|
||||
* the element that prevented the insertion) and a bool denoting whether the
|
||||
* insertion took place.
|
||||
*/
|
||||
template<typename... Args>
|
||||
std::pair<iterator, bool> emplace(Args &&...args) {
|
||||
if constexpr(((sizeof...(Args) == 1u) && ... && std::is_same_v<std::remove_const_t<std::remove_reference_t<Args>>, value_type>)) {
|
||||
return get_or_emplace(std::forward<Args>(args)...);
|
||||
} else {
|
||||
return get_or_emplace(value_type{std::forward<Args>(args)...});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes an element from a given position.
|
||||
* @param pos An iterator to the element to remove.
|
||||
* @return An iterator following the removed element.
|
||||
*/
|
||||
iterator erase(const_iterator pos) {
|
||||
const auto dist = std::distance(cbegin(), pos);
|
||||
erase(*pos);
|
||||
return begin() + dist;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes the given elements from a container.
|
||||
* @param first An iterator to the first element of the range of elements.
|
||||
* @param last An iterator past the last element of the range of elements.
|
||||
* @return An iterator following the last removed element.
|
||||
*/
|
||||
iterator erase(const_iterator first, const_iterator last) {
|
||||
const auto dist = std::distance(cbegin(), first);
|
||||
|
||||
for(auto rfirst = std::make_reverse_iterator(last), rlast = std::make_reverse_iterator(first); rfirst != rlast; ++rfirst) {
|
||||
erase(*rfirst);
|
||||
}
|
||||
|
||||
return dist > static_cast<decltype(dist)>(size()) ? end() : (begin() + dist);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes the element associated with a given value.
|
||||
* @param value Value of an element to remove.
|
||||
* @return Number of elements removed (either 0 or 1).
|
||||
*/
|
||||
size_type erase(const value_type &value) {
|
||||
return do_erase(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Exchanges the contents with those of a given container.
|
||||
* @param other Container to exchange the content with.
|
||||
*/
|
||||
void swap(dense_hash_set &other) {
|
||||
using std::swap;
|
||||
swap(sparse, other.sparse);
|
||||
swap(packed, other.packed);
|
||||
swap(threshold, other.threshold);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds an element with a given value.
|
||||
* @param value Value of an element to search for.
|
||||
* @return An iterator to an element with the given value. If no such
|
||||
* element is found, a past-the-end iterator is returned.
|
||||
*/
|
||||
[[nodiscard]] iterator find(const value_type &value) {
|
||||
return constrained_find(value, bucket(value));
|
||||
}
|
||||
|
||||
/*! @copydoc find */
|
||||
[[nodiscard]] const_iterator find(const value_type &value) const {
|
||||
return constrained_find(value, bucket(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds an element that compares _equivalent_ to a given value.
|
||||
* @tparam Other Type of an element to search for.
|
||||
* @param value Value of an element to search for.
|
||||
* @return An iterator to an element with the given value. If no such
|
||||
* element is found, a past-the-end iterator is returned.
|
||||
*/
|
||||
template<typename Other>
|
||||
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, iterator>>
|
||||
find(const Other &value) {
|
||||
return constrained_find(value, bucket(value));
|
||||
}
|
||||
|
||||
/*! @copydoc find */
|
||||
template<typename Other>
|
||||
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, const_iterator>>
|
||||
find(const Other &value) const {
|
||||
return constrained_find(value, bucket(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the container contains an element with a given value.
|
||||
* @param value Value of an element to search for.
|
||||
* @return True if there is such an element, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool contains(const value_type &value) const {
|
||||
return (find(value) != cend());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the container contains an element that compares
|
||||
* _equivalent_ to a given value.
|
||||
* @tparam Other Type of an element to search for.
|
||||
* @param value Value of an element to search for.
|
||||
* @return True if there is such an element, false otherwise.
|
||||
*/
|
||||
template<typename Other>
|
||||
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, bool>>
|
||||
contains(const Other &value) const {
|
||||
return (find(value) != cend());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning of a given bucket.
|
||||
* @param index An index of a bucket to access.
|
||||
* @return An iterator to the beginning of the given bucket.
|
||||
*/
|
||||
[[nodiscard]] const_local_iterator cbegin(const size_type index) const {
|
||||
return {packed.first().data(), sparse.first()[index]};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning of a given bucket.
|
||||
* @param index An index of a bucket to access.
|
||||
* @return An iterator to the beginning of the given bucket.
|
||||
*/
|
||||
[[nodiscard]] const_local_iterator begin(const size_type index) const {
|
||||
return cbegin(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning of a given bucket.
|
||||
* @param index An index of a bucket to access.
|
||||
* @return An iterator to the beginning of the given bucket.
|
||||
*/
|
||||
[[nodiscard]] local_iterator begin(const size_type index) {
|
||||
return {packed.first().data(), sparse.first()[index]};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end of a given bucket.
|
||||
* @param index An index of a bucket to access.
|
||||
* @return An iterator to the end of the given bucket.
|
||||
*/
|
||||
[[nodiscard]] const_local_iterator cend([[maybe_unused]] const size_type index) const {
|
||||
return {packed.first().data(), std::numeric_limits<size_type>::max()};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end of a given bucket.
|
||||
* @param index An index of a bucket to access.
|
||||
* @return An iterator to the end of the given bucket.
|
||||
*/
|
||||
[[nodiscard]] const_local_iterator end([[maybe_unused]] const size_type index) const {
|
||||
return cend(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end of a given bucket.
|
||||
* @param index An index of a bucket to access.
|
||||
* @return An iterator to the end of the given bucket.
|
||||
*/
|
||||
[[nodiscard]] local_iterator end([[maybe_unused]] const size_type index) {
|
||||
return {packed.first().data(), std::numeric_limits<size_type>::max()};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of buckets.
|
||||
* @return The number of buckets.
|
||||
*/
|
||||
[[nodiscard]] size_type bucket_count() const {
|
||||
return sparse.first().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the maximum number of buckets.
|
||||
* @return The maximum number of buckets.
|
||||
*/
|
||||
[[nodiscard]] size_type max_bucket_count() const {
|
||||
return sparse.first().max_size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of elements in a given bucket.
|
||||
* @param index The index of the bucket to examine.
|
||||
* @return The number of elements in the given bucket.
|
||||
*/
|
||||
[[nodiscard]] size_type bucket_size(const size_type index) const {
|
||||
return static_cast<size_type>(std::distance(begin(index), end(index)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the bucket for a given element.
|
||||
* @param value The value of the element to examine.
|
||||
* @return The bucket for the given element.
|
||||
*/
|
||||
[[nodiscard]] size_type bucket(const value_type &value) const {
|
||||
return hash_to_bucket(sparse.second()(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the average number of elements per bucket.
|
||||
* @return The average number of elements per bucket.
|
||||
*/
|
||||
[[nodiscard]] float load_factor() const {
|
||||
return size() / static_cast<float>(bucket_count());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the maximum average number of elements per bucket.
|
||||
* @return The maximum average number of elements per bucket.
|
||||
*/
|
||||
[[nodiscard]] float max_load_factor() const {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the desired maximum average number of elements per bucket.
|
||||
* @param value A desired maximum average number of elements per bucket.
|
||||
*/
|
||||
void max_load_factor(const float value) {
|
||||
ENTT_ASSERT(value > 0.f, "Invalid load factor");
|
||||
threshold = value;
|
||||
rehash(0u);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reserves at least the specified number of buckets and regenerates
|
||||
* the hash table.
|
||||
* @param count New number of buckets.
|
||||
*/
|
||||
void rehash(const size_type count) {
|
||||
auto value = (std::max)(count, minimum_capacity);
|
||||
value = (std::max)(value, static_cast<size_type>(size() / max_load_factor()));
|
||||
|
||||
if(const auto sz = next_power_of_two(value); sz != bucket_count()) {
|
||||
sparse.first().resize(sz);
|
||||
std::fill(sparse.first().begin(), sparse.first().end(), std::numeric_limits<size_type>::max());
|
||||
|
||||
for(size_type pos{}, last = size(); pos < last; ++pos) {
|
||||
const auto index = bucket(packed.first()[pos].element);
|
||||
packed.first()[pos].next = std::exchange(sparse.first()[index], pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reserves space for at least the specified number of elements and
|
||||
* regenerates the hash table.
|
||||
* @param count New number of elements.
|
||||
*/
|
||||
void reserve(const size_type count) {
|
||||
packed.first().reserve(count);
|
||||
rehash(static_cast<size_type>(std::ceil(count / max_load_factor())));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the function used to hash the elements.
|
||||
* @return The function used to hash the elements.
|
||||
*/
|
||||
[[nodiscard]] hasher hash_function() const {
|
||||
return sparse.second();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the function used to compare elements for equality.
|
||||
* @return The function used to compare elements for equality.
|
||||
*/
|
||||
[[nodiscard]] key_equal key_eq() const {
|
||||
return packed.second();
|
||||
}
|
||||
|
||||
private:
|
||||
compressed_pair<sparse_container_type, hasher> sparse;
|
||||
compressed_pair<packed_container_type, key_equal> packed;
|
||||
float threshold;
|
||||
};
|
||||
|
||||
} // namespace entt
|
||||
|
||||
#endif
|
||||
@@ -13,6 +13,13 @@ template<
|
||||
typename = std::allocator<std::pair<const Key, Type>>>
|
||||
class dense_hash_map;
|
||||
|
||||
}
|
||||
template<
|
||||
typename Type,
|
||||
typename = std::hash<Type>,
|
||||
typename = std::equal_to<Type>,
|
||||
typename = std::allocator<Type>>
|
||||
class dense_hash_set;
|
||||
|
||||
} // namespace entt
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "config/version.h"
|
||||
#include "container/dense_hash_map.hpp"
|
||||
#include "container/dense_hash_set.hpp"
|
||||
#include "core/algorithm.hpp"
|
||||
#include "core/any.hpp"
|
||||
#include "core/attribute.h"
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include "../container/dense_hash_map.hpp"
|
||||
#include "../container/dense_hash_set.hpp"
|
||||
#include "meta.hpp"
|
||||
#include "type_traits.hpp"
|
||||
|
||||
@@ -251,13 +252,22 @@ struct meta_associative_container_traits<std::unordered_set<Key, Args...>>
|
||||
|
||||
/**
|
||||
* @brief Meta associative container traits for `dense_hash_map`s of any type.
|
||||
* @tparam Key The key type of elements.
|
||||
* @tparam Value The value type of elements.
|
||||
* @tparam Key The key type of the elements.
|
||||
* @tparam Type The value type of the elements.
|
||||
* @tparam Args Other arguments.
|
||||
*/
|
||||
template<typename Key, typename Value, typename... Args>
|
||||
struct meta_associative_container_traits<dense_hash_map<Key, Value, Args...>>
|
||||
: internal::basic_meta_associative_container_traits<dense_hash_map<Key, Value, Args...>> {};
|
||||
template<typename Key, typename Type, typename... Args>
|
||||
struct meta_associative_container_traits<dense_hash_map<Key, Type, Args...>>
|
||||
: internal::basic_meta_associative_container_traits<dense_hash_map<Key, Type, Args...>> {};
|
||||
|
||||
/**
|
||||
* @brief Meta associative container traits for `dense_hash_set`s of any type.
|
||||
* @tparam Type The value type of the elements.
|
||||
* @tparam Args Other arguments.
|
||||
*/
|
||||
template<typename Type, typename... Args>
|
||||
struct meta_associative_container_traits<dense_hash_set<Type, Args...>>
|
||||
: internal::basic_meta_associative_container_traits<dense_hash_set<Type, Args...>> {};
|
||||
|
||||
} // namespace entt
|
||||
|
||||
|
||||
@@ -162,6 +162,7 @@ endif()
|
||||
# Test container
|
||||
|
||||
SETUP_BASIC_TEST(dense_hash_map entt/container/dense_hash_map.cpp)
|
||||
SETUP_BASIC_TEST(dense_hash_set entt/container/dense_hash_set.cpp)
|
||||
|
||||
# Test core
|
||||
|
||||
|
||||
811
test/entt/container/dense_hash_set.cpp
Normal file
811
test/entt/container/dense_hash_set.cpp
Normal file
@@ -0,0 +1,811 @@
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/container/dense_hash_set.hpp>
|
||||
#include <entt/core/memory.hpp>
|
||||
#include <entt/core/utility.hpp>
|
||||
|
||||
struct transparent_equal_to {
|
||||
using is_transparent = void;
|
||||
|
||||
template<typename Type, typename Other>
|
||||
constexpr std::enable_if_t<std::is_convertible_v<Other, Type>, bool>
|
||||
operator()(const Type &lhs, const Other &rhs) const {
|
||||
return lhs == static_cast<Type>(rhs);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(DenseHashSet, Functionalities) {
|
||||
entt::dense_hash_set<std::size_t, entt::identity, transparent_equal_to> set;
|
||||
|
||||
ASSERT_NO_THROW([[maybe_unused]] auto alloc = set.get_allocator());
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.load_factor(), 0.f);
|
||||
ASSERT_EQ(set.max_load_factor(), .875f);
|
||||
|
||||
set.max_load_factor(.9f);
|
||||
|
||||
ASSERT_EQ(set.max_load_factor(), .9f);
|
||||
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
|
||||
ASSERT_EQ(set.cbegin(), set.cend());
|
||||
|
||||
ASSERT_NE(set.max_bucket_count(), 0u);
|
||||
ASSERT_EQ(set.bucket_count(), 8u);
|
||||
ASSERT_EQ(set.bucket_size(3u), 0u);
|
||||
|
||||
ASSERT_EQ(set.bucket(0), 0u);
|
||||
ASSERT_EQ(set.bucket(3), 3u);
|
||||
ASSERT_EQ(set.bucket(8), 0u);
|
||||
ASSERT_EQ(set.bucket(10), 2u);
|
||||
|
||||
ASSERT_EQ(set.begin(1u), set.end(1u));
|
||||
ASSERT_EQ(std::as_const(set).begin(1u), std::as_const(set).end(1u));
|
||||
ASSERT_EQ(set.cbegin(1u), set.cend(1u));
|
||||
|
||||
ASSERT_FALSE(set.contains(42));
|
||||
ASSERT_FALSE(set.contains(4.2));
|
||||
|
||||
ASSERT_EQ(set.find(42), set.end());
|
||||
ASSERT_EQ(set.find(4.2), set.end());
|
||||
ASSERT_EQ(std::as_const(set).find(42), set.cend());
|
||||
ASSERT_EQ(std::as_const(set).find(4.2), set.cend());
|
||||
|
||||
ASSERT_EQ(set.hash_function()(42), 42);
|
||||
ASSERT_TRUE(set.key_eq()(42, 42));
|
||||
|
||||
set.emplace(0u);
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
|
||||
ASSERT_NE(set.begin(), set.end());
|
||||
ASSERT_NE(std::as_const(set).begin(), std::as_const(set).end());
|
||||
ASSERT_NE(set.cbegin(), set.cend());
|
||||
|
||||
ASSERT_TRUE(set.contains(0u));
|
||||
ASSERT_EQ(set.bucket(0u), 0u);
|
||||
|
||||
set.clear();
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
|
||||
ASSERT_EQ(set.cbegin(), set.cend());
|
||||
|
||||
ASSERT_FALSE(set.contains(0u));
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, Contructors) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_hash_set<int> set;
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
|
||||
|
||||
set = entt::dense_hash_set<int>{std::allocator<int>{}};
|
||||
set = entt::dense_hash_set<int>{2u * minimum_bucket_count, std::allocator<float>{}};
|
||||
set = entt::dense_hash_set<int>{4u * minimum_bucket_count, std::hash<int>(), std::allocator<double>{}};
|
||||
|
||||
set.emplace(3);
|
||||
|
||||
entt::dense_hash_set<int> temp{set, set.get_allocator()};
|
||||
entt::dense_hash_set<int> other{std::move(temp), set.get_allocator()};
|
||||
|
||||
ASSERT_EQ(other.size(), 1u);
|
||||
ASSERT_EQ(other.size(), 1u);
|
||||
ASSERT_EQ(set.bucket_count(), 4u * minimum_bucket_count);
|
||||
ASSERT_EQ(other.bucket_count(), 4u * minimum_bucket_count);
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, Copy) {
|
||||
entt::dense_hash_set<std::size_t, entt::identity> set;
|
||||
set.max_load_factor(set.max_load_factor() - .05f);
|
||||
set.emplace(3u);
|
||||
|
||||
entt::dense_hash_set<std::size_t, entt::identity> other{set};
|
||||
|
||||
ASSERT_TRUE(set.contains(3u));
|
||||
ASSERT_TRUE(other.contains(3u));
|
||||
ASSERT_EQ(set.max_load_factor(), other.max_load_factor());
|
||||
|
||||
set.emplace(1u);
|
||||
set.emplace(11u);
|
||||
other.emplace(0u);
|
||||
other = set;
|
||||
|
||||
ASSERT_TRUE(other.contains(3u));
|
||||
ASSERT_TRUE(other.contains(1u));
|
||||
ASSERT_TRUE(other.contains(11u));
|
||||
ASSERT_FALSE(other.contains(0u));
|
||||
|
||||
ASSERT_EQ(other.bucket(3u), set.bucket(11u));
|
||||
ASSERT_EQ(other.bucket(3u), other.bucket(11u));
|
||||
ASSERT_EQ(*other.begin(3u), *set.begin(3u));
|
||||
ASSERT_EQ(*other.begin(3u), 11u);
|
||||
ASSERT_EQ((*++other.begin(3u)), 3u);
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, Move) {
|
||||
entt::dense_hash_set<std::size_t, entt::identity> set;
|
||||
set.max_load_factor(set.max_load_factor() - .05f);
|
||||
set.emplace(3u);
|
||||
|
||||
entt::dense_hash_set<std::size_t, entt::identity> other{std::move(set)};
|
||||
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_TRUE(other.contains(3u));
|
||||
ASSERT_EQ(set.max_load_factor(), other.max_load_factor());
|
||||
|
||||
set = other;
|
||||
set.emplace(1u);
|
||||
set.emplace(11u);
|
||||
other.emplace(0u);
|
||||
other = std::move(set);
|
||||
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_TRUE(other.contains(3u));
|
||||
ASSERT_TRUE(other.contains(1u));
|
||||
ASSERT_TRUE(other.contains(11u));
|
||||
ASSERT_FALSE(other.contains(0u));
|
||||
|
||||
ASSERT_EQ(other.bucket(3u), other.bucket(11u));
|
||||
ASSERT_EQ(*other.begin(3u), 11u);
|
||||
ASSERT_EQ(*++other.begin(3u), 3u);
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, Iterator) {
|
||||
using iterator = typename entt::dense_hash_set<int>::iterator;
|
||||
|
||||
static_assert(std::is_same_v<iterator::value_type, int>);
|
||||
static_assert(std::is_same_v<iterator::pointer, const int *>);
|
||||
static_assert(std::is_same_v<iterator::reference, const int &>);
|
||||
|
||||
entt::dense_hash_set<int> set;
|
||||
set.emplace(3);
|
||||
|
||||
iterator end{set.begin()};
|
||||
iterator begin{};
|
||||
begin = set.end();
|
||||
std::swap(begin, end);
|
||||
|
||||
ASSERT_EQ(begin, set.begin());
|
||||
ASSERT_EQ(end, set.end());
|
||||
ASSERT_NE(begin, end);
|
||||
|
||||
ASSERT_EQ(begin++, set.begin());
|
||||
ASSERT_EQ(begin--, set.end());
|
||||
|
||||
ASSERT_EQ(begin + 1, set.end());
|
||||
ASSERT_EQ(end - 1, set.begin());
|
||||
|
||||
ASSERT_EQ(++begin, set.end());
|
||||
ASSERT_EQ(--begin, set.begin());
|
||||
|
||||
ASSERT_EQ(begin += 1, set.end());
|
||||
ASSERT_EQ(begin -= 1, set.begin());
|
||||
|
||||
ASSERT_EQ(begin + (end - begin), set.end());
|
||||
ASSERT_EQ(begin - (begin - end), set.end());
|
||||
|
||||
ASSERT_EQ(end - (end - begin), set.begin());
|
||||
ASSERT_EQ(end + (begin - end), set.begin());
|
||||
|
||||
ASSERT_EQ(begin[0u], *set.begin().operator->());
|
||||
ASSERT_EQ(begin[0u], *set.begin());
|
||||
|
||||
ASSERT_LT(begin, end);
|
||||
ASSERT_LE(begin, set.begin());
|
||||
|
||||
ASSERT_GT(end, begin);
|
||||
ASSERT_GE(end, set.end());
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, ConstIterator) {
|
||||
using iterator = typename entt::dense_hash_set<int>::const_iterator;
|
||||
|
||||
static_assert(std::is_same_v<iterator::value_type, int>);
|
||||
static_assert(std::is_same_v<iterator::pointer, const int *>);
|
||||
static_assert(std::is_same_v<iterator::reference, const int &>);
|
||||
|
||||
entt::dense_hash_set<int> set;
|
||||
set.emplace(3);
|
||||
|
||||
iterator cend{set.cbegin()};
|
||||
iterator cbegin{};
|
||||
cbegin = set.cend();
|
||||
std::swap(cbegin, cend);
|
||||
|
||||
ASSERT_EQ(cbegin, set.cbegin());
|
||||
ASSERT_EQ(cend, set.cend());
|
||||
ASSERT_NE(cbegin, cend);
|
||||
|
||||
ASSERT_EQ(cbegin++, set.cbegin());
|
||||
ASSERT_EQ(cbegin--, set.cend());
|
||||
|
||||
ASSERT_EQ(cbegin + 1, set.cend());
|
||||
ASSERT_EQ(cend - 1, set.cbegin());
|
||||
|
||||
ASSERT_EQ(++cbegin, set.cend());
|
||||
ASSERT_EQ(--cbegin, set.cbegin());
|
||||
|
||||
ASSERT_EQ(cbegin += 1, set.cend());
|
||||
ASSERT_EQ(cbegin -= 1, set.cbegin());
|
||||
|
||||
ASSERT_EQ(cbegin + (cend - cbegin), set.cend());
|
||||
ASSERT_EQ(cbegin - (cbegin - cend), set.cend());
|
||||
|
||||
ASSERT_EQ(cend - (cend - cbegin), set.cbegin());
|
||||
ASSERT_EQ(cend + (cbegin - cend), set.cbegin());
|
||||
|
||||
ASSERT_EQ(cbegin[0u], *set.cbegin().operator->());
|
||||
ASSERT_EQ(cbegin[0u], *set.cbegin());
|
||||
|
||||
ASSERT_LT(cbegin, cend);
|
||||
ASSERT_LE(cbegin, set.cbegin());
|
||||
|
||||
ASSERT_GT(cend, cbegin);
|
||||
ASSERT_GE(cend, set.cend());
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, IteratorConversion) {
|
||||
entt::dense_hash_set<int> set;
|
||||
set.emplace(3);
|
||||
|
||||
typename entt::dense_hash_set<int, int>::iterator it = set.begin();
|
||||
typename entt::dense_hash_set<int, int>::const_iterator cit = it;
|
||||
|
||||
static_assert(std::is_same_v<decltype(*it), const int &>);
|
||||
static_assert(std::is_same_v<decltype(*cit), const int &>);
|
||||
|
||||
ASSERT_EQ(*it, 3);
|
||||
ASSERT_EQ(*it.operator->(), 3);
|
||||
ASSERT_EQ(it.operator->(), cit.operator->());
|
||||
ASSERT_EQ(*it, *cit);
|
||||
|
||||
ASSERT_EQ(it - cit, 0);
|
||||
ASSERT_EQ(cit - it, 0);
|
||||
ASSERT_LE(it, cit);
|
||||
ASSERT_LE(cit, it);
|
||||
ASSERT_GE(it, cit);
|
||||
ASSERT_GE(cit, it);
|
||||
ASSERT_EQ(it, cit);
|
||||
ASSERT_NE(++cit, it);
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, Insert) {
|
||||
entt::dense_hash_set<int> set;
|
||||
typename entt::dense_hash_set<int>::iterator it;
|
||||
bool result;
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.find(0), set.end());
|
||||
ASSERT_FALSE(set.contains(0));
|
||||
|
||||
int value{1};
|
||||
std::tie(it, result) = set.insert(std::as_const(value));
|
||||
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
ASSERT_EQ(it, --set.end());
|
||||
ASSERT_TRUE(set.contains(1));
|
||||
ASSERT_NE(set.find(1), set.end());
|
||||
ASSERT_EQ(*it, 1);
|
||||
|
||||
std::tie(it, result) = set.insert(value);
|
||||
|
||||
ASSERT_FALSE(result);
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
ASSERT_EQ(it, --set.end());
|
||||
ASSERT_EQ(*it, 1);
|
||||
|
||||
std::tie(it, result) = set.insert(3);
|
||||
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_EQ(set.size(), 2u);
|
||||
ASSERT_EQ(it, --set.end());
|
||||
ASSERT_TRUE(set.contains(3));
|
||||
ASSERT_NE(set.find(3), set.end());
|
||||
ASSERT_EQ(*it, 3);
|
||||
|
||||
std::tie(it, result) = set.insert(3);
|
||||
|
||||
ASSERT_FALSE(result);
|
||||
ASSERT_EQ(set.size(), 2u);
|
||||
ASSERT_EQ(it, --set.end());
|
||||
ASSERT_EQ(*it, 3);
|
||||
|
||||
int range[2u]{7, 9};
|
||||
set.insert(std::begin(range), std::end(range));
|
||||
|
||||
ASSERT_EQ(set.size(), 4u);
|
||||
ASSERT_TRUE(set.contains(7));
|
||||
ASSERT_NE(set.find(9), set.end());
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, InsertRehash) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_hash_set<std::size_t, entt::identity> set;
|
||||
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
|
||||
|
||||
for(std::size_t next{}; next < minimum_bucket_count; ++next) {
|
||||
ASSERT_TRUE(set.insert(next).second);
|
||||
}
|
||||
|
||||
ASSERT_EQ(set.size(), minimum_bucket_count);
|
||||
ASSERT_GT(set.bucket_count(), minimum_bucket_count);
|
||||
ASSERT_TRUE(set.contains(minimum_bucket_count / 2u));
|
||||
ASSERT_EQ(set.bucket(minimum_bucket_count / 2u), minimum_bucket_count / 2u);
|
||||
ASSERT_FALSE(set.contains(minimum_bucket_count));
|
||||
|
||||
ASSERT_TRUE(set.insert(minimum_bucket_count).second);
|
||||
|
||||
ASSERT_EQ(set.size(), minimum_bucket_count + 1u);
|
||||
ASSERT_EQ(set.bucket_count(), minimum_bucket_count * 2u);
|
||||
ASSERT_TRUE(set.contains(minimum_bucket_count / 2u));
|
||||
ASSERT_EQ(set.bucket(minimum_bucket_count / 2u), minimum_bucket_count / 2u);
|
||||
ASSERT_TRUE(set.contains(minimum_bucket_count));
|
||||
|
||||
for(std::size_t next{}; next <= minimum_bucket_count; ++next) {
|
||||
ASSERT_TRUE(set.contains(next));
|
||||
ASSERT_EQ(set.bucket(next), next);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, InsertSameBucket) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_hash_set<std::size_t, entt::identity> set;
|
||||
|
||||
for(std::size_t next{}; next < minimum_bucket_count; ++next) {
|
||||
ASSERT_EQ(set.cbegin(next), set.cend(next));
|
||||
}
|
||||
|
||||
ASSERT_TRUE(set.insert(1u).second);
|
||||
ASSERT_TRUE(set.insert(9u).second);
|
||||
|
||||
ASSERT_EQ(set.size(), 2u);
|
||||
ASSERT_TRUE(set.contains(1u));
|
||||
ASSERT_NE(set.find(9u), set.end());
|
||||
ASSERT_EQ(set.bucket(1u), 1u);
|
||||
ASSERT_EQ(set.bucket(9u), 1u);
|
||||
ASSERT_EQ(set.bucket_size(1u), 2u);
|
||||
ASSERT_EQ(set.cbegin(6u), set.cend(6u));
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, Emplace) {
|
||||
entt::dense_hash_set<int> set;
|
||||
typename entt::dense_hash_set<int>::iterator it;
|
||||
bool result;
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.find(0), set.end());
|
||||
ASSERT_FALSE(set.contains(0));
|
||||
|
||||
std::tie(it, result) = set.emplace();
|
||||
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
ASSERT_EQ(it, --set.end());
|
||||
ASSERT_TRUE(set.contains(0));
|
||||
ASSERT_NE(set.find(0), set.end());
|
||||
ASSERT_EQ(*it, 0);
|
||||
|
||||
std::tie(it, result) = set.emplace();
|
||||
|
||||
ASSERT_FALSE(result);
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
ASSERT_EQ(it, --set.end());
|
||||
ASSERT_EQ(*it, 0);
|
||||
|
||||
std::tie(it, result) = set.emplace(1);
|
||||
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_EQ(set.size(), 2u);
|
||||
ASSERT_EQ(it, --set.end());
|
||||
ASSERT_TRUE(set.contains(1));
|
||||
ASSERT_NE(set.find(1), set.end());
|
||||
ASSERT_EQ(*it, 1);
|
||||
|
||||
std::tie(it, result) = set.emplace(1);
|
||||
|
||||
ASSERT_FALSE(result);
|
||||
ASSERT_EQ(set.size(), 2u);
|
||||
ASSERT_EQ(it, --set.end());
|
||||
ASSERT_EQ(*it, 1);
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, EmplaceRehash) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_hash_set<std::size_t, entt::identity> set;
|
||||
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
|
||||
|
||||
for(std::size_t next{}; next < minimum_bucket_count; ++next) {
|
||||
ASSERT_TRUE(set.emplace(next).second);
|
||||
ASSERT_LE(set.load_factor(), set.max_load_factor());
|
||||
}
|
||||
|
||||
ASSERT_EQ(set.size(), minimum_bucket_count);
|
||||
ASSERT_GT(set.bucket_count(), minimum_bucket_count);
|
||||
ASSERT_TRUE(set.contains(minimum_bucket_count / 2u));
|
||||
ASSERT_EQ(set.bucket(minimum_bucket_count / 2u), minimum_bucket_count / 2u);
|
||||
ASSERT_FALSE(set.contains(minimum_bucket_count));
|
||||
|
||||
ASSERT_TRUE(set.emplace(minimum_bucket_count).second);
|
||||
|
||||
ASSERT_EQ(set.size(), minimum_bucket_count + 1u);
|
||||
ASSERT_EQ(set.bucket_count(), minimum_bucket_count * 2u);
|
||||
ASSERT_TRUE(set.contains(minimum_bucket_count / 2u));
|
||||
ASSERT_EQ(set.bucket(minimum_bucket_count / 2u), minimum_bucket_count / 2u);
|
||||
ASSERT_TRUE(set.contains(minimum_bucket_count));
|
||||
|
||||
for(std::size_t next{}; next <= minimum_bucket_count; ++next) {
|
||||
ASSERT_TRUE(set.contains(next));
|
||||
ASSERT_EQ(set.bucket(next), next);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, EmplaceSameBucket) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_hash_set<std::size_t, entt::identity> set;
|
||||
|
||||
for(std::size_t next{}; next < minimum_bucket_count; ++next) {
|
||||
ASSERT_EQ(set.cbegin(next), set.cend(next));
|
||||
}
|
||||
|
||||
ASSERT_TRUE(set.emplace(1u).second);
|
||||
ASSERT_TRUE(set.emplace(9u).second);
|
||||
|
||||
ASSERT_EQ(set.size(), 2u);
|
||||
ASSERT_TRUE(set.contains(1u));
|
||||
ASSERT_NE(set.find(9u), set.end());
|
||||
ASSERT_EQ(set.bucket(1u), 1u);
|
||||
ASSERT_EQ(set.bucket(9u), 1u);
|
||||
ASSERT_EQ(set.bucket_size(1u), 2u);
|
||||
ASSERT_EQ(set.cbegin(6u), set.cend(6u));
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, Erase) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_hash_set<std::size_t, entt::identity> set;
|
||||
|
||||
for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) {
|
||||
set.emplace(next);
|
||||
}
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), 2 * minimum_bucket_count);
|
||||
ASSERT_EQ(set.size(), minimum_bucket_count + 1u);
|
||||
|
||||
for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) {
|
||||
ASSERT_TRUE(set.contains(next));
|
||||
}
|
||||
|
||||
auto it = set.erase(++set.begin());
|
||||
it = set.erase(it, it + 1);
|
||||
|
||||
ASSERT_EQ(*--set.end(), 6u);
|
||||
ASSERT_EQ(set.erase(6u), 1u);
|
||||
ASSERT_EQ(set.erase(6u), 0u);
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), 2 * minimum_bucket_count);
|
||||
ASSERT_EQ(set.size(), minimum_bucket_count + 1u - 3u);
|
||||
|
||||
ASSERT_EQ(it, ++set.begin());
|
||||
ASSERT_EQ(*it, 7u);
|
||||
ASSERT_EQ(*--set.end(), 5u);
|
||||
|
||||
for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) {
|
||||
if(next == 1u || next == 8u || next == 6u) {
|
||||
ASSERT_FALSE(set.contains(next));
|
||||
ASSERT_EQ(set.bucket_size(next), 0u);
|
||||
} else {
|
||||
ASSERT_TRUE(set.contains(next));
|
||||
ASSERT_EQ(set.bucket(next), next);
|
||||
ASSERT_EQ(set.bucket_size(next), 1u);
|
||||
}
|
||||
}
|
||||
|
||||
set.erase(set.begin(), set.end());
|
||||
|
||||
for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) {
|
||||
ASSERT_FALSE(set.contains(next));
|
||||
}
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), 2 * minimum_bucket_count);
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, EraseFromBucket) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_hash_set<std::size_t, entt::identity> set;
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
|
||||
for(std::size_t next{}; next < 4u; ++next) {
|
||||
ASSERT_TRUE(set.emplace(2u * minimum_bucket_count * next).second);
|
||||
ASSERT_TRUE(set.emplace(2u * minimum_bucket_count * next + 2u).second);
|
||||
ASSERT_TRUE(set.emplace(2u * minimum_bucket_count * (next + 1u) - 1u).second);
|
||||
}
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), 2u * minimum_bucket_count);
|
||||
ASSERT_EQ(set.size(), 12u);
|
||||
|
||||
ASSERT_EQ(set.bucket_size(0u), 4u);
|
||||
ASSERT_EQ(set.bucket_size(2u), 4u);
|
||||
ASSERT_EQ(set.bucket_size(15u), 4u);
|
||||
|
||||
set.erase(set.end() - 3, set.end());
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), 2u * minimum_bucket_count);
|
||||
ASSERT_EQ(set.size(), 9u);
|
||||
|
||||
ASSERT_EQ(set.bucket_size(0u), 3u);
|
||||
ASSERT_EQ(set.bucket_size(2u), 3u);
|
||||
ASSERT_EQ(set.bucket_size(15u), 3u);
|
||||
|
||||
for(std::size_t next{}; next < 3u; ++next) {
|
||||
ASSERT_TRUE(set.contains(2u * minimum_bucket_count * next));
|
||||
ASSERT_EQ(set.bucket(2u * minimum_bucket_count * next), 0u);
|
||||
|
||||
ASSERT_TRUE(set.contains(2u * minimum_bucket_count * next + 2u));
|
||||
ASSERT_EQ(set.bucket(2u * minimum_bucket_count * next + 2u), 2u);
|
||||
|
||||
ASSERT_TRUE(set.contains(2u * minimum_bucket_count * (next + 1u) - 1u));
|
||||
ASSERT_EQ(set.bucket(2u * minimum_bucket_count * (next + 1u) - 1u), 15u);
|
||||
}
|
||||
|
||||
ASSERT_FALSE(set.contains(2u * minimum_bucket_count * 3u));
|
||||
ASSERT_FALSE(set.contains(2u * minimum_bucket_count * 3u + 2u));
|
||||
ASSERT_FALSE(set.contains(2u * minimum_bucket_count * (3u + 1u) - 1u));
|
||||
|
||||
set.erase(*++set.begin(0u));
|
||||
set.erase(*++set.begin(2u));
|
||||
set.erase(*++set.begin(15u));
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), 2u * minimum_bucket_count);
|
||||
ASSERT_EQ(set.size(), 6u);
|
||||
|
||||
ASSERT_EQ(set.bucket_size(0u), 2u);
|
||||
ASSERT_EQ(set.bucket_size(2u), 2u);
|
||||
ASSERT_EQ(set.bucket_size(15u), 2u);
|
||||
|
||||
ASSERT_FALSE(set.contains(2u * minimum_bucket_count * 1u));
|
||||
ASSERT_FALSE(set.contains(2u * minimum_bucket_count * 1u + 2u));
|
||||
ASSERT_FALSE(set.contains(2u * minimum_bucket_count * (1u + 1u) - 1u));
|
||||
|
||||
while(set.begin(15) != set.end(15u)) {
|
||||
set.erase(*set.begin(15));
|
||||
}
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), 2u * minimum_bucket_count);
|
||||
ASSERT_EQ(set.size(), 4u);
|
||||
|
||||
ASSERT_EQ(set.bucket_size(0u), 2u);
|
||||
ASSERT_EQ(set.bucket_size(2u), 2u);
|
||||
ASSERT_EQ(set.bucket_size(15u), 0u);
|
||||
|
||||
ASSERT_TRUE(set.contains(0u * minimum_bucket_count));
|
||||
ASSERT_TRUE(set.contains(0u * minimum_bucket_count + 2u));
|
||||
ASSERT_TRUE(set.contains(4u * minimum_bucket_count));
|
||||
ASSERT_TRUE(set.contains(4u * minimum_bucket_count + 2u));
|
||||
|
||||
set.erase(4u * minimum_bucket_count + 2u);
|
||||
set.erase(0u * minimum_bucket_count);
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), 2u * minimum_bucket_count);
|
||||
ASSERT_EQ(set.size(), 2u);
|
||||
|
||||
ASSERT_EQ(set.bucket_size(0u), 1u);
|
||||
ASSERT_EQ(set.bucket_size(2u), 1u);
|
||||
ASSERT_EQ(set.bucket_size(15u), 0u);
|
||||
|
||||
ASSERT_FALSE(set.contains(0u * minimum_bucket_count));
|
||||
ASSERT_TRUE(set.contains(0u * minimum_bucket_count + 2u));
|
||||
ASSERT_TRUE(set.contains(4u * minimum_bucket_count));
|
||||
ASSERT_FALSE(set.contains(4u * minimum_bucket_count + 2u));
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, Swap) {
|
||||
entt::dense_hash_set<int> set;
|
||||
entt::dense_hash_set<int> other;
|
||||
|
||||
set.emplace(0);
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_TRUE(other.empty());
|
||||
ASSERT_TRUE(set.contains(0));
|
||||
ASSERT_FALSE(other.contains(0));
|
||||
|
||||
set.swap(other);
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_FALSE(other.empty());
|
||||
ASSERT_FALSE(set.contains(0));
|
||||
ASSERT_TRUE(other.contains(0));
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, LocalIterator) {
|
||||
using iterator = typename entt::dense_hash_set<std::size_t, entt::identity>::local_iterator;
|
||||
|
||||
static_assert(std::is_same_v<iterator::value_type, std::size_t>);
|
||||
static_assert(std::is_same_v<iterator::pointer, const std::size_t *>);
|
||||
static_assert(std::is_same_v<iterator::reference, const std::size_t &>);
|
||||
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_hash_set<std::size_t, entt::identity> set;
|
||||
set.emplace(3u);
|
||||
set.emplace(3u + minimum_bucket_count);
|
||||
|
||||
iterator end{set.begin(3u)};
|
||||
iterator begin{};
|
||||
begin = set.end(3u);
|
||||
std::swap(begin, end);
|
||||
|
||||
ASSERT_EQ(begin, set.begin(3u));
|
||||
ASSERT_EQ(end, set.end(3u));
|
||||
ASSERT_NE(begin, end);
|
||||
|
||||
ASSERT_EQ(*begin.operator->(), 3u + minimum_bucket_count);
|
||||
ASSERT_EQ(*begin, 3u + minimum_bucket_count);
|
||||
|
||||
ASSERT_EQ(begin.base(), set.begin().base() + 1u);
|
||||
ASSERT_EQ(begin++, set.begin(3u));
|
||||
ASSERT_EQ(begin.base(), set.begin().base());
|
||||
ASSERT_EQ(++begin, set.end(3u));
|
||||
ASSERT_NE(begin.base(), set.end().base());
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, ConstLocalIterator) {
|
||||
using iterator = typename entt::dense_hash_set<std::size_t, entt::identity>::const_local_iterator;
|
||||
|
||||
static_assert(std::is_same_v<iterator::value_type, std::size_t>);
|
||||
static_assert(std::is_same_v<iterator::pointer, const std::size_t *>);
|
||||
static_assert(std::is_same_v<iterator::reference, const std::size_t &>);
|
||||
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_hash_set<std::size_t, entt::identity> set;
|
||||
set.emplace(3u);
|
||||
set.emplace(3u + minimum_bucket_count);
|
||||
|
||||
iterator cend{set.begin(3u)};
|
||||
iterator cbegin{};
|
||||
cbegin = set.end(3u);
|
||||
std::swap(cbegin, cend);
|
||||
|
||||
ASSERT_EQ(cbegin, set.begin(3u));
|
||||
ASSERT_EQ(cend, set.end(3u));
|
||||
ASSERT_NE(cbegin, cend);
|
||||
|
||||
ASSERT_EQ(*cbegin.operator->(), 3u + minimum_bucket_count);
|
||||
ASSERT_EQ(*cbegin, 3u + minimum_bucket_count);
|
||||
|
||||
ASSERT_EQ(cbegin.base(), set.cbegin().base() + 1u);
|
||||
ASSERT_EQ(cbegin++, set.begin(3u));
|
||||
ASSERT_EQ(cbegin.base(), set.cbegin().base());
|
||||
ASSERT_EQ(++cbegin, set.end(3u));
|
||||
ASSERT_NE(cbegin.base(), set.cend().base());
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, LocalIteratorConversion) {
|
||||
entt::dense_hash_set<int> set;
|
||||
set.emplace(3);
|
||||
|
||||
typename entt::dense_hash_set<int>::local_iterator it = set.begin(set.bucket(3));
|
||||
typename entt::dense_hash_set<int>::const_local_iterator cit = it;
|
||||
|
||||
static_assert(std::is_same_v<decltype(*it), const int &>);
|
||||
static_assert(std::is_same_v<decltype(*cit), const int &>);
|
||||
|
||||
ASSERT_EQ(*it, 3);
|
||||
ASSERT_EQ(*it.operator->(), 3);
|
||||
ASSERT_EQ(it.operator->(), cit.operator->());
|
||||
ASSERT_EQ(*it, *cit);
|
||||
|
||||
ASSERT_EQ(it, cit);
|
||||
ASSERT_NE(++cit, it);
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, Rehash) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_hash_set<std::size_t, entt::identity> set;
|
||||
set.emplace(32u);
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
|
||||
ASSERT_TRUE(set.contains(32u));
|
||||
ASSERT_EQ(set.bucket(32u), 0u);
|
||||
|
||||
set.rehash(12u);
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), 2u * minimum_bucket_count);
|
||||
ASSERT_TRUE(set.contains(32u));
|
||||
ASSERT_EQ(set.bucket(32u), 0u);
|
||||
|
||||
set.rehash(44u);
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), 8u * minimum_bucket_count);
|
||||
ASSERT_TRUE(set.contains(32u));
|
||||
ASSERT_EQ(set.bucket(32u), 32u);
|
||||
|
||||
set.rehash(0u);
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
|
||||
ASSERT_TRUE(set.contains(32u));
|
||||
ASSERT_EQ(set.bucket(32u), 0u);
|
||||
|
||||
for(std::size_t next{}; next < minimum_bucket_count; ++next) {
|
||||
set.emplace(next);
|
||||
}
|
||||
|
||||
ASSERT_EQ(set.size(), minimum_bucket_count + 1u);
|
||||
ASSERT_EQ(set.bucket_count(), 2u * minimum_bucket_count);
|
||||
|
||||
set.rehash(0u);
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), 2u * minimum_bucket_count);
|
||||
ASSERT_TRUE(set.contains(32u));
|
||||
|
||||
set.rehash(55u);
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), 8u * minimum_bucket_count);
|
||||
ASSERT_TRUE(set.contains(32u));
|
||||
|
||||
set.rehash(2u);
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), 2u * minimum_bucket_count);
|
||||
ASSERT_TRUE(set.contains(32u));
|
||||
ASSERT_EQ(set.bucket(32u), 0u);
|
||||
|
||||
for(std::size_t next{}; next < minimum_bucket_count; ++next) {
|
||||
ASSERT_TRUE(set.contains(next));
|
||||
ASSERT_EQ(set.bucket(next), next);
|
||||
}
|
||||
|
||||
ASSERT_EQ(set.bucket_size(0u), 2u);
|
||||
ASSERT_EQ(set.bucket_size(3u), 1u);
|
||||
|
||||
ASSERT_EQ(*set.begin(0u), 0u);
|
||||
ASSERT_EQ(*++set.begin(0u), 32u);
|
||||
|
||||
set.clear();
|
||||
set.rehash(2u);
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
|
||||
ASSERT_FALSE(set.contains(32u));
|
||||
|
||||
for(std::size_t next{}; next < minimum_bucket_count; ++next) {
|
||||
ASSERT_FALSE(set.contains(next));
|
||||
}
|
||||
|
||||
ASSERT_EQ(set.bucket_size(0u), 0u);
|
||||
ASSERT_EQ(set.bucket_size(3u), 0u);
|
||||
}
|
||||
|
||||
TEST(DenseHashSet, Reserve) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_hash_set<int> set;
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
|
||||
|
||||
set.reserve(0u);
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
|
||||
|
||||
set.reserve(minimum_bucket_count);
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), 2 * minimum_bucket_count);
|
||||
ASSERT_EQ(set.bucket_count(), entt::next_power_of_two(std::ceil(minimum_bucket_count / set.max_load_factor())));
|
||||
}
|
||||
@@ -322,6 +322,52 @@ TEST_F(MetaContainer, DenseHashMap) {
|
||||
ASSERT_EQ(view.size(), 0u);
|
||||
}
|
||||
|
||||
TEST_F(MetaContainer, DenseHashSet) {
|
||||
entt::dense_hash_set<int> set{};
|
||||
auto any = entt::forward_as_meta(set);
|
||||
auto view = any.as_associative_container();
|
||||
|
||||
set.emplace(2);
|
||||
set.emplace(3);
|
||||
set.emplace(4);
|
||||
|
||||
ASSERT_TRUE(view);
|
||||
ASSERT_TRUE(view.key_only());
|
||||
ASSERT_EQ(view.key_type(), entt::resolve<int>());
|
||||
ASSERT_EQ(view.mapped_type(), entt::meta_type{});
|
||||
ASSERT_EQ(view.value_type(), entt::resolve<int>());
|
||||
|
||||
ASSERT_EQ(view.size(), 3u);
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
|
||||
ASSERT_EQ(view.find(3)->first.cast<int>(), 3);
|
||||
|
||||
ASSERT_FALSE(view.insert(invalid_type{}));
|
||||
|
||||
ASSERT_TRUE(view.insert(.0));
|
||||
ASSERT_TRUE(view.insert(1));
|
||||
|
||||
ASSERT_EQ(view.size(), 5u);
|
||||
ASSERT_EQ(view.find(0)->first.cast<int>(), 0);
|
||||
ASSERT_EQ(view.find(1.)->first.cast<int>(), 1);
|
||||
|
||||
ASSERT_FALSE(view.erase(invalid_type{}));
|
||||
ASSERT_FALSE(view.find(invalid_type{}));
|
||||
ASSERT_EQ(view.size(), 5u);
|
||||
|
||||
ASSERT_TRUE(view.erase(0));
|
||||
ASSERT_EQ(view.size(), 4u);
|
||||
ASSERT_EQ(view.find(0), view.end());
|
||||
|
||||
ASSERT_EQ(view.find(1.f)->first.try_cast<int>(), nullptr);
|
||||
ASSERT_NE(view.find(1.)->first.try_cast<const int>(), nullptr);
|
||||
ASSERT_EQ(view.find(true)->first.cast<const int &>(), 1);
|
||||
|
||||
ASSERT_TRUE(view.erase(1.));
|
||||
ASSERT_TRUE(view.clear());
|
||||
ASSERT_EQ(view.size(), 0u);
|
||||
}
|
||||
|
||||
TEST_F(MetaContainer, ConstSequenceContainer) {
|
||||
std::vector<int> vec{};
|
||||
auto any = entt::forward_as_meta(std::as_const(vec));
|
||||
|
||||
Reference in New Issue
Block a user