OXYGENHUB
◆ main openmsx / src / utils / hash_set.hh
20.3 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
// hash_set
2
//
3
// Written by Wouter Vermaelen, based upon HashSet/HashMap
4
// example code and ideas provided by Jacques Van Damme.
5
6
#ifndef HASH_SET_HH
7
#define HASH_SET_HH
8
9
#include "narrow.hh"
10
#include "stl.hh"
11
#include "unreachable.hh"
12
#include "xrange.hh"
13
14
#include <cassert>
15
#include <concepts>
16
#include <cstdint>
17
#include <cstdlib>
18
#include <functional>
19
#include <initializer_list>
20
#include <iterator>
21
#include <new>
22
#include <type_traits>
23
#include <utility>
24
25
namespace hash_set_impl {
26
27
struct PoolIndex {
28
	unsigned idx;
29
	[[nodiscard]] constexpr bool operator==(const PoolIndex&) const = default;
30
};
31
inline constexpr PoolIndex invalidIndex{unsigned(-1)};
32
33
// Holds the data that will be stored in the hash_set plus some extra
34
// administrative data.
35
// - value: the actual to be stored data
36
// - hash: always equal to hasher(extract(value))
37
// - nextIdx: index of next element in a single-linked-list. This is either
38
//     the list of colliding buckets in the hash, or the list of free objects
39
//     in the Pool (see below).
40
template<typename Value>
41
struct Element {
42
	Value    value;
43
	unsigned hash;
44
	PoolIndex nextIdx;
45
46
	constexpr Element() = default;
47
48
	template<typename V>
49
	constexpr Element(V&& value_, unsigned hash_, PoolIndex nextIdx_)
50
		: value(std::forward<V>(value_))
51
		, hash(hash_)
52
		, nextIdx(nextIdx_)
53
	{
54
	}
55
56
	template<typename T, typename... Args>
57
		requires(!std::same_as<Element, std::remove_cvref_t<T>>) // don't block copy-constructor
58
	explicit constexpr Element(T&& t, Args&&... args)
59
		: value(std::forward<T>(t), std::forward<Args>(args)...)
60
		// hash    left uninitialized
61
		// nextIdx left uninitialized
62
	{
63
	}
64
};
65
66
67
// Holds 'Element' objects. These objects can be in 2 states:
68
// - 'free': In that case 'nextIdx' forms a single-linked list of all
69
//     free objects. 'freeIdx_' is the head of this list. Index 'invalidIndex'
70
//     indicates the end of the list.
71
// - 'in-use': The Element object is in use by some other data structure,
72
//     the Pool class doesn't interpret any of the Element fields.
73
template<typename Value>
74
class Pool {
75
	using Elem = Element<Value>;
76
77
public:
78
	Pool() = default;
79
80
	Pool(Pool&& source) noexcept
81
		: buf_     (source.buf_)
82
		, freeIdx_ (source.freeIdx_)
83
		, capacity_(source.capacity_)
84
	{
85
		source.buf_ = nullptr;
86
		source.freeIdx_ = invalidIndex;
87
		source.capacity_ = 0;
88
	}
89
90
	Pool& operator=(Pool&& source) noexcept
91
	{
92
		buf_      = source.buf_;
93
		freeIdx_  = source.freeIdx_;
94
		capacity_ = source.capacity_;
95
		source.buf_ = nullptr;
96
		source.freeIdx_ = invalidIndex;
97
		source.capacity_ = 0;
98
		return *this;
99
	}
100
101
	~Pool()
102
	{
103
		free(buf_);
104
	}
105
106
	// Lookup Element by index. (External code) is only allowed to call this
107
	// method with indices that were earlier returned from create() and have
108
	// not yet been passed to destroy().
109
	[[nodiscard]] Elem& get(PoolIndex idx)
110
	{
111
		assert(idx.idx < capacity_);
112
		return buf_[idx.idx];
113
	}
114
	[[nodiscard]] const Elem& get(PoolIndex idx) const
115
	{
116
		assert(idx.idx < capacity_);
117
		return buf_[idx.idx];
118
	}
119
120
	// - Insert a new Element in the pool (will be created with the given
121
	//   Element constructor parameters).
122
	// - Returns an index that can be used with the get() method. Never
123
	//   returns 'invalidIndex' (so it can be used as a 'special' value).
124
	// - Internally Pool keeps a list of pre-allocated objects, when this
125
	//   list runs out Pool will automatically allocate more objects.
126
	template<typename V>
127
	[[nodiscard]] PoolIndex create(V&& value, unsigned hash, PoolIndex nextIdx)
128
	{
129
		if (freeIdx_ == invalidIndex) grow();
130
		auto idx = freeIdx_;
131
		auto& elem = get(idx);
132
		freeIdx_ = elem.nextIdx;
133
		new (&elem) Elem(std::forward<V>(value), hash, nextIdx);
134
		return idx;
135
	}
136
137
	// Destroys a previously created object (given by index). Index must
138
	// have been returned by an earlier create() call, and the same index
139
	// can only be destroyed once. It's not allowed to destroy 'invalidIndex'.
140
	void destroy(PoolIndex idx)
141
	{
142
		auto& elem = get(idx);
143
		elem.~Elem();
144
		elem.nextIdx = freeIdx_;
145
		freeIdx_ = idx;
146
	}
147
148
	// Leaves 'hash' and 'nextIdx' members uninitialized!
149
	template<typename... Args>
150
	[[nodiscard]] PoolIndex emplace(Args&&... args)
151
	{
152
		if (freeIdx_ == invalidIndex) grow();
153
		auto idx = freeIdx_;
154
		auto& elem = get(idx);
155
		freeIdx_ = elem.nextIdx;
156
		new (&elem) Elem(std::forward<Args>(args)...);
157
		return idx;
158
	}
159
160
	[[nodiscard]] unsigned capacity() const
161
	{
162
		return capacity_;
163
	}
164
165
	void reserve(unsigned count)
166
	{
167
		// note: not required to be a power-of-2
168
		if (capacity_ >= count) return;
169
		if (capacity_ != 0) {
170
			growMore(count);
171
		} else {
172
			growInitial(count);
173
		}
174
	}
175
176
	friend void swap(Pool& x, Pool& y) noexcept
177
	{
178
		using std::swap;
179
		swap(x.buf_,     y.buf_);
180
		swap(x.freeIdx_,  y.freeIdx_);
181
		swap(x.capacity_, y.capacity_);
182
	}
183
184
private:
185
	void grow()
186
	{
187
		if (capacity_ != 0) {
188
			growMore(2 * capacity_);
189
		} else {
190
			growInitial(4); // initial capacity = 4
191
		}
192
	}
193
194
	void growMore(unsigned newCapacity)
195
	{
196
		auto* oldBuf = buf_;
197
		Elem* newBuf;
198
		if constexpr (std::is_trivially_move_constructible_v<Elem> &&
199
		              std::is_trivially_copyable_v<Elem>) {
200
			newBuf = static_cast<Elem*>(realloc(oldBuf, newCapacity * sizeof(Elem)));
201
			if (!newBuf) throw std::bad_alloc();
202
		} else {
203
			newBuf = static_cast<Elem*>(malloc(newCapacity * sizeof(Elem)));
204
			if (!newBuf) throw std::bad_alloc();
205
206
			for (size_t i : xrange(capacity_)) {
207
				new (&newBuf[i]) Elem(std::move(oldBuf[i]));
208
				oldBuf[i].~Elem();
209
			}
210
			free(oldBuf);
211
		}
212
213
		for (auto i : xrange(capacity_, newCapacity - 1)) {
214
			newBuf[i].nextIdx = PoolIndex{i + 1};
215
		}
216
		newBuf[newCapacity - 1].nextIdx = invalidIndex;
217
218
		buf_ = newBuf;
219
		freeIdx_ = PoolIndex{capacity_};
220
		capacity_ = newCapacity;
221
	}
222
223
	void growInitial(unsigned newCapacity)
224
	{
225
		auto* newBuf = static_cast<Elem*>(malloc(newCapacity * sizeof(Elem)));
226
		if (!newBuf) throw std::bad_alloc();
227
228
		for (auto i : xrange(newCapacity - 1)) {
229
			newBuf[i].nextIdx = PoolIndex{i + 1};
230
		}
231
		newBuf[newCapacity - 1].nextIdx = invalidIndex;
232
233
		buf_ = newBuf;
234
		freeIdx_ = PoolIndex{0};
235
		capacity_ = newCapacity;
236
	}
237
238
private:
239
	Elem* buf_ = nullptr;
240
	PoolIndex freeIdx_ = invalidIndex; // index of a free block, 'invalidIndex' means nothing is free
241
	unsigned capacity_ = 0;
242
};
243
244
// Type-alias for the resulting type of applying Extractor on Value.
245
template<typename Value, typename Extractor>
246
using ExtractedType = typename std::remove_cvref_t<
247
	decltype(std::declval<Extractor>()(std::declval<Value>()))>;
248
249
} // namespace hash_set_impl
250
251
252
// hash_set
253
//
254
// A hash-set implementation with an STL-like interface.
255
//
256
// One important property of this implementation is that stored values are not
257
// the same as the keys that are used for hashing the values or looking them
258
// up.
259
//
260
// By default (when not specifying a 2nd template argument) the values are the
261
// same as the keys (so you get a classic hash-set). but it's possible to
262
// specify an extractor functor that extracts the key-part from the value (e.g.
263
// extract one member variable out of a bigger struct).
264
//
265
// If required it's also possible to specify custom hash and equality functors.
266
template<typename Value,
267
         typename Extractor = std::identity,
