BAN: Cleanup HashSet

Add a concept for HashSetFindable instead of manually specifying the
requries expression everywhere.

Allow HashSetFindable also for `remove` and `contains`.

Remove non-const return values from iterator; you should never modify
the hashed value in place.

Don't require key to be move assignable, move construction is enough.
This commit is contained in:
2026-04-25 22:00:39 +03:00
parent cf2e8ffaff
commit a63818ec33
2 changed files with 18 additions and 19 deletions

View File

@@ -13,7 +13,7 @@ namespace BAN
Entry& operator*() Entry& operator*()
{ {
return m_iterator.operator*(); return const_cast<Entry&>(m_iterator.operator*());
} }
const Entry& operator*() const const Entry& operator*() const
{ {
@@ -22,7 +22,7 @@ namespace BAN
Entry* operator->() Entry* operator->()
{ {
return m_iterator.operator->(); return const_cast<Entry*>(m_iterator.operator->());
} }
const Entry* operator->() const const Entry* operator->() const
{ {
@@ -153,7 +153,7 @@ namespace BAN
{ {
if (auto it = m_hash_set.find(key); it != m_hash_set.end()) if (auto it = m_hash_set.find(key); it != m_hash_set.end())
{ {
it->value = T(BAN::forward<Args>(args)...); const_cast<T&>(it->value) = T(BAN::forward<Args>(args)...);
return iterator(it); return iterator(it);
} }

View File

@@ -15,22 +15,12 @@ namespace BAN
public: public:
HashSetIterator() = default; HashSetIterator() = default;
T& operator*()
{
ASSERT(m_bucket);
return *m_bucket->element();
}
const T& operator*() const const T& operator*() const
{ {
ASSERT(m_bucket); ASSERT(m_bucket);
return *m_bucket->element(); return *m_bucket->element();
} }
T* operator->()
{
ASSERT(m_bucket);
return m_bucket->element();
}
const T* operator->() const const T* operator->() const
{ {
ASSERT(m_bucket); ASSERT(m_bucket);
@@ -81,6 +71,12 @@ namespace BAN
friend HashSet; friend HashSet;
}; };
namespace detail
{
template<typename T, typename U, typename HASH, typename COMP>
concept HashSetFindable = requires(const U& a, const T& b) { COMP()(a, b); HASH()(b); };
}
template<typename T, typename HASH = BAN::hash<T>, typename COMP = BAN::equal<T>> template<typename T, typename HASH = BAN::hash<T>, typename COMP = BAN::equal<T>>
class HashSet class HashSet
{ {
@@ -166,7 +162,8 @@ namespace BAN
{ {
if (!COMP()(*bucket.element(), value)) if (!COMP()(*bucket.element(), value))
continue; continue;
*bucket.element() = BAN::move(value); bucket.element()->~T();
new (bucket.element()) T(BAN::move(value));
} }
else else
{ {
@@ -185,7 +182,8 @@ namespace BAN
} }
} }
void remove(const T& value) template<detail::HashSetFindable<T, HASH, COMP> U>
void remove(const U& value)
{ {
if (auto it = find(value); it != end()) if (auto it = find(value); it != end())
remove(it); remove(it);
@@ -202,13 +200,13 @@ namespace BAN
return iterator(&bucket); return iterator(&bucket);
} }
template<typename U> template<detail::HashSetFindable<T, HASH, COMP> U>
iterator find(const U& value) iterator find(const U& value)
{ {
return iterator(const_cast<Bucket*>(find_impl(value).m_bucket)); return iterator(const_cast<Bucket*>(find_impl(value).m_bucket));
} }
template<typename U> template<detail::HashSetFindable<T, HASH, COMP> U>
const_iterator find(const U& value) const const_iterator find(const U& value) const
{ {
return find_impl(value); return find_impl(value);
@@ -240,7 +238,8 @@ namespace BAN
return {}; return {};
} }
bool contains(const T& value) const template<detail::HashSetFindable<T, HASH, COMP> U>
bool contains(const U& value) const
{ {
return find(value) != end(); return find(value) != end();
} }
@@ -295,7 +294,7 @@ namespace BAN
return {}; return {};
} }
template<typename U> requires requires(const T& a, const U& b) { COMP()(a, b); HASH()(b); } template<detail::HashSetFindable<T, HASH, COMP> U>
const_iterator find_impl(const U& value) const const_iterator find_impl(const U& value) const
{ {
if (m_capacity == 0) if (m_capacity == 0)