sparrow 2.3.1
C++20 idiomatic APIs for the Apache Arrow Columnar Format
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 <climits>
20#include <stdexcept>
21#include <string>
22#include <type_traits>
23
28
29namespace sparrow
30{
67 template <typename B, null_count_policy NCP = tracking_null_count<>>
68 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
69 class dynamic_bitset_base : protected NCP
70 {
71 public:
72
75 using storage_type = B;
76 using storage_type_without_cvrefpointer = std::remove_pointer_t<
77 std::remove_cvref_t<storage_type>>;
78 using block_type = typename storage_type_without_cvrefpointer::value_type;
80 using value_type = bool;
82 using const_reference = bool;
83 using size_type = typename storage_type_without_cvrefpointer::size_type;
85 using difference_type = typename storage_type_without_cvrefpointer::difference_type;
90
96 [[nodiscard]] constexpr size_type size() const noexcept;
97
98 constexpr void set_size(size_type new_size) noexcept;
99
105 [[nodiscard]] constexpr size_type offset() const noexcept;
106
116 constexpr void set_offset(size_type offset) noexcept;
117
123 [[nodiscard]] constexpr bool empty() const noexcept;
124
131 [[nodiscard]] constexpr size_type null_count() const noexcept
132 requires(NCP::track_null_count);
133
142 [[nodiscard]] constexpr bool test(size_type pos) const;
143
154 constexpr void set(size_type pos, value_type value);
155
164 [[nodiscard]] constexpr const_reference at(size_type pos) const;
165
174 [[nodiscard]] constexpr reference at(size_type pos);
175
184 [[nodiscard]] constexpr reference operator[](size_type i);
185
194 [[nodiscard]] constexpr const_reference operator[](size_type i) const;
195
201 [[nodiscard]] constexpr block_type* data() noexcept;
202
208 [[nodiscard]] constexpr const block_type* data() const noexcept;
209
215 [[nodiscard]] constexpr size_type block_count() const noexcept;
216
223 constexpr void swap(self_type& rhs) noexcept;
224
230 [[nodiscard]] constexpr iterator begin();
231
237 [[nodiscard]] constexpr iterator end();
238
244 [[nodiscard]] constexpr const_iterator begin() const;
245
251 [[nodiscard]] constexpr const_iterator end() const;
252
258 [[nodiscard]] constexpr const_iterator cbegin() const;
265 [[nodiscard]] constexpr const_iterator cend() const;
266
274 [[nodiscard]] constexpr reference front();
275
284 [[nodiscard]] constexpr const_reference front() const;
285
293 [[nodiscard]] constexpr reference back();
294
303 [[nodiscard]] constexpr const_reference back() const;
304
310 [[nodiscard]] constexpr const storage_type_without_cvrefpointer& buffer() const noexcept
311 {
312 if constexpr (std::is_pointer_v<storage_type>)
313 {
314 return *m_buffer;
315 }
316 else
317 {
318 return m_buffer;
319 }
320 }
321
327 [[nodiscard]] constexpr storage_type_without_cvrefpointer& buffer() noexcept
328 {
329 if constexpr (std::is_pointer_v<storage_type>)
330 {
331 return *m_buffer;
332 }
333 else
334 {
335 return m_buffer;
336 }
337 }
338
346 [[nodiscard]] static constexpr size_type compute_block_count(size_type bits_count) noexcept;
347
355 [[nodiscard]] storage_type extract_storage() noexcept
357 {
358 return std::move(m_buffer);
359 }
360
361 protected:
362
372
384
398 requires(NCP::track_null_count);
399
400 constexpr ~dynamic_bitset_base() = default;
401
402 constexpr dynamic_bitset_base(const dynamic_bitset_base&) = default;
403 constexpr dynamic_bitset_base(dynamic_bitset_base&&) noexcept = default;
404
405 constexpr dynamic_bitset_base& operator=(const dynamic_bitset_base&) = default;
406 constexpr dynamic_bitset_base& operator=(dynamic_bitset_base&&) noexcept = default;
407
417 constexpr void resize(size_type n, value_type b = false);
418
425 constexpr void clear() noexcept;
426
438
450 constexpr iterator insert(const_iterator pos, size_type count, value_type value);
451
464 template <std::input_iterator InputIt>
465 constexpr iterator insert(const_iterator pos, InputIt first, InputIt last);
466
475 constexpr iterator insert(const_iterator pos, std::initializer_list<value_type> ilist);
476
487
497
508
515 constexpr void push_back(value_type value);
516
523 constexpr void pop_back();
524
530 constexpr void zero_unused_bits();
531
532 private:
533
534 static constexpr std::size_t s_bits_per_block = sizeof(block_type) * CHAR_BIT;
536
542 [[nodiscard]] static constexpr size_type block_index(size_type pos) noexcept;
543
549 [[nodiscard]] static constexpr size_type bit_index(size_type pos) noexcept;
550
556 [[nodiscard]] static constexpr block_type bit_mask(size_type pos) noexcept;
557
562 [[nodiscard]] constexpr size_type count_extra_bits() const noexcept;
563
570 constexpr void shift_bits_right(size_type start, size_type length, size_type shift_amount);
571
578 constexpr void fill_bits(size_type start, size_type count, value_type value);
579
580 storage_type m_buffer;
581 size_type m_size;
582 size_type m_offset;
583
584 friend class bitset_iterator<self_type, true>;
585 friend class bitset_iterator<self_type, false>;
586 friend class bitset_reference<self_type>;
587 };
588
589 template <typename B, null_count_policy NCP>
590 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
591 constexpr auto dynamic_bitset_base<B, NCP>::size() const noexcept -> size_type
592 {
593 return m_size;
594 }
595
596 template <typename B, null_count_policy NCP>
597 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
598 constexpr void dynamic_bitset_base<B, NCP>::set_size(size_type new_size) noexcept
599 {
600 m_size = new_size;
601 }
602
603 template <typename B, null_count_policy NCP>
604 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
605 constexpr bool dynamic_bitset_base<B, NCP>::empty() const noexcept
606 {
607 return m_size == 0;
608 }
609
610 template <typename B, null_count_policy NCP>
611 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
612 constexpr auto dynamic_bitset_base<B, NCP>::offset() const noexcept -> size_type
613 {
614 return m_offset;
615 }
616
617 template <typename B, null_count_policy NCP>
618 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
620 {
621 m_offset = offset;
622 }
623
624 template <typename B, null_count_policy NCP>
625 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
626 constexpr auto dynamic_bitset_base<B, NCP>::null_count() const noexcept -> size_type
627 requires(NCP::track_null_count)
628 {
629 return NCP::null_count();
630 }
631
632 template <typename B, null_count_policy NCP>
633 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
635 {
636 SPARROW_ASSERT_TRUE(pos < size());
637 return reference(*this, pos);
638 }
639
640 template <typename B, null_count_policy NCP>
641 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
643 {
644 return test(pos);
645 }
646
647 template <typename B, null_count_policy NCP>
648 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
650 {
651 SPARROW_ASSERT_TRUE(pos < size());
652 if constexpr (std::is_pointer_v<storage_type>)
653 {
654 if (m_buffer == nullptr)
655 {
656 return true;
657 }
658 }
659 if (data() == nullptr)
660 {
661 return true;
662 }
663 const size_type actual_pos = m_offset + pos;
664 // Ensure the specific bit we're accessing is within the buffer
665 SPARROW_ASSERT_TRUE(block_index(actual_pos) < buffer().size());
666 return buffer().data()[block_index(actual_pos)] & bit_mask(actual_pos);
667 }
668
669 template <typename B, null_count_policy NCP>
670 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
672 {
673 SPARROW_ASSERT_TRUE(pos < size());
674 const size_type actual_pos = m_offset + pos;
675 const size_type target_block = block_index(actual_pos);
676
677 if (data() == nullptr)
678 {
679 if (value == true) // In this case, we don't need to set the bit
680 {
681 return;
682 }
683 else
684 {
685 if constexpr (requires(storage_type_without_cvrefpointer s) { s.resize(0); })
686 {
687 constexpr block_type true_value = block_type(~block_type(0));
688 const auto block_count = compute_block_count(size() + m_offset);
689 buffer().resize(block_count, true_value);
691 }
692 else
693 {
694 throw std::runtime_error("Cannot set a bit in a null buffer.");
695 }
696 }
697 }
698 else if (target_block >= buffer().size())
699 {
700 // Buffer exists but is too small for the bit we're accessing
701 if constexpr (requires(storage_type_without_cvrefpointer s) { s.resize(0); })
702 {
703 constexpr block_type true_value = block_type(~block_type(0));
704 buffer().resize(target_block + 1, true_value);
706 }
707 else
708 {
709 SPARROW_ASSERT_TRUE(target_block < buffer().size());
710 }
711 }
712
713 block_type& block = buffer().data()[target_block];
714 const bool old_value = block & bit_mask(actual_pos);
715 if (value)
716 {
717 block |= bit_mask(actual_pos);
718 }
719 else
720 {
721 block &= block_type(~bit_mask(actual_pos));
722 }
723 this->update_null_count(old_value, value);
724 }
725
726 template <typename B, null_count_policy NCP>
727 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
728 constexpr auto dynamic_bitset_base<B, NCP>::data() noexcept -> block_type*
729 {
730 if constexpr (std::is_pointer_v<storage_type>)
731 {
732 if (m_buffer == nullptr)
733 {
734 return nullptr;
735 }
736 }
737 return buffer().data();
738 }
739
740 template <typename B, null_count_policy NCP>
741 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
742 constexpr auto dynamic_bitset_base<B, NCP>::data() const noexcept -> const block_type*
743 {
744 return buffer().data();
745 }
746
747 template <typename B, null_count_policy NCP>
748 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
749 constexpr auto dynamic_bitset_base<B, NCP>::block_count() const noexcept -> size_type
750 {
751 return buffer().size();
752 }
753
754 template <typename B, null_count_policy NCP>
755 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
756 constexpr void dynamic_bitset_base<B, NCP>::swap(self_type& rhs) noexcept
757 {
758 using std::swap;
759 swap(m_buffer, rhs.m_buffer);
760 swap(m_size, rhs.m_size);
761 swap(m_offset, rhs.m_offset);
762 this->swap_null_count(rhs);
763 }
764
765 template <typename B, null_count_policy NCP>
766 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
768 {
769 return iterator(this, 0u);
770 }
771
772 template <typename B, null_count_policy NCP>
773 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
775 {
776 return iterator(this, size());
777 }
778
779 template <typename B, null_count_policy NCP>
780 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
782 {
783 return cbegin();
784 }
785
786 template <typename B, null_count_policy NCP>
787 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
789 {
790 return cend();
791 }
792
793 template <typename B, null_count_policy NCP>
794 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
796 {
797 return const_iterator(this, 0);
798 }
799
800 template <typename B, null_count_policy NCP>
801 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
803 {
804 return const_iterator(this, size());
805 }
806
807 template <typename B, null_count_policy NCP>
808 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
810 {
811 if (pos >= size())
812 {
813 throw std::out_of_range(
814 "dynamic_bitset_base::at: index out of range for dynamic_bitset_base of size"
815 + std::to_string(size()) + " at index " + std::to_string(pos)
816 );
817 }
818 return (*this)[pos];
819 }
820
821 template <typename B, null_count_policy NCP>
822 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
824 {
825 if (pos >= size())
826 {
827 throw std::out_of_range(
828 "dynamic_bitset_base::at: index out of range for dynamic_bitset_base of size"
829 + std::to_string(size()) + " at index " + std::to_string(pos)
830 );
831 }
832 return test(pos);
833 }
834
835 template <typename B, null_count_policy NCP>
836 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
838 {
840 return (*this)[0];
841 }
842
843 template <typename B, null_count_policy NCP>
844 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
846 {
848 if (data() == nullptr)
849 {
850 return true;
851 }
852 return (*this)[0];
853 }
854
855 template <typename B, null_count_policy NCP>
856 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
858 {
860 return (*this)[size() - 1];
861 }
862
863 template <typename B, null_count_policy NCP>
864 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
866 {
868 if (data() == nullptr)
869 {
870 return true;
871 }
872 return (*this)[size() - 1];
873 }
874
875 template <typename B, null_count_policy NCP>
876 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
878 : m_buffer(std::move(buf))
879 , m_size(size)
880 , m_offset(0)
881 {
882 this->initialize_null_count(data(), m_size, buffer().size(), m_offset);
883 }
884
885 template <typename B, null_count_policy NCP>
886 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
888 : m_buffer(std::move(buf))
889 , m_size(size)
890 , m_offset(offset)
891 {
892 this->initialize_null_count(data(), m_size, buffer().size(), m_offset);
893 }
894
895 template <typename B, null_count_policy NCP>
896 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
898 storage_type buf,
902 )
903 requires(NCP::track_null_count)
904 : m_buffer(std::move(buf))
905 , m_size(size)
906 , m_offset(offset)
907 {
909 }
910
911 template <typename B, null_count_policy NCP>
912 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
914 {
915 return bits_count / s_bits_per_block + static_cast<size_type>(bits_count % s_bits_per_block != 0);
916 }
917
918 template <typename B, null_count_policy NCP>
919 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
920 constexpr auto dynamic_bitset_base<B, NCP>::block_index(size_type pos) noexcept -> size_type
921 {
922 return pos / s_bits_per_block;
923 }
924
925 template <typename B, null_count_policy NCP>
926 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
927 constexpr auto dynamic_bitset_base<B, NCP>::bit_index(size_type pos) noexcept -> size_type
928 {
929 return pos % s_bits_per_block;
930 }
931
932 template <typename B, null_count_policy NCP>
933 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
934 constexpr auto dynamic_bitset_base<B, NCP>::bit_mask(size_type pos) noexcept -> block_type
935 {
936 const size_type bit = bit_index(pos);
937 return static_cast<block_type>(block_type(1) << bit);
938 }
939
940 template <typename B, null_count_policy NCP>
941 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
942 constexpr auto dynamic_bitset_base<B, NCP>::count_extra_bits() const noexcept -> size_type
943 {
944 return bit_index(size() + m_offset);
945 }
946
947 template <typename B, null_count_policy NCP>
948 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
950 {
951 if (data() == nullptr)
952 {
953 return;
954 }
955 const size_type extra_bits = count_extra_bits();
956 if (extra_bits != 0)
957 {
958 buffer().back() &= block_type(~(~block_type(0) << extra_bits));
959 }
960 }
961
962 template <typename B, null_count_policy NCP>
963 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
965 {
966 if ((data() == nullptr) && b)
967 {
968 m_size = n;
969 return;
970 }
971 auto& buffer = this->buffer();
972 const size_type old_size = m_size;
973 size_type old_block_count = buffer.size();
974 const size_type new_block_count = compute_block_count(n + m_offset);
975 const block_type value = b ? block_type(~block_type(0)) : block_type(0);
976
977 if (new_block_count != old_block_count)
978 {
979 if (data() == nullptr)
980 {
981 constexpr block_type true_value = block_type(~block_type(0));
982 old_block_count = compute_block_count(old_size + m_offset);
983 buffer.resize(old_block_count, true_value);
984 // Zero out bits beyond old_size in the last block
985 const size_type old_extra_bits = bit_index(old_size + m_offset);
986 if (old_extra_bits != 0)
987 {
988 buffer.back() &= block_type(~(~block_type(0) << old_extra_bits));
989 }
990 }
991 buffer.resize(new_block_count, value);
992 }
993
994 if (n > old_size)
995 {
996 // Calculate extra bits based on OLD size
997 const size_type old_extra_bits = bit_index(old_size + m_offset);
998 if (old_extra_bits > 0)
999 {
1000 const size_type old_last_block_idx = compute_block_count(old_size + m_offset) - 1;
1001 auto& last_block = buffer.data()[old_last_block_idx];
1002 if (b)
1003 {
1004 // Set bits from old_extra_bits to end of block to true
1005 const auto mask = static_cast<block_type>(value << old_extra_bits);
1006 last_block |= mask;
1007 }
1008 else
1009 {
1010 // Clear bits from old_extra_bits to end of block
1011 const auto mask = static_cast<block_type>(~(~block_type(0) << old_extra_bits));
1012 last_block &= mask;
1013 }
1014 }
1015 }
1016
1017 m_size = n;
1019 this->recompute_null_count(data(), m_size, buffer.size(), m_offset);
1020 }
1021
1022 template <typename B, null_count_policy NCP>
1023 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1024 constexpr void dynamic_bitset_base<B, NCP>::clear() noexcept
1025 {
1026 buffer().clear();
1027 m_size = 0;
1028 this->clear_null_count();
1029 }
1030
1031 template <typename B, null_count_policy NCP>
1032 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1035 {
1036 return insert(pos, 1, value);
1037 }
1038
1039 template <typename B, null_count_policy NCP>
1040 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1043 {
1044 SPARROW_ASSERT_TRUE(cbegin() <= pos);
1045 SPARROW_ASSERT_TRUE(pos <= cend());
1046 const auto index = static_cast<size_type>(std::distance(cbegin(), pos));
1047 if (data() == nullptr && value)
1048 {
1049 m_size += count;
1050 }
1051 else
1052 {
1053 const size_type old_size = size();
1054 const size_type new_size = old_size + count;
1055
1056 resize(new_size);
1057
1058 // Shift existing bits to make room for new ones
1059 const size_type bits_to_shift = old_size - index;
1060 if (bits_to_shift > 0)
1061 {
1062 shift_bits_right(index + m_offset, bits_to_shift, count);
1063 }
1064
1065 // Fill the inserted region with the specified value
1066 fill_bits(index + m_offset, count, value);
1067
1068 // Recompute null count after all bit manipulations are complete
1069 this->recompute_null_count(data(), m_size, buffer().size(), m_offset);
1070 }
1071
1072 return iterator(this, index);
1073 }
1074
1075 template <typename B, null_count_policy NCP>
1076 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1077 template <std::input_iterator InputIt>
1079 dynamic_bitset_base<B, NCP>::insert(const_iterator pos, InputIt first, InputIt last)
1080 {
1081 const auto index = static_cast<size_type>(std::distance(cbegin(), pos));
1082 const auto count = static_cast<size_type>(std::distance(first, last));
1083 if (data() == nullptr)
1084 {
1085 if (std::all_of(
1086 first,
1087 last,
1088 [](auto v)
1089 {
1090 return bool(v);
1091 }
1092 ))
1093 {
1094 m_size += count;
1095 }
1096 return iterator(this, index);
1097 }
1098 SPARROW_ASSERT_TRUE(cbegin() <= pos);
1099 SPARROW_ASSERT_TRUE(pos <= cend());
1100
1101 const size_type old_size = size();
1102 const size_type new_size = old_size + count;
1103
1104 resize(new_size);
1105
1106 // Shift existing bits to make room for new ones
1107 const size_type bits_to_shift = old_size - index;
1108 if (bits_to_shift > 0)
1109 {
1110 shift_bits_right(index + m_offset, bits_to_shift, count);
1111 }
1112
1113 // Insert bits from the iterator range
1114 for (size_type i = 0; i < count; ++i)
1115 {
1116 set(index + i, *first++);
1117 }
1118
1119 // Recompute null count after all bit manipulations are complete
1120 this->recompute_null_count(data(), m_size, buffer().size(), m_offset);
1121
1122 return iterator(this, index);
1123 }
1124
1125 template <typename B, null_count_policy NCP>
1126 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1129 {
1130 return insert(pos, value);
1131 }
1132
1133 template <typename B, null_count_policy NCP>
1134 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1136 {
1137 SPARROW_ASSERT_TRUE(cbegin() <= pos);
1138 SPARROW_ASSERT_TRUE(pos < cend());
1139
1140 return erase(pos, pos + 1);
1141 }
1142
1143 template <typename B, null_count_policy NCP>
1144 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1147 {
1148 SPARROW_ASSERT_TRUE(cbegin() <= first);
1149 SPARROW_ASSERT_TRUE(first <= last);
1150 SPARROW_ASSERT_TRUE(last <= cend());
1151
1152 const auto first_index = static_cast<size_type>(std::distance(cbegin(), first));
1153 const auto last_index = static_cast<size_type>(std::distance(cbegin(), last));
1154 const size_type count = last_index - first_index;
1155
1156 if (data() == nullptr)
1157 {
1158 m_size -= count;
1159 }
1160 else
1161 {
1162 if (last == cend())
1163 {
1164 resize(first_index);
1165 return end();
1166 }
1167
1168 // Shift bits left using block-level operations
1169 const size_type bits_to_move = size() - last_index;
1170 if (bits_to_move > 0)
1171 {
1172 // Copy bits from [last_index, end) to [first_index, ...)
1173 for (size_type i = 0; i < bits_to_move; ++i)
1174 {
1175 const bool bit_value = test(last_index + i);
1176 set(first_index + i, bit_value);
1177 }
1178 }
1179
1180 resize(size() - count);
1181 }
1182 return iterator(this, first_index);
1183 }
1184
1185 template <typename B, null_count_policy NCP>
1186 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1188 {
1189 resize(size() + 1, value);
1190 }
1191
1192 template <typename B, null_count_policy NCP>
1193 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1195 {
1196 if (empty())
1197 {
1198 return;
1199 }
1200 resize(size() - 1);
1201 }
1202
1203 template <typename B, null_count_policy NCP>
1204 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1205 constexpr void
1206 dynamic_bitset_base<B, NCP>::shift_bits_right(size_type start, size_type length, size_type shift_amount)
1207 {
1208 if (length == 0 || shift_amount == 0)
1209 {
1210 return;
1211 }
1212
1213 auto* blocks = data();
1214 const size_type src_start_bit = start;
1215 const size_type dst_start_bit = start + shift_amount;
1216
1217 // Work backwards to avoid overwriting source data
1218 // Process bit-by-bit in reverse order, but in block-sized chunks where possible
1219 size_type remaining = length;
1220
1221 while (remaining > 0)
1222 {
1223 // Calculate position for this iteration (working backwards)
1224 const size_type current_offset = remaining - 1;
1225 const size_type src_bit = src_start_bit + current_offset;
1226 const size_type dst_bit = dst_start_bit + current_offset;
1227
1228 const size_type src_block_idx = block_index(src_bit);
1229 const size_type dst_block_idx = block_index(dst_bit);
1230 const size_type src_bit_offset = bit_index(src_bit);
1231 const size_type dst_bit_offset = bit_index(dst_bit);
1232
1233 // Determine how many bits we can process in this iteration
1234 // We can process up to the start of the current block, or all remaining bits
1235 const size_type bits_available_in_src_block = src_bit_offset + 1;
1236 const size_type bits_available_in_dst_block = dst_bit_offset + 1;
1237 const size_type bits_this_iter = std::min(
1238 {remaining, bits_available_in_src_block, bits_available_in_dst_block}
1239 );
1240
1241 // Extract bits from source
1242 const size_type extract_start_offset = src_bit_offset - (bits_this_iter - 1);
1243 const block_type mask = static_cast<block_type>(
1244 ((block_type(1) << bits_this_iter) - 1) << extract_start_offset
1245 );
1246 const block_type src_bits = static_cast<block_type>(
1247 (blocks[src_block_idx] & mask) >> extract_start_offset
1248 );
1249
1250 // Write bits to destination
1251 const size_type write_start_offset = dst_bit_offset - (bits_this_iter - 1);
1252 const block_type dst_mask = static_cast<block_type>(
1253 ((block_type(1) << bits_this_iter) - 1) << write_start_offset
1254 );
1255 blocks[dst_block_idx] = static_cast<block_type>(
1256 (blocks[dst_block_idx] & ~dst_mask) | ((src_bits << write_start_offset) & dst_mask)
1257 );
1258
1259 remaining -= bits_this_iter;
1260 }
1261 }
1262
1263 template <typename B, null_count_policy NCP>
1264 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1265 constexpr void dynamic_bitset_base<B, NCP>::fill_bits(size_type start, size_type count, value_type value)
1266 {
1267 if (count == 0)
1268 {
1269 return;
1270 }
1271
1272 auto* blocks = data();
1273 const block_type fill_pattern = value ? static_cast<block_type>(~block_type(0)) : block_type(0);
1274
1275 size_type current_bit = start;
1276 size_type remaining = count;
1277
1278 // Handle first partial block if not aligned
1279 const size_type first_bit_offset = bit_index(current_bit);
1280 if (first_bit_offset != 0)
1281 {
1282 const size_type bits_in_first = std::min(remaining, s_bits_per_block - first_bit_offset);
1283 const block_type mask = static_cast<block_type>(
1284 ((block_type(1) << bits_in_first) - 1) << first_bit_offset
1285 );
1286
1287 const size_type block_idx = block_index(current_bit);
1288 if (value)
1289 {
1290 blocks[block_idx] |= mask;
1291 }
1292 else
1293 {
1294 blocks[block_idx] &= ~mask;
1295 }
1296
1297 current_bit += bits_in_first;
1298 remaining -= bits_in_first;
1299 }
1300
1301 // Fill complete blocks
1302 while (remaining >= s_bits_per_block)
1303 {
1304 blocks[block_index(current_bit)] = fill_pattern;
1305 current_bit += s_bits_per_block;
1306 remaining -= s_bits_per_block;
1307 }
1308
1309 // Handle last partial block
1310 if (remaining > 0)
1311 {
1312 const block_type mask = static_cast<block_type>((block_type(1) << remaining) - 1);
1313 const size_type block_idx = block_index(current_bit);
1314 if (value)
1315 {
1316 blocks[block_idx] |= mask;
1317 }
1318 else
1319 {
1320 blocks[block_idx] &= ~mask;
1321 }
1322 }
1323 }
1324
1325}
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.
constexpr const_reference at(size_type pos) const
constexpr reference operator[](size_type i)
Accesses a bit without bounds checking.
constexpr size_type block_count() const noexcept
typename storage_type_without_cvrefpointer::value_type block_type
constexpr dynamic_bitset_base(const dynamic_bitset_base &)=default
constexpr block_type * data() noexcept
constexpr void set(size_type pos, value_type value)
constexpr void set_size(size_type new_size) noexcept
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 const_iterator cbegin() const
constexpr iterator insert(const_iterator pos, value_type value)
constexpr storage_type_without_cvrefpointer & buffer() noexcept
Returns a mutable reference to the underlying buffer.
constexpr iterator erase(const_iterator pos)
constexpr bool test(size_type pos) const
constexpr iterator emplace(const_iterator pos, value_type value)
constexpr void resize(size_type n, value_type b=false)
bitset_iterator< self_type, true > const_iterator
constexpr size_type size() const noexcept
Returns the number of bits in the bitset.
bitset_iterator< self_type, false > iterator
constexpr const storage_type_without_cvrefpointer & buffer() const noexcept
typename storage_type_without_cvrefpointer::size_type size_type
constexpr const_iterator cend() const
constexpr bool empty() const noexcept
constexpr dynamic_bitset_base(storage_type buffer, size_type size)
Constructs a bitset with the given storage and size.
storage_type extract_storage() noexcept
Extracts the underlying storage (move operation).
std::remove_pointer_t< std::remove_cvref_t< storage_type > > storage_type_without_cvrefpointer
constexpr ~dynamic_bitset_base()=default
constexpr size_type null_count() const noexcept
constexpr dynamic_bitset_base(dynamic_bitset_base &&) noexcept=default
constexpr size_type offset() const noexcept
constexpr dynamic_bitset_base(storage_type buffer, size_type size, size_type offset)
Constructs a bitset with the given storage, size, and null count.
constexpr void push_back(value_type value)
constexpr void swap(self_type &rhs) noexcept
constexpr dynamic_bitset_base(storage_type buffer, size_type size, size_type offset, size_type null_count)
Constructs a bitset with the given storage, size, offset, and null count.
constexpr void set_offset(size_type offset) noexcept
dynamic_bitset_base< buffer< T >, tracking_null_count<> > self_type
typename storage_type_without_cvrefpointer::difference_type difference_type
void initialize_null_count(const BlockType *data, size_type bit_size, size_type block_count, size_type offset=0) noexcept
Initializes the null count by counting bits in the buffer.
constexpr void set_null_count(size_type count) noexcept
constexpr void swap_null_count(tracking_null_count &other) noexcept
void recompute_null_count(const BlockType *data, size_type bit_size, size_type block_count, size_type offset=0) noexcept
Recomputes the null count from the buffer.
constexpr void clear_null_count() noexcept
constexpr void update_null_count(bool old_value, bool new_value) noexcept
static constexpr bool track_null_count
Concept that checks if a type is a valid null count policy.
#define SPARROW_ASSERT_TRUE(expr__)
SPARROW_API int count(const std::string &key, int disabled_value=0)
constexpr std::size_t size(typelist< T... >={})
Gets the count of types contained in a typelist.
Definition mp_utils.hpp:216
Extensions to the C++ standard library.