2023-03-06 14:42:04 +01:00
|
|
|
// SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
// author: Max Kellermann <max.kellermann@gmail.com>
|
2022-11-11 21:04:03 +01:00
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
2022-12-01 15:02:39 +01:00
|
|
|
#include "Concepts.hxx"
|
2022-11-11 21:04:03 +01:00
|
|
|
#include "IntrusiveList.hxx"
|
|
|
|
|
|
|
|
#include <algorithm> // for std::all_of()
|
|
|
|
#include <array>
|
|
|
|
#include <numeric> // for std::accumulate()
|
|
|
|
|
|
|
|
template<IntrusiveHookMode mode=IntrusiveHookMode::NORMAL>
|
|
|
|
struct IntrusiveHashSetHook {
|
|
|
|
using SiblingsHook = IntrusiveListHook<mode>;
|
|
|
|
|
|
|
|
SiblingsHook intrusive_hash_set_siblings;
|
2023-01-30 12:16:39 +01:00
|
|
|
|
|
|
|
void unlink() noexcept {
|
|
|
|
intrusive_hash_set_siblings.unlink();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool is_linked() const noexcept {
|
|
|
|
return intrusive_hash_set_siblings.is_linked();
|
|
|
|
}
|
2022-11-11 21:04:03 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For classes which embed #IntrusiveHashSetHook as base class.
|
|
|
|
*/
|
|
|
|
template<typename T>
|
|
|
|
struct IntrusiveHashSetBaseHookTraits {
|
2023-08-02 23:25:50 +02:00
|
|
|
/* a never-called helper function which is used by _Cast() */
|
|
|
|
template<IntrusiveHookMode mode>
|
|
|
|
static constexpr IntrusiveHashSetHook<mode> _Identity(const IntrusiveHashSetHook<mode> &) noexcept;
|
|
|
|
|
|
|
|
/* another never-called helper function which "calls"
|
|
|
|
_Identity(), implicitly casting the item to the
|
|
|
|
IntrusiveHashSetHook specialization; we use this to detect
|
|
|
|
which IntrusiveHashSetHook specialization is used */
|
|
|
|
template<typename U>
|
|
|
|
static constexpr auto _Cast(const U &u) noexcept {
|
|
|
|
return decltype(_Identity(u))();
|
|
|
|
}
|
|
|
|
|
2022-11-11 21:04:03 +01:00
|
|
|
template<typename U>
|
2023-08-02 23:25:50 +02:00
|
|
|
using Hook = decltype(_Cast(std::declval<U>()));
|
2022-11-11 21:04:03 +01:00
|
|
|
|
|
|
|
static constexpr T *Cast(Hook<T> *node) noexcept {
|
|
|
|
return static_cast<T *>(node);
|
|
|
|
}
|
|
|
|
|
|
|
|
static constexpr auto &ToHook(T &t) noexcept {
|
|
|
|
return static_cast<Hook<T> &>(t);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For classes which embed #IntrusiveListHook as member.
|
|
|
|
*/
|
|
|
|
template<auto member>
|
|
|
|
struct IntrusiveHashSetMemberHookTraits {
|
|
|
|
using T = MemberPointerContainerType<decltype(member)>;
|
|
|
|
using _Hook = MemberPointerType<decltype(member)>;
|
|
|
|
|
|
|
|
template<typename Dummy>
|
|
|
|
using Hook = _Hook;
|
2022-12-01 10:54:33 +01:00
|
|
|
|
|
|
|
static constexpr T *Cast(Hook<T> *node) noexcept {
|
|
|
|
return &ContainerCast(*node, member);
|
|
|
|
}
|
|
|
|
|
|
|
|
static constexpr auto &ToHook(T &t) noexcept {
|
|
|
|
return t.*member;
|
|
|
|
}
|
2022-11-11 21:04:03 +01:00
|
|
|
};
|
|
|
|
|
2023-08-02 22:04:00 +02:00
|
|
|
/**
|
|
|
|
* @param GetKey a function object which extracts the "key" part of an
|
|
|
|
* item
|
|
|
|
*/
|
|
|
|
template<typename Hash, typename Equal,
|
|
|
|
typename GetKey=std::identity>
|
2023-08-02 22:11:57 +02:00
|
|
|
struct IntrusiveHashSetOperators {
|
|
|
|
using hasher = Hash;
|
|
|
|
using key_equal = Equal;
|
|
|
|
|
|
|
|
[[no_unique_address]]
|
|
|
|
Hash hash;
|
|
|
|
|
|
|
|
[[no_unique_address]]
|
|
|
|
Equal equal;
|
2023-08-02 22:04:00 +02:00
|
|
|
|
|
|
|
[[no_unique_address]]
|
|
|
|
GetKey get_key;
|
2023-08-02 22:11:57 +02:00
|
|
|
};
|
|
|
|
|
2022-11-11 21:04:03 +01:00
|
|
|
/**
|
|
|
|
* A hash table implementation which stores pointers to items which
|
|
|
|
* have an embedded #IntrusiveHashSetHook. The actual table is
|
|
|
|
* embedded with a compile-time fixed size in this object.
|
2023-08-02 22:11:57 +02:00
|
|
|
*
|
|
|
|
* @param Operators a class which contains functions `hash` and
|
|
|
|
* `equal`
|
2022-11-11 21:04:03 +01:00
|
|
|
*/
|
|
|
|
template<typename T, std::size_t table_size,
|
2023-08-02 22:11:57 +02:00
|
|
|
typename Operators,
|
2022-11-11 21:04:03 +01:00
|
|
|
typename HookTraits=IntrusiveHashSetBaseHookTraits<T>,
|
|
|
|
bool constant_time_size=false>
|
|
|
|
class IntrusiveHashSet {
|
|
|
|
[[no_unique_address]]
|
|
|
|
OptionalCounter<constant_time_size> counter;
|
|
|
|
|
|
|
|
[[no_unique_address]]
|
2023-08-02 22:11:57 +02:00
|
|
|
Operators ops;
|
2022-11-11 21:04:03 +01:00
|
|
|
|
2022-11-21 09:13:58 +01:00
|
|
|
struct BucketHookTraits {
|
2022-11-11 21:04:03 +01:00
|
|
|
template<typename U>
|
|
|
|
using HashSetHook = typename HookTraits::template Hook<U>;
|
|
|
|
|
|
|
|
template<typename U>
|
|
|
|
using ListHook = IntrusiveListMemberHookTraits<&HashSetHook<U>::intrusive_hash_set_siblings>;
|
|
|
|
|
|
|
|
template<typename U>
|
|
|
|
using Hook = typename HashSetHook<U>::SiblingsHook;
|
|
|
|
|
|
|
|
static constexpr T *Cast(IntrusiveListNode *node) noexcept {
|
|
|
|
auto *hook = ListHook<T>::Cast(node);
|
|
|
|
return HookTraits::Cast(hook);
|
|
|
|
}
|
|
|
|
|
|
|
|
static constexpr auto &ToHook(T &t) noexcept {
|
|
|
|
auto &hook = HookTraits::ToHook(t);
|
|
|
|
return hook.intrusive_hash_set_siblings;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-11-21 09:13:58 +01:00
|
|
|
using Bucket = IntrusiveList<T, BucketHookTraits>;
|
|
|
|
std::array<Bucket, table_size> table;
|
2022-11-11 21:04:03 +01:00
|
|
|
|
2022-11-21 09:13:58 +01:00
|
|
|
using bucket_iterator = typename Bucket::iterator;
|
2022-11-30 09:04:56 +01:00
|
|
|
using const_bucket_iterator = typename Bucket::const_iterator;
|
2022-11-11 21:04:03 +01:00
|
|
|
|
|
|
|
public:
|
|
|
|
using value_type = T;
|
|
|
|
using reference = T &;
|
|
|
|
using const_reference = const T &;
|
|
|
|
using pointer = T *;
|
|
|
|
using const_pointer = const T *;
|
|
|
|
using size_type = std::size_t;
|
|
|
|
|
2023-08-02 22:11:57 +02:00
|
|
|
using hasher = typename Operators::hasher;
|
|
|
|
using key_equal = typename Operators::key_equal;
|
2022-11-21 09:10:00 +01:00
|
|
|
|
2022-11-11 21:04:03 +01:00
|
|
|
[[nodiscard]]
|
|
|
|
IntrusiveHashSet() noexcept = default;
|
|
|
|
|
2022-11-21 09:10:00 +01:00
|
|
|
[[nodiscard]]
|
|
|
|
constexpr const hasher &hash_function() const noexcept {
|
2023-08-02 22:11:57 +02:00
|
|
|
return ops.hash;
|
2022-11-21 09:10:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
[[nodiscard]]
|
2022-11-30 09:46:53 +01:00
|
|
|
constexpr const key_equal &key_eq() const noexcept {
|
2023-08-02 22:11:57 +02:00
|
|
|
return ops.equal;
|
2022-11-21 09:10:00 +01:00
|
|
|
}
|
|
|
|
|
2022-11-11 21:04:03 +01:00
|
|
|
[[nodiscard]]
|
2022-11-30 09:46:53 +01:00
|
|
|
constexpr bool empty() const noexcept {
|
2022-11-11 21:04:03 +01:00
|
|
|
if constexpr (constant_time_size)
|
|
|
|
return size() == 0;
|
|
|
|
else
|
2022-11-21 09:13:58 +01:00
|
|
|
return std::all_of(table.begin(), table.end(), [](const auto &bucket){
|
|
|
|
return bucket.empty();
|
2022-11-11 21:04:03 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
[[nodiscard]]
|
2022-11-30 09:46:53 +01:00
|
|
|
constexpr size_type size() const noexcept {
|
2022-11-11 21:04:03 +01:00
|
|
|
if constexpr (constant_time_size)
|
|
|
|
return counter;
|
|
|
|
else
|
2022-11-21 09:13:58 +01:00
|
|
|
return std::accumulate(table.begin(), table.end(), size_type{}, [](std::size_t n, const auto &bucket){
|
|
|
|
return n + bucket.size();
|
2022-11-11 21:04:03 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
constexpr void clear() noexcept {
|
|
|
|
for (auto &i : table)
|
|
|
|
i.clear();
|
|
|
|
|
|
|
|
counter.reset();
|
|
|
|
}
|
|
|
|
|
2022-12-01 15:02:39 +01:00
|
|
|
constexpr void clear_and_dispose(Disposer<value_type> auto disposer) noexcept {
|
2022-11-11 21:04:03 +01:00
|
|
|
for (auto &i : table)
|
|
|
|
i.clear_and_dispose(disposer);
|
|
|
|
|
|
|
|
counter.reset();
|
|
|
|
}
|
|
|
|
|
2023-08-02 15:01:17 +02:00
|
|
|
/**
|
|
|
|
* Remove and dispose all items matching the given predicate.
|
|
|
|
*
|
|
|
|
* @return the number of removed items
|
|
|
|
*/
|
|
|
|
std::size_t remove_and_dispose_if(std::predicate<const_reference> auto pred,
|
2022-12-01 15:02:39 +01:00
|
|
|
Disposer<value_type> auto disposer) noexcept {
|
2023-08-02 15:01:17 +02:00
|
|
|
std::size_t n = 0;
|
2022-11-21 10:46:11 +01:00
|
|
|
for (auto &bucket : table)
|
2023-08-02 15:01:17 +02:00
|
|
|
n += bucket.remove_and_dispose_if(pred, disposer);
|
|
|
|
counter -= n;
|
|
|
|
return n;
|
2022-11-21 10:46:11 +01:00
|
|
|
}
|
|
|
|
|
2023-03-22 15:13:30 +01:00
|
|
|
/**
|
|
|
|
* Remove and dispose all items with the specified key.
|
2023-08-02 15:01:17 +02:00
|
|
|
*
|
|
|
|
* @return the number of removed items
|
2023-03-22 15:13:30 +01:00
|
|
|
*/
|
2023-08-02 15:01:17 +02:00
|
|
|
constexpr std::size_t remove_and_dispose_key(const auto &key,
|
|
|
|
Disposer<value_type> auto disposer) noexcept {
|
2023-03-22 15:13:30 +01:00
|
|
|
auto &bucket = GetBucket(key);
|
2023-08-02 15:01:17 +02:00
|
|
|
std::size_t n = bucket.remove_and_dispose_if([this, &key](const auto &item){
|
2023-08-02 22:04:00 +02:00
|
|
|
return ops.equal(key, ops.get_key(item));
|
2023-03-22 15:13:30 +01:00
|
|
|
}, disposer);
|
2023-08-02 15:01:17 +02:00
|
|
|
counter -= n;
|
|
|
|
return n;
|
2023-03-22 15:13:30 +01:00
|
|
|
}
|
|
|
|
|
2023-08-02 15:01:17 +02:00
|
|
|
constexpr std::size_t remove_and_dispose_key_if(const auto &key,
|
|
|
|
std::predicate<const_reference> auto pred,
|
|
|
|
Disposer<value_type> auto disposer) noexcept {
|
2022-12-01 16:18:40 +01:00
|
|
|
auto &bucket = GetBucket(key);
|
2023-08-02 15:01:17 +02:00
|
|
|
std::size_t n = bucket.remove_and_dispose_if([this, &key, &pred](const auto &item){
|
2023-08-02 22:04:00 +02:00
|
|
|
return ops.equal(key, ops.get_key(item)) && pred(item);
|
2022-12-01 18:52:52 +01:00
|
|
|
}, disposer);
|
2023-08-02 15:01:17 +02:00
|
|
|
counter -= n;
|
|
|
|
return n;
|
2022-12-01 16:18:40 +01:00
|
|
|
}
|
|
|
|
|
2022-11-11 21:04:03 +01:00
|
|
|
[[nodiscard]]
|
2022-11-21 09:13:58 +01:00
|
|
|
static constexpr bucket_iterator iterator_to(reference item) noexcept {
|
|
|
|
return Bucket::iterator_to(item);
|
2022-11-11 21:04:03 +01:00
|
|
|
}
|
|
|
|
|
2023-07-21 13:34:22 +02:00
|
|
|
/**
|
|
|
|
* Prepare insertion of a new item. If the key already
|
|
|
|
* exists, return an iterator to the existing item and
|
|
|
|
* `false`. If the key does not exist, return an iterator to
|
|
|
|
* the bucket where the new item may be inserted using
|
|
|
|
* insert() and `true`.
|
|
|
|
*/
|
2022-11-11 21:04:03 +01:00
|
|
|
[[nodiscard]] [[gnu::pure]]
|
2022-11-21 09:13:58 +01:00
|
|
|
constexpr std::pair<bucket_iterator, bool> insert_check(const auto &key) noexcept {
|
|
|
|
auto &bucket = GetBucket(key);
|
|
|
|
for (auto &i : bucket)
|
2023-08-02 22:04:00 +02:00
|
|
|
if (ops.equal(key, ops.get_key(i)))
|
2022-11-21 09:13:58 +01:00
|
|
|
return {bucket.iterator_to(i), false};
|
2022-11-11 21:04:03 +01:00
|
|
|
|
2023-07-21 13:34:02 +02:00
|
|
|
/* bucket.end() is a pointer to the bucket's list
|
|
|
|
head, a stable value that is guaranteed to be still
|
|
|
|
valid when insert_commit() gets called
|
|
|
|
eventually */
|
|
|
|
return {bucket.end(), true};
|
2022-11-11 21:04:03 +01:00
|
|
|
}
|
|
|
|
|
2023-07-21 13:34:22 +02:00
|
|
|
/**
|
|
|
|
* Finish the insertion if insert_check() has returned true.
|
|
|
|
*
|
|
|
|
* @param bucket the bucket returned by insert_check()
|
|
|
|
*/
|
2023-07-21 13:34:13 +02:00
|
|
|
constexpr void insert_commit(bucket_iterator bucket, reference item) noexcept {
|
2022-11-11 21:04:03 +01:00
|
|
|
++counter;
|
2023-07-21 13:34:02 +02:00
|
|
|
|
|
|
|
/* using insert_after() so the new item gets inserted
|
|
|
|
at the front of the bucket list */
|
2023-08-02 22:04:00 +02:00
|
|
|
GetBucket(ops.get_key(item)).insert_after(bucket, item);
|
2022-11-11 21:04:03 +01:00
|
|
|
}
|
|
|
|
|
2023-07-21 13:34:22 +02:00
|
|
|
/**
|
|
|
|
* Insert a new item without checking whether the key already
|
|
|
|
* exists.
|
|
|
|
*/
|
2022-11-11 21:04:03 +01:00
|
|
|
constexpr void insert(reference item) noexcept {
|
|
|
|
++counter;
|
2023-08-02 22:04:00 +02:00
|
|
|
GetBucket(ops.get_key(item)).push_front(item);
|
2022-11-11 21:04:03 +01:00
|
|
|
}
|
|
|
|
|
2022-11-21 09:13:58 +01:00
|
|
|
constexpr bucket_iterator erase(bucket_iterator i) noexcept {
|
2022-11-11 21:04:03 +01:00
|
|
|
--counter;
|
2023-08-02 22:04:00 +02:00
|
|
|
return GetBucket(ops.get_key(*i)).erase(i);
|
2022-11-11 21:04:03 +01:00
|
|
|
}
|
|
|
|
|
2022-11-21 09:13:58 +01:00
|
|
|
constexpr bucket_iterator erase_and_dispose(bucket_iterator i,
|
2022-12-01 15:02:39 +01:00
|
|
|
Disposer<value_type> auto disposer) noexcept {
|
2022-11-21 10:23:59 +01:00
|
|
|
auto result = erase(i);
|
|
|
|
disposer(&*i);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-11-11 21:04:03 +01:00
|
|
|
[[nodiscard]] [[gnu::pure]]
|
2022-11-21 09:13:58 +01:00
|
|
|
constexpr bucket_iterator find(const auto &key) noexcept {
|
|
|
|
auto &bucket = GetBucket(key);
|
|
|
|
for (auto &i : bucket)
|
2023-08-02 22:04:00 +02:00
|
|
|
if (ops.equal(key, ops.get_key(i)))
|
2022-11-21 09:13:58 +01:00
|
|
|
return bucket.iterator_to(i);
|
2022-11-11 21:04:03 +01:00
|
|
|
|
|
|
|
return end();
|
|
|
|
}
|
|
|
|
|
2022-11-30 09:04:56 +01:00
|
|
|
[[nodiscard]] [[gnu::pure]]
|
|
|
|
constexpr const_bucket_iterator find(const auto &key) const noexcept {
|
|
|
|
auto &bucket = GetBucket(key);
|
|
|
|
for (auto &i : bucket)
|
2023-08-02 22:04:00 +02:00
|
|
|
if (ops.equal(key, ops.get_key(i)))
|
2022-11-30 09:04:56 +01:00
|
|
|
return bucket.iterator_to(i);
|
|
|
|
|
|
|
|
return end();
|
|
|
|
}
|
|
|
|
|
2022-11-30 20:35:28 +01:00
|
|
|
/**
|
|
|
|
* Like find(), but returns an item that matches the given
|
|
|
|
* predicate. This is useful if the container can contain
|
|
|
|
* multiple items that compare equal (according to #Equal, but
|
|
|
|
* not according to #pred).
|
|
|
|
*/
|
|
|
|
[[nodiscard]] [[gnu::pure]]
|
|
|
|
constexpr bucket_iterator find_if(const auto &key,
|
2023-07-21 14:05:04 +02:00
|
|
|
std::predicate<const_reference> auto pred) noexcept {
|
2022-11-30 20:35:28 +01:00
|
|
|
auto &bucket = GetBucket(key);
|
|
|
|
for (auto &i : bucket)
|
2023-08-02 22:04:00 +02:00
|
|
|
if (ops.equal(key, ops.get_key(i)) && pred(i))
|
2022-11-30 20:35:28 +01:00
|
|
|
return bucket.iterator_to(i);
|
|
|
|
|
|
|
|
return end();
|
|
|
|
}
|
|
|
|
|
2022-12-01 14:26:02 +01:00
|
|
|
/**
|
|
|
|
* Like find_if(), but while traversing the bucket linked
|
|
|
|
* list, remove and dispose expired items.
|
|
|
|
*
|
|
|
|
* @param expired_pred returns true if an item is expired; it
|
|
|
|
* will be removed and disposed
|
|
|
|
*
|
|
|
|
* @param disposer function which will be called for items
|
|
|
|
* that were removed (because they are expired)
|
|
|
|
*
|
|
|
|
* @param match_pred returns true if the desired item was
|
|
|
|
* found
|
|
|
|
*/
|
|
|
|
[[nodiscard]] [[gnu::pure]]
|
|
|
|
constexpr bucket_iterator expire_find_if(const auto &key,
|
2023-07-21 14:05:04 +02:00
|
|
|
std::predicate<const_reference> auto expired_pred,
|
2022-12-01 14:26:02 +01:00
|
|
|
Disposer<value_type> auto disposer,
|
2023-07-21 14:05:04 +02:00
|
|
|
std::predicate<const_reference> auto match_pred) noexcept {
|
2022-12-01 14:26:02 +01:00
|
|
|
auto &bucket = GetBucket(key);
|
|
|
|
|
|
|
|
for (auto i = bucket.begin(), e = bucket.end(); i != e;) {
|
2023-08-02 22:04:00 +02:00
|
|
|
if (!ops.equal(key, ops.get_key(*i)))
|
2022-12-01 14:26:02 +01:00
|
|
|
++i;
|
|
|
|
else if (expired_pred(*i))
|
|
|
|
i = erase_and_dispose(i, disposer);
|
|
|
|
else if (match_pred(*i))
|
|
|
|
return i;
|
|
|
|
else
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
|
|
|
|
return end();
|
|
|
|
}
|
|
|
|
|
2022-11-21 09:13:58 +01:00
|
|
|
constexpr bucket_iterator end() noexcept {
|
2022-11-11 21:04:03 +01:00
|
|
|
return table.front().end();
|
|
|
|
}
|
|
|
|
|
2022-11-30 09:04:56 +01:00
|
|
|
constexpr const_bucket_iterator end() const noexcept {
|
|
|
|
return table.front().end();
|
|
|
|
}
|
|
|
|
|
2022-11-21 10:17:55 +01:00
|
|
|
constexpr void for_each(auto &&f) {
|
|
|
|
for (auto &bucket : table)
|
|
|
|
for (auto &i : bucket)
|
|
|
|
f(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
constexpr void for_each(auto &&f) const {
|
|
|
|
for (const auto &bucket : table)
|
|
|
|
for (const auto &i : bucket)
|
|
|
|
f(i);
|
|
|
|
}
|
|
|
|
|
2022-11-11 21:04:03 +01:00
|
|
|
private:
|
|
|
|
template<typename K>
|
|
|
|
[[gnu::pure]]
|
|
|
|
[[nodiscard]]
|
2022-11-21 09:13:58 +01:00
|
|
|
constexpr auto &GetBucket(K &&key) noexcept {
|
2023-08-02 22:11:57 +02:00
|
|
|
const auto h = ops.hash(std::forward<K>(key));
|
2022-11-11 21:04:03 +01:00
|
|
|
return table[h % table_size];
|
|
|
|
}
|
2022-11-30 09:04:56 +01:00
|
|
|
|
|
|
|
template<typename K>
|
|
|
|
[[gnu::pure]]
|
|
|
|
[[nodiscard]]
|
|
|
|
constexpr const auto &GetBucket(K &&key) const noexcept {
|
2023-08-02 22:11:57 +02:00
|
|
|
const auto h = ops.hash(std::forward<K>(key));
|
2022-11-30 09:04:56 +01:00
|
|
|
return table[h % table_size];
|
|
|
|
}
|
2022-11-11 21:04:03 +01:00
|
|
|
};
|