sparrow 1.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 <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;
178
184 [[nodiscard]] constexpr const block_type* data() const noexcept;
185
191 [[nodiscard]] constexpr size_type block_count() const noexcept;
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 = static_cast<block_type>(
864 ~static_cast<block_type>(~static_cast<block_type>(0) << bits_count)
865 );
866 const block_type block = buffer().data()[full_blocks] & mask;
867 res += std::popcount(block);
868 }
869
870 return static_cast<size_t>(res);
871 }
872
873 template <typename B>
874 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
875 constexpr auto dynamic_bitset_base<B>::count_extra_bits() const noexcept -> size_type
876 {
877 return bit_index(size());
878 }
879
880 template <typename B>
881 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
883 {
884 if (data() == nullptr)
885 {
886 return;
887 }
888 const size_type extra_bits = count_extra_bits();
889 if (extra_bits != 0)
890 {
891 buffer().back() &= block_type(~(~block_type(0) << extra_bits));
892 }
893 }
894
895 template <typename B>
896 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
897 constexpr void dynamic_bitset_base<B>::update_null_count(bool old_value, bool new_value)
898 {
899 if (new_value && !old_value)
900 {
901 --m_null_count;
902 }
903 else if (!new_value && old_value)
904 {
905 ++m_null_count;
906 }
907 }
908
909 template <typename B>
910 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
912 {
913 if ((data() == nullptr) && b)
914 {
915 m_size = n;
916 return;
917 }
918 size_type old_block_count = buffer().size();
919 const size_type new_block_count = compute_block_count(n);
920 const block_type value = b ? block_type(~block_type(0)) : block_type(0);
921
922 if (new_block_count != old_block_count)
923 {
924 if (data() == nullptr)
925 {
926 constexpr block_type true_value = block_type(~block_type(0));
927 old_block_count = compute_block_count(size());
928 buffer().resize(old_block_count, true_value);
930 }
931 buffer().resize(new_block_count, value);
932 }
933
934 if (b && (n > m_size))
935 {
936 const size_type extra_bits = count_extra_bits();
937 if (extra_bits > 0)
938 {
939 buffer().data()[old_block_count - 1] |= static_cast<block_type>(value << extra_bits);
940 }
941 }
942
943 m_size = n;
944 m_null_count = m_size - count_non_null();
946 }
947
948 template <typename B>
949 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
950 constexpr void dynamic_bitset_base<B>::clear() noexcept
951 {
952 buffer().clear();
953 m_size = 0;
954 m_null_count = 0;
955 }
956
957 template <typename B>
958 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
961 {
962 return insert(pos, 1, value);
963 }
964
965 template <typename B>
966 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
969 {
970 SPARROW_ASSERT_TRUE(cbegin() <= pos);
971 SPARROW_ASSERT_TRUE(pos <= cend());
972 const auto index = static_cast<size_type>(std::distance(cbegin(), pos));
973 if (data() == nullptr && value)
974 {
975 m_size += count;
976 }
977 else
978 {
979 const size_type old_size = size();
980 const size_type new_size = old_size + count;
981
982 // TODO: The current implementation is not efficient. It can be improved.
983
984 resize(new_size);
985
986 for (size_type i = old_size + count - 1; i >= index + count; --i)
987 {
988 set(i, test(i - count));
989 }
990
991 for (size_type i = 0; i < count; ++i)
992 {
993 set(index + i, value);
994 }
995 }
996
997 return iterator(this, index);
998 }
999
1000 template <typename B>
1001 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1002 template <std::input_iterator InputIt>
1004 dynamic_bitset_base<B>::insert(const_iterator pos, InputIt first, InputIt last)
1005 {
1006 const auto index = static_cast<size_type>(std::distance(cbegin(), pos));
1007 const auto count = static_cast<size_type>(std::distance(first, last));
1008 if (data() == nullptr)
1009 {
1010 if (std::all_of(
1011 first,
1012 last,
1013 [](auto v)
1014 {
1015 return bool(v);
1016 }
1017 ))
1018 {
1019 m_size += count;
1020 }
1021 return iterator(this, index);
1022 }
1023 SPARROW_ASSERT_TRUE(cbegin() <= pos);
1024 SPARROW_ASSERT_TRUE(pos <= cend());
1025
1026 const size_type old_size = size();
1027 const size_type new_size = old_size + count;
1028
1029 resize(new_size);
1030
1031 // TODO: The current implementation is not efficient. It can be improved.
1032
1033 for (size_type i = old_size + count - 1; i >= index + count; --i)
1034 {
1035 set(i, test(i - count));
1036 }
1037
1038 for (size_type i = 0; i < count; ++i)
1039 {
1040 set(index + i, *first++);
1041 }
1042
1043 return iterator(this, index);
1044 }
1045
1046 template <typename B>
1047 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1050 {
1051 return insert(pos, value);
1052 }
1053
1054 template <typename B>
1055 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1057 {
1058 SPARROW_ASSERT_TRUE(cbegin() <= pos);
1059 SPARROW_ASSERT_TRUE(pos < cend());
1060
1061 return erase(pos, pos + 1);
1062 }
1063
1064 template <typename B>
1065 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1068 {
1069 SPARROW_ASSERT_TRUE(cbegin() <= first);
1070 SPARROW_ASSERT_TRUE(first <= last);
1071 SPARROW_ASSERT_TRUE(last <= cend());
1072
1073 const auto first_index = static_cast<size_type>(std::distance(cbegin(), first));
1074 const auto last_index = static_cast<size_type>(std::distance(cbegin(), last));
1075 const size_type count = last_index - first_index;
1076
1077 if (data() == nullptr)
1078 {
1079 m_size -= count;
1080 }
1081 else
1082 {
1083 if (last == cend())
1084 {
1085 resize(first_index);
1086 return end();
1087 }
1088
1089 // TODO: The current implementation is not efficient. It can be improved.
1090
1091 const size_type bit_to_move = size() - last_index;
1092 for (size_type i = 0; i < bit_to_move; ++i)
1093 {
1094 set(first_index + i, test(last_index + i));
1095 }
1096
1097 resize(size() - count);
1098 }
1099 return iterator(this, first_index);
1100 }
1101
1102 template <typename B>
1103 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1105 {
1106 resize(size() + 1, value);
1107 }
1108
1109 template <typename B>
1110 requires std::ranges::random_access_range<std::remove_pointer_t<B>>
1112 {
1113 if (empty())
1114 {
1115 return;
1116 }
1117 resize(size() - 1);
1118 }
1119}
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__)