sparrow 1.0.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 <algorithm>
19#include <bit>
20#include <stdexcept>
21#include <string>
22#include <type_traits>
23
27
28namespace sparrow
29{
66 template <typename B>
67 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
69 {
70 public:
71
73 using storage_type = B;
74 using storage_type_without_cvrefpointer = std::remove_pointer_t<
75 std::remove_cvref_t<storage_type>>;
76 using block_type = typename storage_type_without_cvrefpointer::value_type;
78 using value_type = bool;
80 using const_reference = bool;
81 using size_type = typename storage_type_without_cvrefpointer::size_type;
83 using difference_type = typename storage_type_without_cvrefpointer::difference_type;
88
94 [[nodiscard]] constexpr size_type size() const noexcept;
95
101 [[nodiscard]] constexpr bool empty() const noexcept;
102
108 [[nodiscard]] constexpr size_type null_count() const noexcept;
109
118 [[nodiscard]] constexpr bool test(size_type pos) const;
119
130 constexpr void set(size_type pos, value_type value);
131
140 [[nodiscard]] constexpr const_reference at(size_type pos) const;
141
150 [[nodiscard]] constexpr reference at(size_type pos);
151
160 [[nodiscard]] constexpr reference operator[](size_type i);
161
170 [[nodiscard]] constexpr const_reference operator[](size_type i) const;
171
177 [[nodiscard]] constexpr block_type* data() noexcept;
184 [[nodiscard]] constexpr const block_type* data() const noexcept;
185
191 [[nodiscard]] constexpr size_type block_count() const noexcept;
192
199 constexpr void swap(self_type& rhs) noexcept;
200
206 [[nodiscard]] constexpr iterator begin();
207
213 [[nodiscard]] constexpr iterator end();
214
220 [[nodiscard]] constexpr const_iterator begin() const;
221
227 [[nodiscard]] constexpr const_iterator end() const;
228
234 [[nodiscard]] constexpr const_iterator cbegin() const;
235
241 [[nodiscard]] constexpr const_iterator cend() const;
242
250 [[nodiscard]] constexpr reference front();
251
260 [[nodiscard]] constexpr const_reference front() const;
261
269 [[nodiscard]] constexpr reference back();
270
279 [[nodiscard]] constexpr const_reference back() const;
280
286 [[nodiscard]] constexpr const storage_type_without_cvrefpointer& buffer() const noexcept
287 {
288 if constexpr (std::is_pointer_v<storage_type>)
289 {
290 return *m_buffer;
291 }
292 else
293 {
294 return m_buffer;
295 }
296 }
297
303 [[nodiscard]] constexpr storage_type_without_cvrefpointer& buffer() noexcept
304 {
305 if constexpr (std::is_pointer_v<storage_type>)
306 {
307 return *m_buffer;
308 }
309 else
310 {
311 return m_buffer;
312 }
313 }
314
322 [[nodiscard]] static constexpr size_type compute_block_count(size_type bits_count) noexcept;
323
331 [[nodiscard]] storage_type extract_storage() noexcept
333 {
334 return std::move(m_buffer);
335 }
336
337 protected:
338
347
358
359 constexpr ~dynamic_bitset_base() = default;
360
361 constexpr dynamic_bitset_base(const dynamic_bitset_base&) = default;
362 constexpr dynamic_bitset_base(dynamic_bitset_base&&) noexcept = default;
363
364 constexpr dynamic_bitset_base& operator=(const dynamic_bitset_base&) = default;
365 constexpr dynamic_bitset_base& operator=(dynamic_bitset_base&&) noexcept = default;
366
376 constexpr void resize(size_type n, value_type b = false);
377
384 constexpr void clear() noexcept;
385
397
409 constexpr iterator insert(const_iterator pos, size_type count, value_type value);
410
423 template <std::input_iterator InputIt>
424 constexpr iterator insert(const_iterator pos, InputIt first, InputIt last);
425
434 constexpr iterator insert(const_iterator pos, std::initializer_list<value_type> ilist);
435
446
456
467
474 constexpr void push_back(value_type value);
475
482 constexpr void pop_back();
483
489 constexpr void zero_unused_bits();
490
498 [[nodiscard]] size_type count_non_null() const noexcept;
499
500 private:
501
502 static constexpr std::size_t s_bits_per_block = sizeof(block_type) * CHAR_BIT;
504
510 [[nodiscard]] static constexpr size_type block_index(size_type pos) noexcept;
511
517 [[nodiscard]] static constexpr size_type bit_index(size_type pos) noexcept;
518
524 [[nodiscard]] static constexpr block_type bit_mask(size_type pos) noexcept;
525
530 [[nodiscard]] constexpr size_type count_extra_bits() const noexcept;
531
538 constexpr void update_null_count(bool old_value, bool new_value);
539
540 storage_type m_buffer;
541 size_type m_size;
542 size_type m_null_count;
543
544 friend class bitset_iterator<self_type, true>;
545 friend class bitset_iterator<self_type, false>;
546 friend class bitset_reference<self_type>;
547 };
548
549 template <typename B>
550 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
551 constexpr auto dynamic_bitset_base<B>::size() const noexcept -> size_type
552 {
553 return m_size;
554 }
555
556 template <typename B>
557 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
558 constexpr bool dynamic_bitset_base<B>::empty() const noexcept
559 {
560 return m_size == 0;
561 }
562
563 template <typename B>
564 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
565 constexpr auto dynamic_bitset_base<B>::null_count() const noexcept -> size_type
566 {
567 return m_null_count;
568 }
569
570 template <typename B>
571 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
573 {
574 SPARROW_ASSERT_TRUE(pos < size());
575 return reference(*this, pos);
576 }
577
578 template <typename B>
579 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
581 {
582 return test(pos);
583 }
584
585 template <typename B>
586 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
587 constexpr bool dynamic_bitset_base<B>::test(size_type pos) const
588 {
589 SPARROW_ASSERT_TRUE(pos < size());
590 if constexpr (std::is_pointer_v<storage_type>)
591 {
592 if (m_buffer == nullptr)
593 {
594 return true;
595 }
596 }
597 if (data() == nullptr)
598 {
599 return true;
600 }
601 return !m_null_count || buffer().data()[block_index(pos)] & bit_mask(pos);
602 }
603
604 template <typename B>
605 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
607 {
608 SPARROW_ASSERT_TRUE(pos < size());
609 if (data() == nullptr)
610 {
611 if (value == true) // In this case, we don't need to set the bit
612 {
613 return;
614 }
615 else
616 {
617 if constexpr (requires(storage_type_without_cvrefpointer s) { s.resize(0); })
618 {
619 constexpr block_type true_value = block_type(~block_type(0));
620 const auto block_count = compute_block_count(size());
621 buffer().resize(block_count, true_value);
623 }
624 else
625 {
626 throw std::runtime_error("Cannot set a bit in a null buffer.");
627 }
628 }
629 }
630 block_type& block = buffer().data()[block_index(pos)];
631 const bool old_value = block & bit_mask(pos);
632 if (value)
633 {
634 block |= bit_mask(pos);
635 }
636 else
637 {
638 block &= block_type(~bit_mask(pos));
639 }
640 update_null_count(old_value, value);
641 }
642
643 template <typename B>
644 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
645 constexpr auto dynamic_bitset_base<B>::data() noexcept -> block_type*
646 {
647 if constexpr (std::is_pointer_v<storage_type>)
648 {
649 if (m_buffer == nullptr)
650 {
651 return nullptr;
652 }
653 }
654 return buffer().data();
655 }
656
657 template <typename B>
658 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
659 constexpr auto dynamic_bitset_base<B>::data() const noexcept -> const block_type*
660 {
661 return buffer().data();
662 }
663
664 template <typename B>
665 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
666 constexpr auto dynamic_bitset_base<B>::block_count() const noexcept -> size_type
667 {
668 return buffer().size();
669 }
670
671 template <typename B>
672 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
673 constexpr void dynamic_bitset_base<B>::swap(self_type& rhs) noexcept
674 {
675 using std::swap;
676 swap(m_buffer, rhs.m_buffer);
677 swap(m_size, rhs.m_size);
678 swap(m_null_count, rhs.m_null_count);
679 }
680
681 template <typename B>
682 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
684 {
685 return iterator(this, 0u);
686 }
687
688 template <typename B>
689 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
691 {
692 return iterator(this, size());
693 }
694
695 template <typename B>
696 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
698 {
699 return cbegin();
700 }
701
702 template <typename B>
703 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
705 {
706 return cend();
707 }
708
709 template <typename B>
710 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
712 {
713 return const_iterator(this, 0);
714 }
715
716 template <typename B>
717 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
719 {
720 return const_iterator(this, size());
721 }
722
723 template <typename B>
724 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
726 {
727 if (pos >= size())
728 {
729 throw std::out_of_range(
730 "dynamic_bitset_base::at: index out of range for dynamic_bitset_base of size"
731 + std::to_string(size()) + " at index " + std::to_string(pos)
732 );
733 }
734 return (*this)[pos];
735 }
736
737 template <typename B>
738 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
740 {
741 if (pos >= size())
742 {
743 throw std::out_of_range(
744 "dynamic_bitset_base::at: index out of range for dynamic_bitset_base of size"
745 + std::to_string(size()) + " at index " + std::to_string(pos)
746 );
747 }
748 return test(pos);
749 }
750
751 template <typename B>
752 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
754 {
756 return (*this)[0];
757 }
758
759 template <typename B>
760 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
762 {
764 if (data() == nullptr)
765 {
766 return true;
767 }
768 return (*this)[0];
769 }
770
771 template <typename B>
772 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
774 {
776 return (*this)[size() - 1];
777 }
778
779 template <typename B>
780 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
782 {
784 if (data() == nullptr)
785 {
786 return true;
787 }
788 return (*this)[size() - 1];
789 }
790
791 template <typename B>
792 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
794 : m_buffer(std::move(buf))
795 , m_size(size)
796 , m_null_count(m_size - count_non_null())
797 {
798 }
799
800 template <typename B>
801 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
803 : m_buffer(std::move(buf))
804 , m_size(size)
805 , m_null_count(null_count)
806 {
807 }
808
809 template <typename B>
810 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
812 {
813 return bits_count / s_bits_per_block + static_cast<size_type>(bits_count % s_bits_per_block != 0);
814 }
815
816 template <typename B>
817 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
818 constexpr auto dynamic_bitset_base<B>::block_index(size_type pos) noexcept -> size_type
819 {
820 return pos / s_bits_per_block;
821 }
822
823 template <typename B>
824 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
825 constexpr auto dynamic_bitset_base<B>::bit_index(size_type pos) noexcept -> size_type
826 {
827 return pos % s_bits_per_block;
828 }
829
830 template <typename B>
831 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
832 constexpr auto dynamic_bitset_base<B>::bit_mask(size_type pos) noexcept -> block_type
833 {
834 const size_type bit = bit_index(pos);
835 return static_cast<block_type>(block_type(1) << bit);
836 }
837
838 template <typename B>
839 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
841 {
842 if constexpr (std::is_pointer_v<storage_type>)
843 {
844 if (m_buffer == nullptr)
845 {
846 return m_size;
847 }
848 }
849 if (data() == nullptr || buffer().empty())
850 {
851 return m_size;
852 }
853
854 int res = 0;
855 size_t full_blocks = m_size / s_bits_per_block;
856 for (size_t i = 0; i < full_blocks; ++i)
857 {
858 res += std::popcount(buffer().data()[i]);
859 }
860 if (full_blocks != buffer().size())
861 {
862 const size_t bits_count = m_size % s_bits_per_block;
863 const block_type mask = ~block_type(~block_type(0) << bits_count);
864 const block_type block = buffer().data()[full_blocks] & mask;
865 res += std::popcount(block);
866 }
867
868 return static_cast<size_t>(res);
869 }
870
871 template <typename B>
872 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
873 constexpr auto dynamic_bitset_base<B>::count_extra_bits() const noexcept -> size_type
874 {
875 return bit_index(size());
876 }
877
878 template <typename B>
879 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
881 {
882 if (data() == nullptr)
883 {
884 return;
885 }
886 const size_type extra_bits = count_extra_bits();
887 if (extra_bits != 0)
888 {
889 buffer().back() &= block_type(~(~block_type(0) << extra_bits));
890 }
891 }
892
893 template <typename B>
894 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
895 constexpr void dynamic_bitset_base<B>::update_null_count(bool old_value, bool new_value)
896 {
897 if (new_value && !old_value)
898 {
899 --m_null_count;
900 }
901 else if (!new_value && old_value)
902 {
903 ++m_null_count;
904 }
905 }
906
907 template <typename B>
908 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
910 {
911 if ((data() == nullptr) && b)
912 {
913 m_size = n;
914 return;
915 }
916 size_type old_block_count = buffer().size();
917 const size_type new_block_count = compute_block_count(n);
918 const block_type value = b ? block_type(~block_type(0)) : block_type(0);
919
920 if (new_block_count != old_block_count)
921 {
922 if (data() == nullptr)
923 {
924 constexpr block_type true_value = block_type(~block_type(0));
925 old_block_count = compute_block_count(size());
926 buffer().resize(old_block_count, true_value);
928 }
929 buffer().resize(new_block_count, value);
930 }
931
932 if (b && (n > m_size))
933 {
934 const size_type extra_bits = count_extra_bits();
935 if (extra_bits > 0)
936 {
937 buffer().data()[old_block_count - 1] |= static_cast<block_type>(value << extra_bits);
938 }
939 }
940
941 m_size = n;
942 m_null_count = m_size - count_non_null();
944 }
945
946 template <typename B>
947 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
948 constexpr void dynamic_bitset_base<B>::clear() noexcept
949 {
950 buffer().clear();
951 m_size = 0;
952 m_null_count = 0;
953 }
954
955 template <typename B>
956 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
959 {
960 return insert(pos, 1, value);
961 }
962
963 template <typename B>
964 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
967 {
968 SPARROW_ASSERT_TRUE(cbegin() <= pos);
969 SPARROW_ASSERT_TRUE(pos <= cend());
970 const auto index = static_cast<size_type>(std::distance(cbegin(), pos));
971 if (data() == nullptr && value)
972 {
973 m_size += count;
974 }
975 else
976 {
977 const size_type old_size = size();
978 const size_type new_size = old_size + count;
979
980 // TODO: The current implementation is not efficient. It can be improved.
981
982 resize(new_size);
983
984 for (size_type i = old_size + count - 1; i >= index + count; --i)
985 {
986 set(i, test(i - count));
987 }
988
989 for (size_type i = 0; i < count; ++i)
990 {
991 set(index + i, value);
992 }
993 }
994
995 return iterator(this, index);
996 }
997
998 template <typename B>
999 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1000 template <std::input_iterator InputIt>
1002 dynamic_bitset_base<B>::insert(const_iterator pos, InputIt first, InputIt last)
1003 {
1004 const auto index = static_cast<size_type>(std::distance(cbegin(), pos));
1005 const auto count = static_cast<size_type>(std::distance(first, last));
1006 if (data() == nullptr)
1007 {
1008 if (std::all_of(
1009 first,
1010 last,
1011 [](auto v)
1012 {
1013 return bool(v);
1014 }
1015 ))
1016 {
1017 m_size += count;
1018 }
1019 return iterator(this, index);
1020 }
1021 SPARROW_ASSERT_TRUE(cbegin() <= pos);
1022 SPARROW_ASSERT_TRUE(pos <= cend());
1023
1024 const size_type old_size = size();
1025 const size_type new_size = old_size + count;
1026
1027 resize(new_size);
1028
1029 // TODO: The current implementation is not efficient. It can be improved.
1030
1031 for (size_type i = old_size + count - 1; i >= index + count; --i)
1032 {
1033 set(i, test(i - count));
1034 }
1035
1036 for (size_type i = 0; i < count; ++i)
1037 {
1038 set(index + i, *first++);
1039 }
1040
1041 return iterator(this, index);
1042 }
1043
1044 template <typename B>
1045 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1048 {
1049 return insert(pos, value);
1050 }
1051
1052 template <typename B>
1053 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1055 {
1056 SPARROW_ASSERT_TRUE(cbegin() <= pos);
1057 SPARROW_ASSERT_TRUE(pos < cend());
1058
1059 return erase(pos, pos + 1);
1060 }
1061
1062 template <typename B>
1063 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1066 {
1067 SPARROW_ASSERT_TRUE(cbegin() <= first);
1068 SPARROW_ASSERT_TRUE(first <= last);
1069 SPARROW_ASSERT_TRUE(last <= cend());
1070
1071 const auto first_index = static_cast<size_type>(std::distance(cbegin(), first));
1072 const auto last_index = static_cast<size_type>(std::distance(cbegin(), last));
1073 const size_type count = last_index - first_index;
1074
1075 if (data() == nullptr)
1076 {
1077 m_size -= count;
1078 }
1079 else
1080 {
1081 if (last == cend())
1082 {
1083 resize(first_index);
1084 return end();
1085 }
1086
1087 // TODO: The current implementation is not efficient. It can be improved.
1088
1089 const size_type bit_to_move = size() - last_index;
1090 for (size_type i = 0; i < bit_to_move; ++i)
1091 {
1092 set(first_index + i, test(last_index + i));
1093 }
1094
1095 resize(size() - count);
1096 }
1097 return iterator(this, first_index);
1098 }
1099
1100 template <typename B>
1101 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1103 {
1104 resize(size() + 1, value);
1105 }
1106
1107 template <typename B>
1108 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1110 {
1111 if (empty())
1112 {
1113 return;
1114 }
1115 resize(size() - 1);
1116 }
1117}
Iterator used to iterate over the bits of a dynamic bitset as if they were addressable values.
A proxy reference class that provides mutable access to individual bits in a bitset.
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
size_type count_non_null() const noexcept
constexpr void push_back(value_type value)
constexpr iterator erase(const_iterator pos)
std::remove_pointer_t< std::remove_cvref_t< storage_type > > storage_type_without_cvrefpointer
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
Extracts the underlying storage (move operation).
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
static constexpr size_type compute_block_count(size_type bits_count) noexcept
Computes the number of blocks needed to store the specified number of bits.
constexpr size_type size() const noexcept
Returns the number of bits in the bitset.
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)
Accesses a bit without bounds checking.
constexpr dynamic_bitset_base(storage_type buffer, size_type size, size_type null_count)
Constructs a bitset with the given storage, size, and null count.
constexpr dynamic_bitset_base(const dynamic_bitset_base &)=default
constexpr block_type * data() noexcept
constexpr void swap(self_type &rhs) noexcept
constexpr dynamic_bitset_base(storage_type buffer, size_type size)
Constructs a bitset with the given storage and size.
constexpr const_iterator cend() const
constexpr storage_type_without_cvrefpointer & buffer() noexcept
Returns a mutable reference to the underlying buffer.
#define SPARROW_ASSERT_TRUE(expr__)