sparrow 0.3.0
Loading...
Searching...
No Matches
dynamic_bitset_base.hpp
Go to the documentation of this file.
1// Copyright 2024 Man Group Operations Limited
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#pragma once
16
17
18#include <bit>
19#include <stdexcept>
20#include <string>
21#include <type_traits>
22
26
27namespace sparrow
28{
40 template <typename B>
41 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
43 {
44 public:
45
47 using storage_type = B;
48 using storage_type_without_cvrefpointer = std::remove_pointer_t<std::remove_cvref_t<storage_type>>;
49 using block_type = typename storage_type_without_cvrefpointer::value_type;
50 using value_type = bool;
52 using const_reference = bool;
53 using size_type = typename storage_type_without_cvrefpointer::size_type;
54 using difference_type = typename storage_type_without_cvrefpointer::difference_type;
57
58 [[nodiscard]] constexpr size_type size() const noexcept;
59 [[nodiscard]] constexpr bool empty() const noexcept;
60 [[nodiscard]] constexpr size_type null_count() const noexcept;
61
62 [[nodiscard]] constexpr bool test(size_type pos) const;
63 constexpr void set(size_type pos, value_type value);
64
65 [[nodiscard]] constexpr const_reference at(size_type pos) const;
66 [[nodiscard]] constexpr reference at(size_type pos);
67
68 [[nodiscard]] constexpr reference operator[](size_type i);
69 [[nodiscard]] constexpr const_reference operator[](size_type i) const;
70
71 [[nodiscard]] constexpr block_type* data() noexcept;
72 [[nodiscard]] constexpr const block_type* data() const noexcept;
73 [[nodiscard]] constexpr size_type block_count() const noexcept;
75 constexpr void swap(self_type&) noexcept;
77 [[nodiscard]] constexpr iterator begin();
78 [[nodiscard]] constexpr iterator end();
79
80 [[nodiscard]] constexpr const_iterator begin() const;
81 [[nodiscard]] constexpr const_iterator end() const;
82 [[nodiscard]] constexpr const_iterator cbegin() const;
83 [[nodiscard]] constexpr const_iterator cend() const;
84
85 [[nodiscard]] constexpr reference front();
86 [[nodiscard]] constexpr const_reference front() const;
87 [[nodiscard]] constexpr reference back();
88 [[nodiscard]] constexpr const_reference back() const;
89
90 [[nodiscard]] constexpr const storage_type_without_cvrefpointer& buffer() const noexcept
91 {
92 if constexpr (std::is_pointer_v<storage_type>)
93 {
94 return *m_buffer;
95 }
96 else
97 {
98 return m_buffer;
99 }
100 }
101
102 [[nodiscard]] constexpr storage_type_without_cvrefpointer& buffer() noexcept
103 {
104 if constexpr (std::is_pointer_v<storage_type>)
105 {
106 return *m_buffer;
107 }
108 else
109 {
110 return m_buffer;
111 }
112 }
113
114 [[nodiscard]] static constexpr size_type compute_block_count(size_type bits_count) noexcept;
115
116 // storage_type is a value_type
117 [[nodiscard]] storage_type extract_storage() noexcept
119 {
120 return std::move(m_buffer);
121 }
122
123 protected:
124
127 constexpr ~dynamic_bitset_base() = default;
128
129 constexpr dynamic_bitset_base(const dynamic_bitset_base&) = default;
130 constexpr dynamic_bitset_base(dynamic_bitset_base&&) noexcept = default;
131
132 constexpr dynamic_bitset_base& operator=(const dynamic_bitset_base&) = default;
133 constexpr dynamic_bitset_base& operator=(dynamic_bitset_base&&) noexcept = default;
134
135 constexpr void resize(size_type n, value_type b = false);
136 constexpr void clear() noexcept;
137
139 constexpr iterator insert(const_iterator pos, size_type count, value_type value);
140 template <std::input_iterator InputIt>
141 constexpr iterator insert(const_iterator pos, InputIt first, InputIt last);
142 constexpr iterator insert(const_iterator pos, std::initializer_list<value_type> ilist);
146
147 constexpr void push_back(value_type value);
148 constexpr void pop_back();
149
150 private:
151
152 static constexpr std::size_t s_bits_per_block = sizeof(block_type) * CHAR_BIT;
153 [[nodiscard]] static constexpr size_type block_index(size_type pos) noexcept;
154 [[nodiscard]] static constexpr size_type bit_index(size_type pos) noexcept;
155 [[nodiscard]] static constexpr block_type bit_mask(size_type pos) noexcept;
156
157 [[nodiscard]] size_type count_non_null() const noexcept;
158 [[nodiscard]] constexpr size_type count_extra_bits() const noexcept;
159 constexpr void zero_unused_bits();
160 constexpr void update_null_count(bool old_value, bool new_value);
161
162 storage_type m_buffer;
163 size_type m_size;
164 size_type m_null_count;
165
166 friend class bitset_iterator<self_type, true>;
167 friend class bitset_iterator<self_type, false>;
168 friend class bitset_reference<self_type>;
169 };
170
171 template <typename B>
172 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
173 constexpr auto dynamic_bitset_base<B>::size() const noexcept -> size_type
174 {
175 return m_size;
176 }
177
178 template <typename B>
179 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
180 constexpr bool dynamic_bitset_base<B>::empty() const noexcept
181 {
182 return m_size == 0;
183 }
184
185 template <typename B>
186 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
187 constexpr auto dynamic_bitset_base<B>::null_count() const noexcept -> size_type
188 {
189 return m_null_count;
190 }
191
192 template <typename B>
193 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
195 {
196 SPARROW_ASSERT_TRUE(pos < size());
197 SPARROW_ASSERT_TRUE(data() != nullptr);
198 return reference(*this, buffer().data()[block_index(pos)], bit_mask(pos));
199 }
200
201 template <typename B>
202 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
204 {
205 return test(pos);
206 }
207
208 template <typename B>
209 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
210 constexpr bool dynamic_bitset_base<B>::test(size_type pos) const
211 {
212 SPARROW_ASSERT_TRUE(pos < size());
213 if (data() == nullptr)
214 {
215 return true;
216 }
217 return !m_null_count || buffer().data()[block_index(pos)] & bit_mask(pos);
218 }
219
220 template <typename B>
221 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
223 {
224 SPARROW_ASSERT_TRUE(pos < size());
225 SPARROW_ASSERT_TRUE(data() != nullptr);
226 block_type& block = buffer().data()[block_index(pos)];
227 const bool old_value = block & bit_mask(pos);
228 if (value)
229 {
230 block |= bit_mask(pos);
231 }
232 else
233 {
234 block &= block_type(~bit_mask(pos));
235 }
236 update_null_count(old_value, value);
237 }
238
239 template <typename B>
240 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
241 constexpr auto dynamic_bitset_base<B>::data() noexcept -> block_type*
242 {
243 return buffer().data();
244 }
245
246 template <typename B>
247 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
248 constexpr auto dynamic_bitset_base<B>::data() const noexcept -> const block_type*
249 {
250 return buffer().data();
251 }
252
253 template <typename B>
254 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
255 constexpr auto dynamic_bitset_base<B>::block_count() const noexcept -> size_type
256 {
257 return buffer().size();
258 }
259
260 template <typename B>
261 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
262 constexpr void dynamic_bitset_base<B>::swap(self_type& rhs) noexcept
263 {
264 using std::swap;
265 swap(m_buffer, rhs.m_buffer);
266 swap(m_size, rhs.m_size);
267 swap(m_null_count, rhs.m_null_count);
268 }
269
270 template <typename B>
271 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
273 {
274 return iterator(this, data(), 0u);
275 }
276
277 template <typename B>
278 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
280 {
281 block_type* block = buffer().size() ? data() + buffer().size() - 1 : data();
282 return iterator(this, block, size() % s_bits_per_block);
283 }
284
285 template <typename B>
286 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
288 {
289 return cbegin();
290 }
291
292 template <typename B>
293 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
295 {
296 return cend();
297 }
298
299 template <typename B>
300 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
302 {
303 return const_iterator(this, data(), 0u);
304 }
305
306 template <typename B>
307 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
309 {
310 const block_type* block = buffer().size() ? data() + buffer().size() - 1 : data();
311 return const_iterator(this, block, size() % s_bits_per_block);
312 }
313
314 template <typename B>
315 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
317 {
318 if (pos >= size())
319 {
320 throw std::out_of_range(
321 "dynamic_bitset_base::at: index out of range for dynamic_bitset_base of size"
322 + std::to_string(size()) + " at index " + std::to_string(pos)
323 );
324 }
325 return (*this)[pos];
326 }
327
328 template <typename B>
329 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
331 {
332 if (pos >= size())
333 {
334 throw std::out_of_range(
335 "dynamic_bitset_base::at: index out of range for dynamic_bitset_base of size"
336 + std::to_string(size()) + " at index " + std::to_string(pos)
337 );
338 }
339 return test(pos);
340 }
341
342 template <typename B>
343 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
345 {
347 return (*this)[0];
348 }
349
350 template <typename B>
351 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
353 {
355 if (data() == nullptr)
356 {
357 return true;
358 }
359 return (*this)[0];
360 }
361
362 template <typename B>
363 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
365 {
367 return (*this)[size() - 1];
368 }
369
370 template <typename B>
371 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
373 {
375 if (data() == nullptr)
376 {
377 return true;
378 }
379 return (*this)[size() - 1];
380 }
381
382 template <typename B>
383 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
385 : m_buffer(std::move(buf))
386 , m_size(size)
387 , m_null_count(m_size - count_non_null())
388 {
389 if constexpr (!std::is_const_v<block_type>)
390 {
391 zero_unused_bits();
392 }
393 }
394
395 template <typename B>
396 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
398 : m_buffer(std::move(buf))
399 , m_size(size)
400 , m_null_count(null_count)
401 {
402 if constexpr (!std::is_const_v<block_type>)
403 {
404 zero_unused_bits();
405 SPARROW_ASSERT_TRUE(m_null_count == m_size - count_non_null());
406 }
407 }
408
409 template <typename B>
410 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
412 {
413 return bits_count / s_bits_per_block + static_cast<size_type>(bits_count % s_bits_per_block != 0);
414 }
415
416 template <typename B>
417 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
418 constexpr auto dynamic_bitset_base<B>::block_index(size_type pos) noexcept -> size_type
419 {
420 return pos / s_bits_per_block;
421 }
422
423 template <typename B>
424 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
425 constexpr auto dynamic_bitset_base<B>::bit_index(size_type pos) noexcept -> size_type
426 {
427 return pos % s_bits_per_block;
428 }
429
430 template <typename B>
431 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
432 constexpr auto dynamic_bitset_base<B>::bit_mask(size_type pos) noexcept -> block_type
433 {
434 const size_type bit = bit_index(pos);
435 return static_cast<block_type>(block_type(1) << bit);
436 }
437
438 template <typename B>
439 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
440 auto dynamic_bitset_base<B>::count_non_null() const noexcept -> size_type
441 {
442 if (data() == nullptr)
443 {
444 return m_size;
445 }
446 if (buffer().empty())
447 {
448 return 0u;
449 }
450
451 int res = 0;
452 size_t full_blocks = m_size / s_bits_per_block;
453 for (size_t i = 0; i < full_blocks; ++i)
454 {
455 res += std::popcount(buffer().data()[i]);
456 }
457 if (full_blocks != buffer().size())
458 {
459 const size_t bits_count = m_size % s_bits_per_block;
460 const block_type mask = ~block_type(~block_type(0) << bits_count);
461 const block_type block = buffer().data()[full_blocks] & mask;
462 res += std::popcount(block);
463 }
464
465 return static_cast<size_t>(res);
466 }
467
468 template <typename B>
469 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
470 constexpr auto dynamic_bitset_base<B>::count_extra_bits() const noexcept -> size_type
471 {
472 return bit_index(size());
473 }
474
475 template <typename B>
476 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
477 constexpr void dynamic_bitset_base<B>::zero_unused_bits()
478 {
479 if (data() == nullptr)
480 {
481 return;
482 }
483 const size_type extra_bits = count_extra_bits();
484 if (extra_bits != 0)
485 {
486 buffer().back() &= block_type(~(~block_type(0) << extra_bits));
487 }
488 }
489
490 template <typename B>
491 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
492 constexpr void dynamic_bitset_base<B>::update_null_count(bool old_value, bool new_value)
493 {
494 if (new_value && !old_value)
495 {
496 --m_null_count;
497 }
498 else if (!new_value && old_value)
499 {
500 ++m_null_count;
501 }
502 }
503
504 template <typename B>
505 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
507 {
508 const size_type old_block_count = buffer().size();
509 const size_type new_block_count = compute_block_count(n);
510 const block_type value = b ? block_type(~block_type(0)) : block_type(0);
511
512 if (new_block_count != old_block_count)
513 {
514 buffer().resize(new_block_count, value);
515 }
516
517 if (b && n > m_size)
518 {
519 const size_type extra_bits = count_extra_bits();
520 if (extra_bits > 0)
521 {
522 buffer().data()[old_block_count - 1] |= static_cast<block_type>(value << extra_bits);
523 }
524 }
525
526 m_size = n;
527 m_null_count = m_size - count_non_null();
528 zero_unused_bits();
529 }
530
531 template <typename B>
532 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
533 constexpr void dynamic_bitset_base<B>::clear() noexcept
534 {
535 buffer().clear();
536 m_size = 0;
537 m_null_count = 0;
538 }
539
540 template <typename B>
541 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
544 {
545 return insert(pos, 1, value);
546 }
547
548 template <typename B>
549 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
552 {
553 SPARROW_ASSERT_TRUE(data() != nullptr);
554 SPARROW_ASSERT_TRUE(cbegin() <= pos);
555 SPARROW_ASSERT_TRUE(pos <= cend());
556 const auto index = static_cast<size_type>(std::distance(cbegin(), pos));
557 const size_type old_size = size();
558 const size_type new_size = old_size + count;
559
560 // TODO: The current implementation is not efficient. It can be improved.
561
562 resize(new_size);
563
564 for (size_type i = old_size + count - 1; i >= index + count; --i)
565 {
566 set(i, test(i - count));
567 }
568
569 for (size_type i = 0; i < count; ++i)
570 {
571 set(index + i, value);
572 }
573
574 auto block = data() + block_index(index);
575 return iterator(this, block, bit_index(index));
576 }
577
578 template <typename B>
579 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
580 template <std::input_iterator InputIt>
582 dynamic_bitset_base<B>::insert(const_iterator pos, InputIt first, InputIt last)
583 {
584 SPARROW_ASSERT_TRUE(data() != nullptr);
585 SPARROW_ASSERT_TRUE(cbegin() <= pos);
586 SPARROW_ASSERT_TRUE(pos <= cend());
587 const auto index = static_cast<size_type>(std::distance(cbegin(), pos));
588 const size_type old_size = size();
589 const size_type count = static_cast<size_type>(std::distance(first, last));
590 const size_type new_size = old_size + count;
591
592 resize(new_size);
593
594 // TODO: The current implementation is not efficient. It can be improved.
595
596 for (size_type i = old_size + count - 1; i >= index + count; --i)
597 {
598 set(i, test(i - count));
599 }
600
601 for (size_type i = 0; i < count; ++i)
602 {
603 set(index + i, *first++);
604 }
605
606 auto block = data() + block_index(index);
607 return iterator(this, block, bit_index(index));
608 }
609
610 template <typename B>
611 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
614 {
615 return insert(pos, value);
616 }
617
618 template <typename B>
619 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
621 {
622 SPARROW_ASSERT_TRUE(cbegin() <= pos);
623 SPARROW_ASSERT_TRUE(pos < cend());
624
625 return erase(pos, pos + 1);
626 }
627
628 template <typename B>
629 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
632 {
633 SPARROW_ASSERT_TRUE(data() != nullptr);
634 SPARROW_ASSERT_TRUE(cbegin() <= first);
635 SPARROW_ASSERT_TRUE(first <= last);
636 SPARROW_ASSERT_TRUE(last <= cend());
637
638 const auto first_index = static_cast<size_type>(std::distance(cbegin(), first));
639
640 if (last == cend())
641 {
642 resize(first_index);
643 return end();
644 }
645
646 // TODO: The current implementation is not efficient. It can be improved.
647
648 const auto last_index = static_cast<size_type>(std::distance(cbegin(), last));
649 const size_type count = last_index - first_index;
650
651 const size_type bit_to_move = size() - last_index;
652 for (size_type i = 0; i < bit_to_move; ++i)
653 {
654 set(first_index + i, test(last_index + i));
655 }
656
657 resize(size() - count);
658
659 auto block = data() + block_index(first_index);
660 return iterator(this, block, bit_index(first_index));
661 }
662
663 template <typename B>
664 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
666 {
667 resize(size() + 1, value);
668 }
669
670 template <typename B>
671 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
673 {
674 resize(size() - 1);
675 }
676}
Iterator used to iterate over the bits of a dynamic bitset as if they were addressable values.
Reference proxy used by the bitset_iterator class to make it possible to assign a bit of a bitset as ...
Object that owns a piece of contiguous memory.
Definition buffer.hpp:109
constexpr U * data() noexcept
Definition buffer.hpp:594
constexpr reference back()
Definition buffer.hpp:579
typename storage_type_without_cvrefpointer::value_type block_type
constexpr ~dynamic_bitset_base()=default
bitset_iterator< self_type, false > iterator
constexpr iterator insert(const_iterator pos, value_type value)
constexpr const_iterator cbegin() const
constexpr void push_back(value_type value)
constexpr iterator erase(const_iterator pos)
typename storage_type_without_cvrefpointer::size_type size_type
constexpr bool empty() const noexcept
constexpr iterator emplace(const_iterator pos, value_type value)
dynamic_bitset_base< buffer< T > > self_type
storage_type extract_storage() noexcept
constexpr const_reference at(size_type pos) const
constexpr bool test(size_type pos) const
constexpr void resize(size_type n, value_type b=false)
constexpr size_type null_count() const noexcept
bitset_iterator< self_type, true > const_iterator
std::remove_pointer_t< std::remove_cvref_t< storage_type > > storage_type_without_cvrefpointer
static constexpr size_type compute_block_count(size_type bits_count) noexcept
constexpr size_type size() const noexcept
constexpr dynamic_bitset_base(dynamic_bitset_base &&) noexcept=default
constexpr void set(size_type pos, value_type value)
typename storage_type_without_cvrefpointer::difference_type difference_type
constexpr size_type block_count() const noexcept
constexpr const storage_type_without_cvrefpointer & buffer() const noexcept
constexpr reference operator[](size_type i)
constexpr dynamic_bitset_base(storage_type buffer, size_type size, size_type null_count)
constexpr dynamic_bitset_base(const dynamic_bitset_base &)=default
constexpr void swap(self_type &) noexcept
constexpr block_type * data() noexcept
constexpr dynamic_bitset_base(storage_type buffer, size_type size)
constexpr const_iterator cend() const
constexpr storage_type_without_cvrefpointer & buffer() noexcept
typename base_type::block_type block_type
#define SPARROW_ASSERT_TRUE(expr__)
constexpr std::size_t size(typelist< T... >={})
Definition mp_utils.hpp:107