268
         typename Hasher = std::hash<hash_set_impl::ExtractedType<Value, Extractor>>,
269
         typename Equal = std::equal_to<>>
270
class hash_set
271
{
272
protected:
273
	using PoolIndex = hash_set_impl::PoolIndex;
274
	static constexpr auto invalidIndex = hash_set_impl::invalidIndex;
275
276
public:
277
	using value_type = Value;
278
279
	template<typename HashSet, typename IValue>
280
	class Iter {
281
	public:
282
		using value_type = IValue;
283
		using difference_type = int;
284
		using pointer = IValue*;
285
		using reference = IValue&;
286
		using iterator_category = std::forward_iterator_tag;
287
288
		Iter() = default;
289
290
		template<typename HashSet2, typename IValue2>
291
		explicit Iter(const Iter<HashSet2, IValue2>& other)
292
			: hashSet(other.hashSet), elemIdx(other.elemIdx) {}
293
294
		template<typename HashSet2, typename IValue2>
295
		Iter& operator=(const Iter<HashSet2, IValue2>& rhs) {
296
			hashSet = rhs.hashSet;
297
			elemIdx = rhs.elemIdx;
298
			return *this;
299
		}
300
301
		[[nodiscard]] bool operator==(const Iter& rhs) const {
302
			assert((hashSet == rhs.hashSet) || !hashSet || !rhs.hashSet);
303
			return elemIdx == rhs.elemIdx;
304
		}
305
306
		Iter& operator++() {
307
			auto& oldElem = hashSet->pool.get(elemIdx);
308
			elemIdx = oldElem.nextIdx;
309
			if (elemIdx == invalidIndex) {
310
				unsigned tableIdx = oldElem.hash & hashSet->allocMask;
311
				do {
312
					if (tableIdx == hashSet->allocMask) break;
313
					elemIdx = hashSet->table[++tableIdx];
314
				} while (elemIdx == invalidIndex);
315
			}
316
			return *this;
317
		}
318
		Iter operator++(int) {
319
			Iter tmp = *this;
320
			++(*this);
321
			return tmp;
322
		}
323
324
		[[nodiscard]] IValue& operator*() const {
325
			return hashSet->pool.get(elemIdx).value;
326
		}
327
		[[nodiscard]] IValue* operator->() const {
328
			return &hashSet->pool.get(elemIdx).value;
329
		}
330
331
	//internal:   should only be called from hash_set and hash_map
332
		Iter(HashSet* m, PoolIndex idx)
333
			: hashSet(m), elemIdx(idx) {}
334
335
		[[nodiscard]] PoolIndex getElementIdx() const {
336
			return elemIdx;
337
		}
338
339
	private:
340
		HashSet* hashSet = nullptr;
341
		PoolIndex elemIdx = invalidIndex;
342
	};
343
344
	using       iterator = Iter<      hash_set,       Value>;
345
	using const_iterator = Iter<const hash_set, const Value>;
346
347
public:
348
	explicit hash_set(unsigned initialSize = 0,
349
	         Extractor extract_ = Extractor(),
350
	         Hasher hasher_ = Hasher(),
351
	         Equal equal_ = Equal())
352
		: extract(extract_), hasher(hasher_), equal(equal_)
353
	{
354
		reserve(initialSize); // optimized away if initialSize==0
355
	}
356
357
	hash_set(const hash_set& source)
358
		: extract(source.extract), hasher(source.hasher)
359
		, equal(source.equal)
360
	{
361
		if (source.elemCount == 0) return;
362
		reserve(source.elemCount);
363
364
		for (unsigned i = 0; i <= source.allocMask; ++i) {
365
			for (auto idx = source.table[i]; idx != invalidIndex; /**/) {
366
				const auto& elem = source.pool.get(idx);
367
				insert_noCapacityCheck_noDuplicateCheck(elem.value);
368
				idx = elem.nextIdx;
369
			}
370
		}
371
	}
372
373
	hash_set(hash_set&& source) noexcept
374
		: table(source.table)
375
		, pool(std::move(source.pool))
376
		, allocMask(source.allocMask)
377
		, elemCount(source.elemCount)
378
		, extract(std::move(source.extract))
379
		, hasher (std::move(source.hasher))
380
		, equal  (std::move(source.equal))
381
	{
382
		source.table = nullptr;
383
		source.allocMask = -1;
384
		source.elemCount = 0;
385
	}
386
387
	explicit hash_set(std::initializer_list<Value> args)
388
	{
389
		reserve(narrow<unsigned>(args.size()));
390
		for (auto a : args) insert_noCapacityCheck(a); // need duplicate check??
391
	}
392
393
	~hash_set()
394
	{
395
		clear();
396
		free(table);
397
	}
398
399
	hash_set& operator=(const hash_set& source)
400
	{
401
		if (&source == this) return *this;
402
		clear();
403
		if (source.elemCount == 0) return *this;
404
		reserve(source.elemCount);
405
406
		for (unsigned i = 0; i <= source.allocMask; ++i) {
407
			for (auto idx = source.table[i]; idx != invalidIndex; /**/) {
408
				const auto& elem = source.pool.get(idx);
409
				insert_noCapacityCheck_noDuplicateCheck(elem.value);
410
				idx = elem.nextIdx;
411
			}
412
		}
413
		extract = source.extract;
414
		hasher  = source.hasher;
415
		equal   = source.equal;
416
		return *this;
417
	}
418
419
	hash_set& operator=(hash_set&& source) noexcept
420
	{
421
		table     = source.table;
422
		pool      = std::move(source.pool);
423
		allocMask = source.allocMask;
424
		elemCount = source.elemCount;
425
		extract   = std::move(source.extract);
426
		hasher    = std::move(source.hasher);
427
		equal     = std::move(source.equal);
428
429
		source.table = nullptr;
430
		source.allocMask = -1;
431
		source.elemCount = 0;
432
		return *this;
433
	}
434
435
	template<typename K>
436
	[[nodiscard]] bool contains(const K& key) const
437
	{
438
		return locateElement(key) != invalidIndex;
439
	}
440
441
	template<typename V>
442
	std::pair<iterator, bool> insert(V&& value)
443
	{
444
		return insert_impl<true, true>(std::forward<V>(value));
445
	}
446
	template<typename V>
447
	std::pair<iterator, bool> insert_noCapacityCheck(V&& value)
448
	{
449
		return insert_impl<false, true>(std::forward<V>(value));
450
	}
451
	template<typename V>
452
	iterator insert_noDuplicateCheck(V&& value)
453
	{
454
		return insert_impl<true, false>(std::forward<V>(value)).first;
455
	}
456
	template<typename V>
457
	iterator insert_noCapacityCheck_noDuplicateCheck(V&& value)
458
	{
459
		return insert_impl<false, false>(std::forward<V>(value)).first;
460
	}
461
462
	template<typename... Args>
463
	std::pair<iterator, bool> emplace(Args&&... args)
464
	{
465
		return emplace_impl<true, true>(std::forward<Args>(args)...);
466
	}
467
	template<typename... Args>
468
	std::pair<iterator, bool> emplace_noCapacityCheck(Args&&... args)
469
	{
470
		return emplace_impl<false, true>(std::forward<Args>(args)...);
471
	}
472
	template<typename... Args>
473
	iterator emplace_noDuplicateCheck(Args&&... args)
474
	{
475
		return emplace_impl<true, false>(std::forward<Args>(args)...).first;
476
	}
477
	template<typename... Args>
478
	iterator emplace_noCapacityCheck_noDuplicateCheck(Args&&... args)
479
	{
480
		return emplace_impl<false, false>(std::forward<Args>(args)...).first;
481
	}
482
483
	template<typename K>
484
	bool erase(const K& key)
485
	{
486
		if (elemCount == 0) return false;
487
488
		auto hash = unsigned(hasher(key));
489
		auto tableIdx = hash & allocMask;
490
491
		for (auto* prev = &table[tableIdx]; *prev != invalidIndex; prev = &(pool.get(*prev).nextIdx)) {
492
			auto elemIdx = *prev;
493
			auto& elem = pool.get(elemIdx);
494
			if (elem.hash != hash) continue;
495
			if (!equal(extract(elem.value), key)) continue;
496
497
			*prev = elem.nextIdx;
498
			pool.destroy(elemIdx);
499
			--elemCount;
500
			return true;
501
		}
502
		return false;
503
	}
504
505
	void erase(iterator it)
506
	{
507
		auto elemIdx = it.getElementIdx();
508
		assert(elemIdx != invalidIndex); // not allowed to call erase(end())
509
		auto& elem = pool.get(elemIdx);
510
		auto tableIdx = pool.get(elemIdx).hash & allocMask;
511
		auto* prev = &table[tableIdx];
512
		assert(*prev != invalidIndex);
513
		while (*prev != elemIdx) {
514
			prev = &(pool.get(*prev).nextIdx);
515
			assert(*prev != invalidIndex);
516
		}
517
		*prev = elem.nextIdx;
518
		pool.destroy(elemIdx);
519
		--elemCount;
520
	}
521
522
	[[nodiscard]] bool empty() const
523
	{
524
		return elemCount == 0;
525
	}
526
527
	[[nodiscard]] unsigned size() const
528
	{
529
		return elemCount;
530
	}
531
532
	void clear()
533
	{
534
		if (elemCount == 0) return;
535
536
		for (unsigned i = 0; i <= allocMask; ++i) {
537
			for (auto elemIdx = table[i]; elemIdx != invalidIndex; /**/) {
538
				auto nextIdx = pool.get(elemIdx).nextIdx;
539
				pool.destroy(elemIdx);
540
				elemIdx = nextIdx;
541
			}
542
			table[i] = invalidIndex;
543
		}
544
		elemCount = 0;
545
	}
546
547
	template<typename K>
548
	[[nodiscard]] iterator find(const K& key)
549
	{
550
		return iterator(this, locateElement(key));
551
	}
552
553
	template<typename K>
554
	[[nodiscard]] const_iterator find(const K& key) const
555
	{
556
		return const_iterator(this, locateElement(key));
557
	}
558
559
	[[nodiscard]] iterator begin()
560
	{
561
		if (elemCount == 0) return end();
562
563
		for (unsigned idx = 0; idx <= allocMask; ++idx) {
564
			if (table[idx] != invalidIndex) {
565
				return iterator(this, table[idx]);
566
			}
567
		}
568
		UNREACHABLE;
569
	}
570
571
	[[nodiscard]] const_iterator begin() const
572
	{
573
		if (elemCount == 0) return end();
574
575
		for (unsigned idx = 0; idx <= allocMask; ++idx) {
576
			if (table[idx] != invalidIndex) {
577
				return const_iterator(this, table[idx]);
578
			}
579
		}
580
		UNREACHABLE;
581
	}
582
583
	[[nodiscard]] iterator end()
584
	{
585
		return iterator();
586
	}
587
588
	[[nodiscard]] const_iterator end() const
589
	{
590
		return const_iterator();
591
	}
592
593
	[[nodiscard]] unsigned capacity() const
594
	{
595
		unsigned poolCapacity = pool.capacity();
596
		unsigned tableCapacity = (allocMask + 1) / 4 * 3;
597
		return std::min(poolCapacity, tableCapacity);
598
	}
599
600
	// After this call, the hash_set can at least contain 'count' number of
601
	// elements without requiring any additional memory allocation or rehash.
602
	void reserve(unsigned count)
603
	{
604
		pool.reserve(count);
605
606
		unsigned oldCount = allocMask + 1;
607
		unsigned newCount = nextPowerOf2((count + 2) / 3 * 4);
608
		if (oldCount >= newCount) return;
609
610
		allocMask = newCount - 1;
611
		if (oldCount == 0) {
612
			table = static_cast<PoolIndex*>(malloc(newCount * sizeof(PoolIndex)));
613
			std::fill(table, table + newCount, invalidIndex);
614
		} else {
615
			table = static_cast<PoolIndex*>(realloc(table, newCount * sizeof(PoolIndex)));
616
			do {
617
				rehash(oldCount);
618
				oldCount *= 2;
619
			} while (oldCount < newCount);
620
		}
621
	}
622
623
	friend void swap(hash_set& x, hash_set& y) noexcept
624
	{
625
		using std::swap;
626
		swap(x.table,     y.table);
627
		swap(x.pool,      y.pool);
628
		swap(x.allocMask, y.allocMask);
629
		swap(x.elemCount, y.elemCount);
630
		swap(x.extract  , y.extract);
631
		swap(x.hasher   , y.hasher);
632
		swap(x.equal    , y.equal);
633
	}
634
635
	[[nodiscard]] friend auto begin(      hash_set& s) { return s.begin(); }
636
	[[nodiscard]] friend auto begin(const hash_set& s) { return s.begin(); }
637
	[[nodiscard]] friend auto end  (      hash_set& s) { return s.end();   }
638
	[[nodiscard]] friend auto end  (const hash_set& s) { return s.end();   }
639
640
protected:
641
	// Returns the smallest value that is >= x that is also a power of 2.
642
	// (for x=0 it returns 0)
643
	[[nodiscard]] static unsigned nextPowerOf2(unsigned x)
644
	{
645
		static_assert(sizeof(unsigned) == sizeof(uint32_t), "only works for exactly 32 bit");
646
		x -= 1;
647
		x |= x >>  1;
648
		x |= x >>  2;
649
		x |= x >>  4;
650
		x |= x >>  8;
651
		x |= x >> 16;
652
		return x + 1;
653
	}
654
655
	template<bool CHECK_CAPACITY, bool CHECK_DUPLICATE, typename V>
656
	[[nodiscard]] std::pair<iterator, bool> insert_impl(V&& value)
657
	{
658
		auto hash = unsigned(hasher(extract(value)));
659
		auto tableIdx = hash & allocMask;
660
		PoolIndex primary = invalidIndex;
661
662
		if (!CHECK_CAPACITY || (elemCount > 0)) {
663
			primary = table[tableIdx];
664
			if constexpr (CHECK_DUPLICATE) {
665
				for (auto elemIdx = primary; elemIdx != invalidIndex; /**/) {
666
					auto& elem = pool.get(elemIdx);
667
					if ((elem.hash == hash) &&
668
					    equal(extract(elem.value), extract(value))) {
669
						// already exists
670
						return std::pair(iterator(this, elemIdx), false);
671
					}
672
					elemIdx = elem.nextIdx;
673
				}
674
			}
675
		}
676
677
		if (CHECK_CAPACITY && (elemCount >= ((allocMask + 1) / 4 * 3))) {
678
			grow();
679
			tableIdx = hash & allocMask;
680
			primary = table[tableIdx];
681
		}
682
683
		elemCount++;
684
		auto idx = pool.create(std::forward<V>(value), hash, primary);
685
		table[tableIdx] = idx;
686
		return std::pair(iterator(this, idx), true);
687
	}
688
689
	template<bool CHECK_CAPACITY, bool CHECK_DUPLICATE, typename... Args>
690
	[[nodiscard]] std::pair<iterator, bool> emplace_impl(Args&&... args)
691
	{
692
		auto poolIdx = pool.emplace(std::forward<Args>(args)...);
693
		auto& poolElem = pool.get(poolIdx);
694
695
		auto hash = unsigned(hasher(extract(poolElem.value)));
696
		auto tableIdx = hash & allocMask;
697
		PoolIndex primary = invalidIndex;
698
699
		if (!CHECK_CAPACITY || (elemCount > 0)) {
700
			primary = table[tableIdx];
701
			if constexpr (CHECK_DUPLICATE) {
702
				for (auto elemIdx = primary; elemIdx != invalidIndex; /**/) {
703
					auto& elem = pool.get(elemIdx);
704
					if ((elem.hash == hash) &&
705
					    equal(extract(elem.value), extract(poolElem.value))) {
706
						// already exists
707
						pool.destroy(poolIdx);
708
						return std::pair(iterator(this, elemIdx), false);
709
					}
710
					elemIdx = elem.nextIdx;
711
				}
712
			}
713
		}
714
715
		if (CHECK_CAPACITY && (elemCount >= ((allocMask + 1) / 4 * 3))) {
716
			grow();
717
			tableIdx = hash & allocMask;
718
			primary = table[tableIdx];
719
		}
720
721
		elemCount++;
722
		poolElem.hash = hash;
723
		poolElem.nextIdx = primary;
724
		table[tableIdx] = poolIdx;
725
		return std::pair(iterator(this, poolIdx), true);
726
	}
727
728
	void grow()
729
	{
730
		unsigned oldCount = allocMask + 1;
731
		if (oldCount == 0) {
732
			allocMask = 4 - 1; // initial size
733
			table = static_cast<PoolIndex*>(malloc(4 * sizeof(PoolIndex)));
734
			std::fill(table, table + 4, invalidIndex);
735
		} else {
736
			unsigned newCount = 2 * oldCount;
737
			allocMask = newCount - 1;
738
			table = static_cast<PoolIndex*>(realloc(table, newCount * sizeof(unsigned)));
739
			rehash(oldCount);
740
		}
741
	}
742
743
	void rehash(unsigned oldCount)
744
	{
745
		assert((oldCount & (oldCount - 1)) == 0); // must be a power-of-2
746
		for (auto i : xrange(oldCount)) {
747
			auto* p0 = &table[i];
748
			auto* p1 = &table[i + oldCount];
749
			for (auto p = *p0; p != invalidIndex; p = pool.get(p).nextIdx) {
750
				auto& elem = pool.get(p);
751
				if ((elem.hash & oldCount) == 0) {
752
					*p0 = p;
753
					p0 = &elem.nextIdx;
754
				} else {
755
					*p1 = p;
756
					p1 = &elem.nextIdx;
757
				}
758
			}
759
			*p0 = invalidIndex;
760
			*p1 = invalidIndex;
761
		}
762
	}
763
764
	template<typename K>
765
	[[nodiscard]] PoolIndex locateElement(const K& key) const
766
	{
767
		if (elemCount == 0) return invalidIndex;
768
769
		auto hash = unsigned(hasher(key));
770
		auto tableIdx = hash & allocMask;
771
		for (auto elemIdx = table[tableIdx]; elemIdx != invalidIndex; /**/) {
772
			auto& elem = pool.get(elemIdx);
773
			if ((elem.hash == hash) &&
774
			    equal(extract(elem.value), key)) {
775
				return elemIdx;
776
			}
777
			elemIdx = elem.nextIdx;
778
		}
779
		return invalidIndex;
780
	}
781
782
protected:
783
	PoolIndex* table = nullptr;
784
	hash_set_impl::Pool<Value> pool;
785
	unsigned allocMask = unsigned(-1);
786
	unsigned elemCount = 0;
787
	[[no_unique_address]] Extractor extract;
788
	[[no_unique_address]] Hasher hasher;
789
	[[no_unique_address]] Equal equal;
790
};
791
792
#endif