FE 0.15.0
A C++23 toolkit for writing compiler/interpreter frontends.
Loading...
Searching...
No Matches
bitset.h
Go to the documentation of this file.
1#pragma once
2
3#include <cassert>
4#include <cstddef>
5#include <cstdint>
6
7#include <algorithm>
8#include <bit>
9#include <initializer_list>
10#include <iosfwd>
11#include <iterator>
12#include <ranges>
13#include <utility>
14
15#include "fe/algo.h"
16#include "fe/hash.h"
17
18namespace fe {
19
20/// A dynamically growing set of bits with small storage optimization.
21/// The first Bitset::Inline_Bits bits live inside the Bitset itself; only beyond that it allocates on the heap.
22/// Think of a Bitset as an infinite sequence of bits that are zero except for the ones you set:
23/// Bitset::set/Bitset::flip grow the storage on demand,
24/// while testing or clearing a bit beyond Bitset::capacity needs no storage at all.
25/// @note Everything is `constexpr` as long as the Bitset stays inline:
26/// the heap words hide in a `uint64_t` and are only recovered by a `std::bit_cast` that a constant evaluation rejects.
27class Bitset {
28public:
29 static constexpr size_t Bits_Per_Word = sizeof(uint64_t) * 8; ///< Number of bits in one word.
30 static constexpr size_t Inline_Bits = Bits_Per_Word; ///< Number of bits available without allocating.
31 static constexpr size_t npos = size_t(-1); ///< Returned by Bitset::next if there is no set bit.
32
33 /// Proxy that Bitset::operator[] hands out to read/write the bit it refers to.
34 class reference {
35 public:
36 constexpr reference& operator=(bool b) noexcept {
37 bitset_->set(i_, b);
38 return *this;
39 }
40 constexpr reference& operator=(const reference& other) noexcept { return *this = bool(other); }
41 constexpr reference& flip() noexcept {
42 bitset_->flip(i_);
43 return *this;
44 }
45
46 constexpr operator bool() const noexcept { return bitset_->test(i_); }
47 constexpr bool operator~() const noexcept { return !bitset_->test(i_); }
48
49 private:
50 constexpr reference(Bitset* bitset, size_t i) noexcept
51 : bitset_(bitset)
52 , i_(i) {}
53
54 Bitset* bitset_;
55 size_t i_;
56
57 friend class Bitset;
58 };
59
60 /// Iterates over the *indices* of all set bits in ascending order.
61 class iterator {
62 public:
63 using iterator_category = std::forward_iterator_tag;
64 using value_type = size_t;
65 using difference_type = std::ptrdiff_t;
66 using pointer = void;
67 using reference = size_t;
68
69 constexpr iterator() noexcept = default;
70
71 constexpr size_t operator*() const noexcept { return i_; }
72 constexpr iterator& operator++() noexcept {
73 assert(i_ != npos);
74 i_ = bitset_->next(i_ + 1);
75 return *this;
76 }
77 constexpr iterator operator++(int) noexcept {
78 auto res = *this;
79 ++*this;
80 return res;
81 }
82 constexpr bool operator==(iterator other) const noexcept { return i_ == other.i_; }
83
84 private:
85 constexpr iterator(const Bitset* bitset, size_t i) noexcept
86 : bitset_(bitset)
87 , i_(i) {}
88
89 const Bitset* bitset_ = nullptr;
90 size_t i_ = npos;
91
92 friend class Bitset;
93 };
94
95 /// @name Constructors, Destructor, Assignment
96 ///@{
97 constexpr Bitset() noexcept = default;
98 constexpr Bitset(std::initializer_list<size_t> bits) {
99 for (auto i : bits)
100 set(i);
101 }
102 /// Constructs the set of the first @p num_bits bits; with @p value `false` the empty set that has
103 /// room for them - so `Bitset(n, b)` reads like `std::vector<bool>(n, b)`.
104 /// @note @p value has no default on purpose: a one-argument `Bitset(3)` would be all too easy to read as
105 /// the `Bitset{3}` above - which is `{3}`, not `{0, 1, 2}`.
106 constexpr Bitset(size_t num_bits, bool value) {
107 if (num_bits == 0) return;
108 grow((num_bits + Bits_Per_Word - 1) / Bits_Per_Word);
109 if (!value) return;
110 auto w = words();
111 auto full = num_bits / Bits_Per_Word;
112 for (size_t i = 0; i != full; ++i)
113 w[i] = ~uint64_t(0);
114 if (auto rest = num_bits % Bits_Per_Word) w[full] = (uint64_t(1) << rest) - 1;
115 }
116 constexpr Bitset(const Bitset& other)
117 : num_words_(other.num_words_) {
118 if (other.on_heap()) {
119 auto heap = new uint64_t[num_words_];
120 std::copy_n(other.words(), num_words_, heap);
121 data_ = bitcast_resize<uint64_t>(heap);
122 } else {
123 data_ = other.data_;
124 }
125 }
126 constexpr Bitset(Bitset&& other) noexcept { swap(*this, other); }
127 constexpr ~Bitset() noexcept {
128 if (on_heap()) delete[] words();
129 }
130 constexpr Bitset& operator=(Bitset other) noexcept {
131 swap(*this, other);
132 return *this;
133 }
134 friend constexpr void swap(Bitset& b1, Bitset& b2) noexcept {
135 using std::swap;
136 swap(b1.data_, b2.data_);
137 swap(b1.num_words_, b2.num_words_);
138 }
139 ///@}
140
141 /// @name Access
142 ///@{
143 [[nodiscard]] constexpr bool test(size_t i) const noexcept {
144 auto w = i / Bits_Per_Word;
145 return w < num_words_ && (words()[w] & bit(i)) != 0;
146 }
147 [[nodiscard]] constexpr bool operator[](size_t i) const noexcept { return test(i); }
148 [[nodiscard]] constexpr reference operator[](size_t i) noexcept { return {this, i}; }
149
150 /// Index of the first bit that is set at or after @p i - or npos, if there is none.
151 [[nodiscard]] constexpr size_t next(size_t i) const noexcept {
152 auto w = i / Bits_Per_Word;
153 if (w >= num_words_) return npos;
154
155 auto words = this->words();
156 for (auto word = words[w] & (~uint64_t(0) << (i % Bits_Per_Word));; word = words[w]) {
157 if (word) return w * Bits_Per_Word + size_t(std::countr_zero(word));
158 if (++w == num_words_) return npos;
159 }
160 }
161 ///@}
162
163 /// @name Modifiers
164 /// Bitset::set and Bitset::flip grow the storage as needed; the others never allocate.
165 ///@{
166 constexpr Bitset& set(size_t i) {
167 grow(i / Bits_Per_Word + 1);
168 words()[i / Bits_Per_Word] |= bit(i);
169 return *this;
170 }
171 constexpr Bitset& set(size_t i, bool b) { return b ? set(i) : clear(i); }
172 constexpr Bitset& flip(size_t i) {
173 grow(i / Bits_Per_Word + 1);
174 words()[i / Bits_Per_Word] ^= bit(i);
175 return *this;
176 }
177 constexpr Bitset& clear(size_t i) noexcept {
178 if (auto w = i / Bits_Per_Word; w < num_words_) words()[w] &= ~bit(i);
179 return *this;
180 }
181 /// Clears all bits and releases the heap storage again.
182 constexpr Bitset& clear() noexcept {
183 if (on_heap()) delete[] words();
184 data_ = 0;
185 num_words_ = 1;
186 return *this;
187 }
188 ///@}
189
190 /// @name Queries
191 ///@{
192 [[nodiscard]] constexpr size_t count() const noexcept {
193 size_t res = 0;
194 for (size_t i = 0, e = num_words_; i != e; ++i)
195 res += size_t(std::popcount(words()[i]));
196 return res;
197 }
198 [[nodiscard]] constexpr bool any() const noexcept { return !zeros(words(), num_words_); }
199 [[nodiscard]] constexpr bool none() const noexcept { return zeros(words(), num_words_); }
200 /// Number of bits available without growing.
201 [[nodiscard]] constexpr size_t capacity() const noexcept { return num_words_ * Bits_Per_Word; }
202 /// Outgrown the inline storage?
203 [[nodiscard]] constexpr bool on_heap() const noexcept { return num_words_ != 1; }
204 ///@}
205
206 /// @name Set Operations
207 /// Union, intersection, symmetric difference, and difference.
208 ///@{
209 constexpr Bitset& operator|=(const Bitset& other) {
210 auto e = other.used();
211 grow(e);
212 for (size_t i = 0; i != e; ++i)
213 words()[i] |= other.words()[i];
214 return *this;
215 }
216 constexpr Bitset& operator&=(const Bitset& other) noexcept {
217 auto e = std::min(num_words_, other.num_words_);
218 for (size_t i = 0; i != e; ++i)
219 words()[i] &= other.words()[i];
220 for (size_t i = e, n = num_words_; i != n; ++i)
221 words()[i] = 0;
222 return *this;
223 }
224 constexpr Bitset& operator^=(const Bitset& other) {
225 auto e = other.used();
226 grow(e);
227 for (size_t i = 0; i != e; ++i)
228 words()[i] ^= other.words()[i];
229 return *this;
230 }
231 constexpr Bitset& operator-=(const Bitset& other) noexcept {
232 for (size_t i = 0, e = std::min(num_words_, other.num_words_); i != e; ++i)
233 words()[i] &= ~other.words()[i];
234 return *this;
235 }
236
237 friend constexpr Bitset operator|(Bitset b1, const Bitset& b2) { return std::move(b1 |= b2); }
238 friend constexpr Bitset operator&(Bitset b1, const Bitset& b2) { return std::move(b1 &= b2); }
239 friend constexpr Bitset operator^(Bitset b1, const Bitset& b2) { return std::move(b1 ^= b2); }
240 friend constexpr Bitset operator-(Bitset b1, const Bitset& b2) { return std::move(b1 -= b2); }
241 ///@}
242
243 /// @name Comparisons
244 /// The bits beyond Bitset::capacity are all zero, so a different capacity does *not* make two Bitset%s differ.
245 ///@{
246 [[nodiscard]] constexpr bool operator==(const Bitset& other) const noexcept {
247 auto e = std::min(num_words_, other.num_words_);
248 for (size_t i = 0; i != e; ++i)
249 if (words()[i] != other.words()[i]) return false;
250 return zeros(words() + e, num_words_ - e) && zeros(other.words() + e, other.num_words_ - e);
251 }
252
253 /// Is every bit set in `this` also set in @p other?
254 [[nodiscard]] constexpr bool subset_of(const Bitset& other) const noexcept {
255 auto e = std::min(num_words_, other.num_words_);
256 for (size_t i = 0; i != e; ++i)
257 if (words()[i] & ~other.words()[i]) return false;
258 return zeros(words() + e, num_words_ - e);
259 }
260
261 /// Do `this` and @p other have at least one bit in common?
262 [[nodiscard]] constexpr bool intersects(const Bitset& other) const noexcept {
263 for (size_t i = 0, e = std::min(num_words_, other.num_words_); i != e; ++i)
264 if (words()[i] & other.words()[i]) return true;
265 return false;
266 }
267 ///@}
268
269 /// @name Iterators
270 /// Yield the index of each set bit in ascending order.
271 ///@{
272 [[nodiscard]] constexpr iterator begin() const noexcept { return {this, next(0)}; }
273 [[nodiscard]] constexpr iterator end() const noexcept { return {}; }
274 ///@}
275
276 /// @name Hash
277 ///@{
278 [[nodiscard]] constexpr size_t hash() const noexcept {
279 auto res = hash_begin();
280 for (size_t i = 0, e = used(); i != e; ++i) {
281 auto word = words()[i];
282 res = hash_combine(res, uint32_t(word));
283 res = hash_combine(res, uint32_t(word >> 32));
284 }
285 return res;
286 }
287
288 struct Hash {
289 constexpr size_t operator()(const Bitset& bitset) const noexcept { return bitset.hash(); }
290 };
291
292#ifdef FE_ABSL
293 template<class H>
294 friend H AbslHashValue(H h, const Bitset& bitset) {
295 return H::combine(std::move(h), bitset.hash());
296 }
297#endif
298 ///@}
299
300 /// @name Output
301 ///@{
302 void dump() const;
303
304 friend std::ostream& operator<<(std::ostream& os, const Bitset& bitset);
305 ///@}
306
307private:
308 static constexpr uint64_t bit(size_t i) noexcept { return uint64_t(1) << (i % Bits_Per_Word); }
309
310 static constexpr bool zeros(const uint64_t* words, size_t num_words) noexcept {
311 for (size_t i = 0; i != num_words; ++i)
312 if (words[i]) return false;
313 return true;
314 }
315
316 constexpr uint64_t* words() noexcept { return on_heap() ? bitcast_resize<uint64_t*>(data_) : &data_; }
317 constexpr const uint64_t* words() const noexcept {
318 return on_heap() ? bitcast_resize<const uint64_t*>(data_) : &data_;
319 }
320
321 /// Number of words up to and including the last one that has a bit set.
322 constexpr size_t used() const noexcept {
323 for (auto i = num_words_; i-- != 0;)
324 if (words()[i]) return i + 1;
325 return 0;
326 }
327
328 constexpr void grow(size_t num_words) {
329 if (num_words <= num_words_) return;
330
331 num_words = std::max(num_words, num_words_ * 2);
332 auto heap = new uint64_t[num_words]();
333 std::copy_n(words(), num_words_, heap);
334 if (on_heap()) delete[] words();
335 data_ = bitcast_resize<uint64_t>(heap);
336 num_words_ = num_words;
337 }
338
339 uint64_t data_ = 0; ///< The bits themselves while inline, the heap words otherwise.
340 size_t num_words_ = 1; ///< Capacity in words; `1` means inline.
341};
342
343static_assert(sizeof(void*) != 8 || sizeof(Bitset) == 16, "Bitset should stay two machine words");
344static_assert(std::forward_iterator<Bitset::iterator>);
345static_assert(std::ranges::forward_range<Bitset>);
346
347} // namespace fe
348
349#ifndef DOXYGEN
350template<>
351struct std::hash<fe::Bitset> {
352 constexpr size_t operator()(const fe::Bitset& bitset) const noexcept { return bitset.hash(); }
353};
354#endif
Iterates over the indices of all set bits in ascending order.
Definition bitset.h:61
constexpr iterator & operator++() noexcept
Definition bitset.h:72
constexpr iterator() noexcept=default
std::forward_iterator_tag iterator_category
Definition bitset.h:63
std::ptrdiff_t difference_type
Definition bitset.h:65
friend class Bitset
Definition bitset.h:92
constexpr iterator operator++(int) noexcept
Definition bitset.h:77
constexpr bool operator==(iterator other) const noexcept
Definition bitset.h:82
Proxy that Bitset::operator[] hands out to read/write the bit it refers to.
Definition bitset.h:34
constexpr bool operator~() const noexcept
Definition bitset.h:47
constexpr reference & operator=(bool b) noexcept
Definition bitset.h:36
friend class Bitset
Definition bitset.h:57
constexpr reference & flip() noexcept
Definition bitset.h:41
constexpr reference & operator=(const reference &other) noexcept
Definition bitset.h:40
A dynamically growing set of bits with small storage optimization.
Definition bitset.h:27
constexpr Bitset(const Bitset &other)
Definition bitset.h:116
constexpr size_t capacity() const noexcept
Number of bits available without growing.
Definition bitset.h:201
constexpr bool none() const noexcept
Definition bitset.h:199
constexpr ~Bitset() noexcept
Definition bitset.h:127
constexpr size_t next(size_t i) const noexcept
Index of the first bit that is set at or after i - or npos, if there is none.
Definition bitset.h:151
constexpr Bitset & operator^=(const Bitset &other)
Definition bitset.h:224
constexpr Bitset & clear(size_t i) noexcept
Definition bitset.h:177
constexpr bool test(size_t i) const noexcept
Definition bitset.h:143
friend constexpr void swap(Bitset &b1, Bitset &b2) noexcept
Definition bitset.h:134
constexpr reference operator[](size_t i) noexcept
Definition bitset.h:148
friend constexpr Bitset operator-(Bitset b1, const Bitset &b2)
Definition bitset.h:240
constexpr Bitset() noexcept=default
constexpr Bitset & operator|=(const Bitset &other)
Definition bitset.h:209
constexpr iterator end() const noexcept
Definition bitset.h:273
constexpr Bitset(size_t num_bits, bool value)
Constructs the set of the first num_bits bits; with value false the empty set that has room for them ...
Definition bitset.h:106
constexpr iterator begin() const noexcept
Definition bitset.h:272
constexpr bool any() const noexcept
Definition bitset.h:198
constexpr bool intersects(const Bitset &other) const noexcept
Do this and other have at least one bit in common?
Definition bitset.h:262
constexpr size_t hash() const noexcept
Definition bitset.h:278
constexpr Bitset & clear() noexcept
Clears all bits and releases the heap storage again.
Definition bitset.h:182
static constexpr size_t Inline_Bits
Number of bits available without allocating.
Definition bitset.h:30
constexpr size_t count() const noexcept
Definition bitset.h:192
void dump() const
constexpr bool operator==(const Bitset &other) const noexcept
Definition bitset.h:246
friend constexpr Bitset operator&(Bitset b1, const Bitset &b2)
Definition bitset.h:238
constexpr Bitset & set(size_t i, bool b)
Definition bitset.h:171
static constexpr size_t Bits_Per_Word
Number of bits in one word.
Definition bitset.h:29
constexpr Bitset(Bitset &&other) noexcept
Definition bitset.h:126
constexpr Bitset & operator=(Bitset other) noexcept
Definition bitset.h:130
constexpr Bitset & flip(size_t i)
Definition bitset.h:172
constexpr bool operator[](size_t i) const noexcept
Definition bitset.h:147
constexpr Bitset & operator-=(const Bitset &other) noexcept
Definition bitset.h:231
friend std::ostream & operator<<(std::ostream &os, const Bitset &bitset)
static constexpr size_t npos
Returned by Bitset::next if there is no set bit.
Definition bitset.h:31
constexpr Bitset & set(size_t i)
Definition bitset.h:166
friend constexpr Bitset operator^(Bitset b1, const Bitset &b2)
Definition bitset.h:239
constexpr bool on_heap() const noexcept
Outgrown the inline storage?
Definition bitset.h:203
constexpr Bitset & operator&=(const Bitset &other) noexcept
Definition bitset.h:216
friend constexpr Bitset operator|(Bitset b1, const Bitset &b2)
Definition bitset.h:237
constexpr bool subset_of(const Bitset &other) const noexcept
Is every bit set in this also set in other?
Definition bitset.h:254
Definition algo.h:17
constexpr D bitcast_resize(const S &src) noexcept
A bitcast from src of type S to D, supporting different sizes.
Definition algo.h:26
constexpr size_t hash_begin() noexcept
Seeds a hash chain with the FNV-1 offset basis.
Definition hash.h:69
constexpr size_t hash_combine(size_t seed, T v) noexcept
Mixes v into seed word-wise, reusing the FNV-1 prime as multiplier.
Definition hash.h:73
Definition span.h:150
constexpr size_t operator()(const Bitset &bitset) const noexcept
Definition bitset.h:289