osb/source/core/StarFlatHashTable.hpp
Kai Blaschke 431a9c00a5
Fixed a huge amount of Clang warnings
On Linux and macOS, using Clang to compile OpenStarbound produces about 400 MB worth of warnings during the build, making the compiler output unreadable and slowing the build down considerably.

99% of the warnings were unqualified uses of std::move and std::forward, which are now all properly qualified.

Fixed a few other minor warnings about non-virtual destructors and some uses of std::move preventing copy elision on temporary objects.

Most remaining warnings are now unused parameters.
2024-02-19 16:55:19 +01:00

558 lines
20 KiB
C++

#ifndef STAR_FLAT_HASH_TABLE_HPP
#define STAR_FLAT_HASH_TABLE_HPP
#include <vector>
#include "StarConfig.hpp"
namespace Star {
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
struct FlatHashTable {
private:
static size_t const EmptyHashValue = 0;
static size_t const EndHashValue = 1;
static size_t const FilledHashBit = (size_t)1 << (sizeof(size_t) * 8 - 1);
struct Bucket {
Bucket();
~Bucket();
Bucket(Bucket const& rhs);
Bucket(Bucket&& rhs);
Bucket& operator=(Bucket const& rhs);
Bucket& operator=(Bucket&& rhs);
void setFilled(size_t hash, Value value);
void setEmpty();
void setEnd();
Value const* valuePtr() const;
Value* valuePtr();
bool isEmpty() const;
bool isEnd() const;
union {
Value value;
};
size_t hash;
};
typedef std::vector<Bucket, typename Allocator::template rebind<Bucket>::other> Buckets;
public:
struct const_iterator {
bool operator==(const_iterator const& rhs) const;
bool operator!=(const_iterator const& rhs) const;
const_iterator& operator++();
const_iterator operator++(int);
Value const& operator*() const;
Value const* operator->() const;
Bucket const* current;
};
struct iterator {
bool operator==(iterator const& rhs) const;
bool operator!=(iterator const& rhs) const;
iterator& operator++();
iterator operator++(int);
Value& operator*() const;
Value* operator->() const;
operator const_iterator() const;
Bucket* current;
};
FlatHashTable(size_t bucketCount, GetKey const& getKey, Hash const& hash, Equals const& equal, Allocator const& alloc);
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
size_t empty() const;
size_t size() const;
void clear();
pair<iterator, bool> insert(Value value);
iterator erase(const_iterator pos);
iterator erase(const_iterator first, const_iterator last);
const_iterator find(Key const& key) const;
iterator find(Key const& key);
void reserve(size_t capacity);
Allocator getAllocator() const;
bool operator==(FlatHashTable const& rhs) const;
bool operator!=(FlatHashTable const& rhs) const;
private:
static constexpr size_t MinCapacity = 8;
static constexpr double MaxFillLevel = 0.7;
// Scans for the next bucket value that is non-empty
static Bucket* scan(Bucket* p);
static Bucket const* scan(Bucket const* p);
size_t hashBucket(size_t hash) const;
size_t bucketError(size_t current, size_t target) const;
void checkCapacity(size_t additionalCapacity);
Buckets m_buckets;
size_t m_filledCount;
GetKey m_getKey;
Hash m_hash;
Equals m_equals;
};
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::Bucket() {
this->hash = EmptyHashValue;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::~Bucket() {
if (auto s = valuePtr())
s->~Value();
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::Bucket(Bucket const& rhs) {
this->hash = rhs.hash;
if (auto o = rhs.valuePtr())
new (&this->value) Value(*o);
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::Bucket(Bucket&& rhs) {
this->hash = rhs.hash;
if (auto o = rhs.valuePtr())
new (&this->value) Value(std::move(*o));
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::operator=(Bucket const& rhs) -> Bucket& {
if (auto o = rhs.valuePtr()) {
if (auto s = valuePtr())
*s = *o;
else
new (&this->value) Value(*o);
} else {
if (auto s = valuePtr())
s->~Value();
}
this->hash = rhs.hash;
return *this;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::operator=(Bucket&& rhs) -> Bucket& {
if (auto o = rhs.valuePtr()) {
if (auto s = valuePtr())
*s = std::move(*o);
else
new (&this->value) Value(std::move(*o));
} else {
if (auto s = valuePtr())
s->~Value();
}
this->hash = rhs.hash;
return *this;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
void FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::setFilled(size_t hash, Value value) {
if (auto s = valuePtr())
*s = std::move(value);
else
new (&this->value) Value(std::move(value));
this->hash = hash | FilledHashBit;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
void FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::setEmpty() {
if (auto s = valuePtr())
s->~Value();
this->hash = EmptyHashValue;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
void FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::setEnd() {
if (auto s = valuePtr())
s->~Value();
this->hash = EndHashValue;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
Value const* FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::valuePtr() const {
if (hash & FilledHashBit)
return &this->value;
return nullptr;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
Value* FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::valuePtr() {
if (hash & FilledHashBit)
return &this->value;
return nullptr;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
bool FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::isEmpty() const {
return this->hash == EmptyHashValue;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
bool FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::isEnd() const {
return this->hash == EndHashValue;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
bool FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::const_iterator::operator==(const_iterator const& rhs) const {
return current == rhs.current;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
bool FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::const_iterator::operator!=(const_iterator const& rhs) const {
return current != rhs.current;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::const_iterator::operator++() -> const_iterator& {
current = scan(++current);
return *this;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::const_iterator::operator++(int) -> const_iterator {
const_iterator copy(*this);
operator++();
return copy;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::const_iterator::operator*() const -> Value const& {
return *operator->();
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::const_iterator::operator->() const -> Value const* {
return current->valuePtr();
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
bool FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::iterator::operator==(iterator const& rhs) const {
return current == rhs.current;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
bool FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::iterator::operator!=(iterator const& rhs) const {
return current != rhs.current;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::iterator::operator++() -> iterator& {
current = scan(++current);
return *this;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::iterator::operator++(int) -> iterator {
iterator copy(*this);
operator++();
return copy;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::iterator::operator*() const -> Value& {
return *operator->();
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::iterator::operator->() const -> Value* {
return current->valuePtr();
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::iterator::operator typename FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::const_iterator() const {
return const_iterator{current};
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::FlatHashTable(size_t bucketCount,
GetKey const& getKey, Hash const& hash, Equals const& equal, Allocator const& alloc)
: m_buckets(alloc), m_filledCount(0), m_getKey(getKey),
m_hash(hash), m_equals(equal) {
if (bucketCount != 0)
checkCapacity(bucketCount);
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::begin() -> iterator {
if (m_buckets.empty())
return end();
return iterator{scan(m_buckets.data())};
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::end() -> iterator {
return iterator{m_buckets.data() + m_buckets.size() - 1};
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::begin() const -> const_iterator {
return const_cast<FlatHashTable*>(this)->begin();
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::end() const -> const_iterator {
return const_cast<FlatHashTable*>(this)->end();
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
size_t FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::empty() const {
return m_filledCount == 0;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
size_t FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::size() const {
return m_filledCount;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
void FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::clear() {
if (m_buckets.empty())
return;
for (size_t i = 0; i < m_buckets.size() - 1; ++i)
m_buckets[i].setEmpty();
m_filledCount = 0;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::insert(Value value) -> pair<iterator, bool> {
if (m_buckets.empty() || m_filledCount + 1 > (m_buckets.size() - 1) * MaxFillLevel)
checkCapacity(1);
size_t hash = m_hash(m_getKey(value)) | FilledHashBit;
size_t targetBucket = hashBucket(hash);
size_t currentBucket = targetBucket;
size_t insertedBucket = NPos;
while (true) {
auto& target = m_buckets[currentBucket];
if (auto entryValue = target.valuePtr()) {
if (target.hash == hash && m_equals(m_getKey(*entryValue), m_getKey(value)))
return make_pair(iterator{m_buckets.data() + currentBucket}, false);
size_t entryTargetBucket = hashBucket(target.hash);
size_t entryError = bucketError(currentBucket, entryTargetBucket);
size_t addError = bucketError(currentBucket, targetBucket);
if (addError > entryError) {
if (insertedBucket == NPos)
insertedBucket = currentBucket;
swap(value, *entryValue);
swap(hash, target.hash);
targetBucket = entryTargetBucket;
}
currentBucket = hashBucket(currentBucket + 1);
} else {
target.setFilled(hash, std::move(value));
++m_filledCount;
if (insertedBucket == NPos)
insertedBucket = currentBucket;
return make_pair(iterator{m_buckets.data() + insertedBucket}, true);
}
}
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::erase(const_iterator pos) -> iterator {
size_t bucketIndex = pos.current - m_buckets.data();
size_t currentBucketIndex = bucketIndex;
auto currentBucket = &m_buckets[currentBucketIndex];
while (true) {
size_t nextBucketIndex = hashBucket(currentBucketIndex + 1);
auto nextBucket = &m_buckets[nextBucketIndex];
if (auto nextPtr = nextBucket->valuePtr()) {
if (bucketError(nextBucketIndex, nextBucket->hash) > 0) {
currentBucket->hash = nextBucket->hash;
*currentBucket->valuePtr() = std::move(*nextPtr);
currentBucketIndex = nextBucketIndex;
currentBucket = nextBucket;
} else {
break;
}
} else {
break;
}
}
m_buckets[currentBucketIndex].setEmpty();
--m_filledCount;
return iterator{scan(m_buckets.data() + bucketIndex)};
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::erase(const_iterator first, const_iterator last) -> iterator {
while (first != last)
first = erase(first);
return iterator{(Bucket*)first.current};
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::find(Key const& key) const -> const_iterator {
return const_cast<FlatHashTable*>(this)->find(key);
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::find(Key const& key) -> iterator {
if (m_buckets.empty())
return end();
size_t hash = m_hash(key) | FilledHashBit;
size_t targetBucket = hashBucket(hash);
size_t currentBucket = targetBucket;
while (true) {
auto& bucket = m_buckets[currentBucket];
if (auto value = bucket.valuePtr()) {
if (bucket.hash == hash && m_equals(m_getKey(*value), key))
return iterator{m_buckets.data() + currentBucket};
size_t entryError = bucketError(currentBucket, bucket.hash);
size_t findError = bucketError(currentBucket, targetBucket);
if (findError > entryError)
return end();
currentBucket = hashBucket(currentBucket + 1);
} else {
return end();
}
}
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
void FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::reserve(size_t capacity) {
if (capacity > m_filledCount)
checkCapacity(capacity - m_filledCount);
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
Allocator FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::getAllocator() const {
return m_buckets.get_allocator();
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
bool FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::operator==(FlatHashTable const& rhs) const {
if (size() != rhs.size())
return false;
auto i = begin();
auto j = rhs.begin();
auto e = end();
while (i != e) {
if (*i != *j)
return false;
++i;
++j;
}
return true;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
bool FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::operator!=(FlatHashTable const& rhs) const {
return !operator==(rhs);
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
constexpr size_t FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::MinCapacity;
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
constexpr double FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::MaxFillLevel;
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::scan(Bucket* p) -> Bucket* {
while (p->isEmpty())
++p;
return p;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::scan(Bucket const* p) -> Bucket const* {
while (p->isEmpty())
++p;
return p;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
size_t FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::hashBucket(size_t hash) const {
return hash & (m_buckets.size() - 2);
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
size_t FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::bucketError(size_t current, size_t target) const {
return hashBucket(current - target);
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
void FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::checkCapacity(size_t additionalCapacity) {
if (additionalCapacity == 0)
return;
size_t newSize;
if (!m_buckets.empty())
newSize = m_buckets.size() - 1;
else
newSize = MinCapacity;
while ((double)(m_filledCount + additionalCapacity) / (double)newSize > MaxFillLevel)
newSize *= 2;
if (newSize == m_buckets.size() - 1)
return;
Buckets oldBuckets;
swap(m_buckets, oldBuckets);
// Leave an extra end entry when allocating buckets, so that iterators are
// simpler and can simply iterate until they find something that is not an
// empty entry.
m_buckets.resize(newSize + 1);
while (m_buckets.capacity() > newSize * 2 + 1) {
newSize *= 2;
m_buckets.resize(newSize + 1);
}
m_buckets[newSize].setEnd();
m_filledCount = 0;
for (auto& entry : oldBuckets) {
if (auto ptr = entry.valuePtr())
insert(std::move(*ptr));
}
}
}
#endif