From 694df712ff359f8569fdf0570136971f34035369 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Mon, 24 Feb 2025 22:20:51 -0500 Subject: [PATCH 01/85] fixed storage bit_array container --- include/bitlib/bit-containers/bit_array.hpp | 286 +++++++++++++++++ include/bitlib/bit-containers/bit_bitsof.hpp | 58 ++++ include/bitlib/bit-iterator/bit_details.hpp | 28 +- include/bitlib/bit-iterator/bit_reference.hpp | 6 + test/src/test-array.cpp | 63 ++++ test/src/vector_test.cpp | 288 +++++++++--------- 6 files changed, 574 insertions(+), 155 deletions(-) create mode 100644 include/bitlib/bit-containers/bit_array.hpp create mode 100644 include/bitlib/bit-containers/bit_bitsof.hpp create mode 100644 test/src/test-array.cpp diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp new file mode 100644 index 00000000..829e860b --- /dev/null +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -0,0 +1,286 @@ +// ================================= BIT_ARRAY =================================== // +// Project: The Experimental Bit Algorithms Library +// \file bit_array.hpp +// Description: Implementation of bit_array +// Creator: Vincent Reverdy +// Contributor: Peter McLean [2025] +// License: BSD 3-Clause License +// ========================================================================== // +#ifndef _BIT_ARRAY_HPP_INCLUDED +#define _BIT_ARRAY_HPP_INCLUDED +// ========================================================================== // + + + +// ================================ PREAMBLE ================================ // +// C++ standard library +#include +#include +#include +#include +#include +#include +#include +// Project sources +#include "bitlib/bit-iterator/bit.hpp" +#include "bitlib/bit-algorithms/bit_algorithm.hpp" +#include "bitlib/bit-containers/bit_bitsof.hpp" + +namespace bit { +// ========================================================================== // + +template +class bit_array { +public: +static constexpr std::size_t bits = N; + +private: +using T = bit_value; +using WordType = std::byte; +static constexpr std::size_t Words = (N * bitsof() + bitsof() - 1) / bitsof(); + +alignas(alignof(T)) std::array storage; + +public: +using value_type = bit_value; +using size_type = std::size_t; +using difference_type = std::ptrdiff_t; +using reference = bit_reference; +using const_reference = const reference; +using pointer = bit_pointer; +using const_pointer = const pointer; +using iterator = bit_iterator::iterator>; +using const_iterator = bit_iterator::const_iterator>; + +/* + * Constructors, copies and moves... + */ +constexpr bit_array() noexcept; +constexpr bit_array(value_type bit_val); +constexpr bit_array(const bit_array& other) = default; +constexpr bit_array(const bit_array&& other) noexcept; +constexpr bit_array(std::initializer_list init); +constexpr bit_array(std::initializer_list init); +constexpr bit_array(std::initializer_list init); +constexpr bit_array(std::string_view s); + +~bit_array() = default; + +private: + +public: + + /* + * Assignment + */ + constexpr bit_array& operator=(const bit_array& other) = default; + constexpr bit_array& operator=(bit_array&& other) noexcept; + + /* + * Element Access + */ + constexpr reference operator[](size_type pos); + constexpr const_reference operator[](size_type pos) const; + constexpr reference at(size_type pos); + constexpr const_reference at(size_type pos) const; + constexpr reference front(); + constexpr const_reference front() const; + constexpr reference back(); + constexpr const_reference back() const; + constexpr WordType* data() noexcept; + constexpr const WordType* data() const noexcept; + + /* + * Iterators + */ + constexpr iterator begin() noexcept; + constexpr iterator end() noexcept; + constexpr const_iterator begin() const noexcept; + constexpr const_iterator end() const noexcept; + constexpr const_iterator cbegin() const noexcept; + constexpr const_iterator cend() const noexcept; + + /* + * Capacity + */ + constexpr bool empty() const noexcept; + constexpr size_type size() const noexcept; + constexpr size_type max_size() const noexcept; + + /* + * Operations + */ + //constexpr fill() noexcept; + //constexpr swap() noexcept; + //constexpr std::synth-three-way-result operator<=>() const noexcept; +}; + +template +constexpr bit_array::bit_array() noexcept : storage{} {} + +template +constexpr bit_array::bit_array(bit_array::value_type bit_val) { + if (bit_val) { + std::fill(storage.begin(), storage.end(), bit_array::WordType(-1)); + } else { + std::fill(storage.begin(), storage.end(), bit_array::WordType(0)); + } +} + +template +constexpr bit_array::bit_array(std::initializer_list init) { + static_assert(init.size() == bitsof(*this)); + std::copy(init.begin(), init.end(), this->begin()); +} + +template +constexpr bit_array::bit_array(std::initializer_list init) { + static_assert(init.size() == bitsof(*this)); + std::copy(init.begin(), init.end(), this->begin()); +} + +template +constexpr bit_array::bit_array(std::initializer_list init) : storage(init) { + static_assert(init.size() == storage.size()); +} + +template +constexpr bit_array::bit_array(std::string_view s) { + if(bitsof(*this) != (std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))) [[unlikely]] { + throw std::invalid_argument("String contains an invalid number of bits for bit_array."); + }; + size_type i = 0; + for (char c : s) { + if (c == '0') { + begin()[i++] = bit0; + } else if (c == '1') { + begin()[i++] = bit1; + } + } +} + +template +constexpr bit_array::bit_array(const bit_array&& other) noexcept + : storage(other.storage) {} + + +template +constexpr bit_array& bit_array::operator=(bit_array&& other) noexcept { + std::copy(other.storage.begin(), other.storage.end(), storage.begin()); + return *this; +} + +// -------------------------------------------------------------------------- // + /* + * Element Access + */ +template +constexpr typename bit_array::reference bit_array::operator[](size_type pos) { + return begin()[pos]; +} + +template +constexpr typename bit_array::const_reference bit_array::operator[](size_type pos) const { + return begin()[pos]; +} + +template +constexpr typename bit_array::reference bit_array::at(size_type pos) { + if (pos < size()) { + return begin()[pos]; + } else { + throw std::out_of_range("Position is out of range"); + } +} + +template +constexpr typename bit_array::const_reference bit_array::at(size_type pos) const { + if (pos < size()) { + return begin()[pos]; + } else { + throw std::out_of_range("Position is out of range"); + } +} + +template +constexpr typename bit_array::reference bit_array::front() { + return begin()[0]; +} + +template +constexpr typename bit_array::const_reference bit_array::front() const { + return begin()[0]; +} + +template +constexpr typename bit_array::reference bit_array::back() { + return begin()[size()-1]; +} + +template +constexpr typename bit_array::const_reference bit_array::back() const { + return begin()[size()-1]; +} + +template +constexpr typename bit_array::WordType* bit_array::data() noexcept { + return size() ? storage.data() : nullptr; +} + +template +constexpr const typename bit_array::WordType* bit_array::data() const noexcept { + return size() ? storage.data() : nullptr; +} + +// -------------------------------------------------------------------------- // + + +template +constexpr typename bit_array::size_type bit_array::size() const noexcept {return N;} + +template +constexpr typename bit_array::size_type bit_array::max_size() const noexcept {return size();} + +template +constexpr bool bit_array::empty() const noexcept {return 0 == size();} + +// Iterators +// -------------------------------------------------------------------------- // + +template +constexpr typename bit_array::iterator bit_array::begin() noexcept { + return iterator(storage.begin()); +} + +template +constexpr typename bit_array::iterator bit_array::end() noexcept { + return begin() + size(); +} + +template +constexpr typename bit_array::const_iterator bit_array::begin() const noexcept { + return const_iterator(storage.begin()); +} + +template +constexpr typename bit_array::const_iterator bit_array::end() const noexcept { + return const_iterator(storage.begin()) + size(); +} + +template +constexpr typename bit_array::const_iterator bit_array::cbegin() const noexcept { + return const_iterator(storage.begin()); +} + +template +constexpr typename bit_array::const_iterator bit_array::cend() const noexcept { + return const_iterator(storage.begin()) + size(); +} + +// -------------------------------------------------------------------------- // + + +// ========================================================================== // +} // namespace bit +#endif // _BIT_ARRAY_HPP_INCLUDED +// ========================================================================== // diff --git a/include/bitlib/bit-containers/bit_bitsof.hpp b/include/bitlib/bit-containers/bit_bitsof.hpp new file mode 100644 index 00000000..4a6cf78a --- /dev/null +++ b/include/bitlib/bit-containers/bit_bitsof.hpp @@ -0,0 +1,58 @@ +#ifndef _BIT_BITSOF_HPP_INCLUDED +#define _BIT_BITSOF_HPP_INCLUDED + +namespace bit { + +// Forward declare +template +class bit_array; + +// Helper trait to detect bit_array types +template +struct is_bit_array : std::false_type {}; + +template +struct is_bit_array> : std::true_type {}; + +template +struct is_bit_vector : std::false_type {}; + +template +struct is_bit_vector> : std::true_type {}; + +// General case: for types that are not bit_array, bit_vector, or bit_value. +template ::value && + !std::is_same::value && + !is_bit_vector::value, int>::type = 0> +constexpr std::size_t bitsof() noexcept { + return 8u * sizeof(T); +} + +// Overload for bit_value. +template ::value, int>::type = 0> +constexpr std::size_t bitsof() noexcept { + return 1; +} + +// Overload for bit_array types. +template ::value, int>::type = 0> +constexpr std::size_t bitsof() noexcept { + return T::bits; +} + +// Overload for bit_vector +template ::value, int>::type = 0> +std::size_t bitsof(const T& v) noexcept { + return v.size(); +} + +template ::value, int>::type = 0> +constexpr std::size_t bitsof(const T&) noexcept {return bitsof();} + +} + +#endif diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index 595cd810..cb61a814 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -24,6 +24,7 @@ #include #include #include +#include // Project sources // Third-party libraries // Miscellaneous @@ -38,21 +39,24 @@ template class bit_iterator; /* ***************************** BINARY DIGITS ****************************** */ // Binary digits structure definition -template -struct binary_digits -: std::conditional< - std::is_const::value || std::is_volatile::value, - binary_digits::type>, - std::integral_constant::digits> ->::type +// Implementation template: only instantiates static_asserts for non-byte types. +template ::value> +struct binary_digits_impl : std::integral_constant::digits> { - // Assertions - static_assert(std::is_integral::value, ""); - static_assert(std::is_unsigned::value, ""); - static_assert(!std::is_same::value, ""); - static_assert(!std::is_same::value, ""); + static_assert(std::is_integral::value, "Type must be integral"); + static_assert(std::is_unsigned::value, "Type must be unsigned"); + static_assert(!std::is_same::value, "Type must not be bool"); + static_assert(!std::is_same::value, "Type must not be char"); }; +// Specialization for std::byte. +template <> +struct binary_digits_impl : std::integral_constant::digits> {}; + +// Public interface that removes cv-qualifiers. +template +struct binary_digits : binary_digits_impl> {}; + // Binary digits value template constexpr std::size_t binary_digits_v = binary_digits::value; diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index 93441581..40573620 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -273,6 +273,12 @@ constexpr bit_reference& bit_reference::operator^=( // ----------------------- BIT REFERENCE: CONVERSION ------------------------ // // Explicitly converts the bit reference to a boolean value +template<> +constexpr bit_reference::operator bool( +) const noexcept +{ + return static_cast(*_ptr & _mask); +} template constexpr bit_reference::operator bool( ) const noexcept diff --git a/test/src/test-array.cpp b/test/src/test-array.cpp new file mode 100644 index 00000000..e940530c --- /dev/null +++ b/test/src/test-array.cpp @@ -0,0 +1,63 @@ + +#include "bitlib/bit-containers/bit_array.hpp" + +#include "fixtures.hpp" + +// Third-party libraries +#include "gtest/gtest.h" + +/* + * Constructor tests + */ +// Tests the default c'tor. +TEST(ArrayTest, DefaultConstructor) { + bit::bit_array<11> barr(bit::bit0); + EXPECT_EQ(2, sizeof(barr)); + EXPECT_EQ(11u, barr.size()); + EXPECT_EQ(bit::bit0, barr[0]); + EXPECT_EQ(bit::bit0, barr[1]); + EXPECT_EQ(bit::bit0, barr[2]); + EXPECT_EQ(bit::bit0, barr[3]); + EXPECT_EQ(bit::bit0, barr[4]); + EXPECT_EQ(bit::bit0, barr[5]); + EXPECT_EQ(bit::bit0, barr[6]); + EXPECT_EQ(bit::bit0, barr[7]); + EXPECT_EQ(bit::bit0, barr[8]); + EXPECT_EQ(bit::bit0, barr[9]); + EXPECT_EQ(bit::bit0, barr[10]); +} + +TEST(ArrayTest, BitsOf) { + bit::bit_array<11> barr(bit::bit0); + EXPECT_EQ(11u, bitsof(barr)); + EXPECT_EQ(11u, bitsof(bit::bit_array<11>())); +} + +TEST(ArrayTest, BasicIteration) { + // <-- LSB, apparently šŸ™„ + bit::bit_array<11> barr("0110_0101_110"); + int i=0; + for(auto bbit : barr) { + switch(10 - i++) { + case 0: EXPECT_EQ(bit::bit0, bbit); break; + case 1: EXPECT_EQ(bit::bit1, bbit); break; + case 2: EXPECT_EQ(bit::bit1, bbit); break; + case 3: EXPECT_EQ(bit::bit1, bbit); break; + case 4: EXPECT_EQ(bit::bit0, bbit); break; + case 5: EXPECT_EQ(bit::bit1, bbit); break; + case 6: EXPECT_EQ(bit::bit0, bbit); break; + case 7: EXPECT_EQ(bit::bit0, bbit); break; + case 8: EXPECT_EQ(bit::bit1, bbit); break; + case 9: EXPECT_EQ(bit::bit1, bbit); break; + case 10: EXPECT_EQ(bit::bit0, bbit); break; + } + } +} + +TEST(ArrayTest, ZeroSize) { + bit::bit_array<0> barr{}; + std::array foo{}; + EXPECT_EQ(sizeof(foo), sizeof(barr)); + EXPECT_EQ(0, bitsof(barr)); +} + diff --git a/test/src/vector_test.cpp b/test/src/vector_test.cpp index 666b031d..4530b5ff 100644 --- a/test/src/vector_test.cpp +++ b/test/src/vector_test.cpp @@ -1,6 +1,6 @@ // =============================== FIXTURES ================================= // // Project: The Experimental Bit Algorithms Library -// Description: Fixtures for testing +// Description: Fixtures for testing // Contributor(s): Bryce Kille // License: BSD 3-Clause License // ========================================================================== // @@ -12,6 +12,7 @@ #include // Project sources #include "bitlib/bit-containers/bit-containers.hpp" +#include "bitlib/bit-containers/bit_bitsof.hpp" #include "fixtures.hpp" // Third-party libraries #include "gtest/gtest.h" @@ -29,6 +30,7 @@ TYPED_TEST(VectorTest, DefaultConstructor) { // Tests the size c'tor. TYPED_TEST(VectorTest, SizeInitializerConstructor) { EXPECT_EQ(18, this->v2_.size()); + EXPECT_EQ(18, bitsof(this->v2_)); for (auto bv: this->v2_) { EXPECT_FALSE(bv); } @@ -44,7 +46,7 @@ TYPED_TEST(VectorTest, SizeInitializerConstructor) { // TODO misplaced test for range-based for loop for (auto bv: this->empty_vec) { EXPECT_FALSE(bv); - i++; + i++; } EXPECT_EQ(i, veclen); } @@ -58,7 +60,7 @@ TYPED_TEST(VectorTest, CountValueConstructor) { // TODO misplaced test for range-based for loop for (auto bv: this->empty_vec) { EXPECT_TRUE(bv); - i++; + i++; } EXPECT_EQ(i, veclen); this->empty_vec = vec_type(veclen, bit::bit0); @@ -66,7 +68,7 @@ TYPED_TEST(VectorTest, CountValueConstructor) { // TODO misplaced test for range-based for loop for (auto bv: this->empty_vec) { EXPECT_FALSE(bv); - i++; + i++; } EXPECT_EQ(i, veclen); } @@ -81,8 +83,8 @@ TYPED_TEST(VectorTest, WordIterPairConstructor) { std::vector word_vec = get_random_vec(veclen); vec_type test(word_vec.begin(), word_vec.end()); EXPECT_TRUE(std::equal( - test.begin(), - test.end(), + test.begin(), + test.end(), bit::bit_iterator(word_vec.begin()), bit::bit_iterator(word_vec.end()))); } @@ -95,8 +97,8 @@ TYPED_TEST(VectorTest, BoolIterPairConstructor) { auto& boolvec = this->random_boolvecs[vec_idx]; vec_type test = vec_type(boolvec.begin(), boolvec.end()); EXPECT_TRUE(std::equal( - test.begin(), - test.end(), + test.begin(), + test.end(), boolvec.begin(), boolvec.end(), comparator)); @@ -111,8 +113,8 @@ TYPED_TEST(VectorTest, CopyConstructor) { auto& boolvec = this->random_boolvecs[vec_idx]; vec_type test = vec_type(bitvec); EXPECT_TRUE(std::equal( - test.begin(), - test.end(), + test.begin(), + test.end(), boolvec.begin(), boolvec.end(), comparator)); @@ -126,8 +128,8 @@ TYPED_TEST(VectorTest, MoveConstructor) { auto& boolvec = this->random_boolvecs[vec_idx]; vec_type test = vec_type(std::move(bitvec)); EXPECT_TRUE(std::equal( - test.begin(), - test.end(), + test.begin(), + test.end(), boolvec.begin(), boolvec.end(), comparator)); @@ -149,10 +151,10 @@ TYPED_TEST(VectorTest, StringConstructor) { } this->empty_vec = bit::bit_vector(rand_bs); EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), comparator)); } } @@ -174,10 +176,10 @@ TYPED_TEST(VectorTest, CopyAssignment) { this->empty_vec = bitvec; EXPECT_NE(this->empty_vec.begin().base(), bitvec.begin().base()); EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - boolvec.begin(), - boolvec.end(), + this->empty_vec.begin(), + this->empty_vec.end(), + boolvec.begin(), + boolvec.end(), comparator)); if (bitvec.size() > 0) { bitvec[0] = ~bitvec[0]; @@ -201,10 +203,10 @@ TYPED_TEST(VectorTest, MoveAssignment) { this->empty_vec = std::move(bitvec); EXPECT_EQ(this->empty_vec.begin().base(), bitvec_base); EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - boolvec.begin(), - boolvec.end(), + this->empty_vec.begin(), + this->empty_vec.end(), + boolvec.begin(), + boolvec.end(), comparator)); } } @@ -217,10 +219,10 @@ TYPED_TEST(VectorTest, InitializerListConstructor) { std::vector boolvec {true, false, true, true, true, false, false, true, false, true, true, false}; vec_type test {true, false, true, true, true, false, false, true, false, true, true, false}; EXPECT_TRUE(std::equal( - test.begin(), - test.end(), - boolvec.begin(), - boolvec.end(), + test.begin(), + test.end(), + boolvec.begin(), + boolvec.end(), comparator)); } @@ -252,10 +254,10 @@ TYPED_TEST(VectorTest, BracketWrite) { boolvec[i] = !boolvec[i]; } EXPECT_TRUE(std::equal( - bitvec.begin(), - bitvec.end(), - boolvec.begin(), - boolvec.end(), + bitvec.begin(), + bitvec.end(), + boolvec.begin(), + boolvec.end(), comparator)); } } @@ -361,19 +363,19 @@ TYPED_TEST(VectorTest, InsertAtEnd1) { bool to_insert_bool = generate_random_number(0, 1) > 0 ? true : false; bit::bit_value to_insert_bit = to_insert_bool ? bit::bit1 : bit::bit0; auto bitret = this->empty_vec.insert( - this->empty_vec.end(), + this->empty_vec.end(), to_insert_bit); auto boolret = this->empty_vec_bool.insert( - this->empty_vec_bool.end(), + this->empty_vec_bool.end(), to_insert_bool); EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), comparator)); EXPECT_EQ( - std::distance(this->empty_vec.begin(), bitret), + std::distance(this->empty_vec.begin(), bitret), std::distance(this->empty_vec_bool.begin(), boolret)); } } @@ -383,19 +385,19 @@ TYPED_TEST(VectorTest, InsertAtBegin1) { bool to_insert_bool = generate_random_number(0, 1) > 0 ? true : false; bit::bit_value to_insert_bit = to_insert_bool ? bit::bit1 : bit::bit0; auto bitret = this->empty_vec.insert( - this->empty_vec.begin(), + this->empty_vec.begin(), to_insert_bit); auto boolret = this->empty_vec_bool.insert( - this->empty_vec_bool.begin(), + this->empty_vec_bool.begin(), to_insert_bool); EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), comparator)); EXPECT_EQ( - std::distance(this->empty_vec.begin(), bitret), + std::distance(this->empty_vec.begin(), bitret), std::distance(this->empty_vec_bool.begin(), boolret)); } } @@ -410,16 +412,16 @@ TYPED_TEST(VectorTest, InsertAtRand1) { this->empty_vec.begin() + insert_location, to_insert_bit); auto boolret = this->empty_vec_bool.insert( - this->empty_vec_bool.begin() + insert_location, + this->empty_vec_bool.begin() + insert_location, to_insert_bool); EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), comparator)); EXPECT_EQ( - std::distance(this->empty_vec.begin(), bitret), + std::distance(this->empty_vec.begin(), bitret), std::distance(this->empty_vec_bool.begin(), boolret)); } } @@ -429,21 +431,21 @@ TYPED_TEST(VectorTest, InsertAtBegin2) { for (auto _ = 16; _--;) { auto to_insert = generate_random_number(0, 4*this->digits); auto bitret = this->empty_vec.insert( - this->empty_vec.begin(), - to_insert, + this->empty_vec.begin(), + to_insert, bit::bit1); auto boolret = this->empty_vec_bool.insert( this->empty_vec_bool.begin(), - to_insert, + to_insert, true); EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), comparator)); EXPECT_EQ( - std::distance(this->empty_vec.begin(), bitret), + std::distance(this->empty_vec.begin(), bitret), std::distance(this->empty_vec_bool.begin(), boolret)); } } @@ -453,21 +455,21 @@ TYPED_TEST(VectorTest, InsertAtEnd2) { for (auto _ = 16; _--;) { auto to_insert = generate_random_number(0, 444*this->digits); auto bitret = this->empty_vec.insert( - this->empty_vec.end(), - to_insert, + this->empty_vec.end(), + to_insert, bit::bit1); auto boolret = this->empty_vec_bool.insert( - this->empty_vec_bool.end(), - to_insert, + this->empty_vec_bool.end(), + to_insert, true); EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), comparator)); EXPECT_EQ( - std::distance(this->empty_vec.begin(), bitret), + std::distance(this->empty_vec.begin(), bitret), std::distance(this->empty_vec_bool.begin(), boolret)); } } @@ -478,21 +480,21 @@ TYPED_TEST(VectorTest, InsertAtRand2) { auto to_insert = generate_random_number(0, 4*this->digits); auto insert_location = generate_random_number(0, this->empty_vec.size()); auto bitret = this->empty_vec.insert( - this->empty_vec.begin() + insert_location, - to_insert, + this->empty_vec.begin() + insert_location, + to_insert, bit::bit1); auto boolret = this->empty_vec_bool.insert( - this->empty_vec_bool.begin() + insert_location, - to_insert, + this->empty_vec_bool.begin() + insert_location, + to_insert, true); EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), comparator)); EXPECT_EQ( - std::distance(this->empty_vec.begin(), bitret), + std::distance(this->empty_vec.begin(), bitret), std::distance(this->empty_vec_bool.begin(), boolret)); } } @@ -504,21 +506,21 @@ TYPED_TEST(VectorTest, InsertAtBegin3) { auto insert_bitvec = this->random_bitvecs[to_insert]; auto insert_boolvec = this->random_boolvecs[to_insert]; auto bitret = this->empty_vec.insert( - this->empty_vec.begin(), - insert_bitvec.begin(), + this->empty_vec.begin(), + insert_bitvec.begin(), insert_bitvec.end()); auto boolret = this->empty_vec_bool.insert( - this->empty_vec_bool.begin(), - insert_boolvec.begin(), + this->empty_vec_bool.begin(), + insert_boolvec.begin(), insert_boolvec.end()); EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), comparator)); EXPECT_EQ( - std::distance(this->empty_vec.begin(), bitret), + std::distance(this->empty_vec.begin(), bitret), std::distance(this->empty_vec_bool.begin(), boolret)); } } @@ -530,21 +532,21 @@ TYPED_TEST(VectorTest, InsertAtEnd3) { auto insert_bitvec = this->random_bitvecs[to_insert]; auto insert_boolvec = this->random_boolvecs[to_insert]; auto bitret = this->empty_vec.insert( - this->empty_vec.end(), - insert_bitvec.begin(), + this->empty_vec.end(), + insert_bitvec.begin(), insert_bitvec.end()); auto boolret = this->empty_vec_bool.insert( - this->empty_vec_bool.end(), - insert_boolvec.begin(), + this->empty_vec_bool.end(), + insert_boolvec.begin(), insert_boolvec.end()); EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), comparator)); EXPECT_EQ( - std::distance(this->empty_vec.begin(), bitret), + std::distance(this->empty_vec.begin(), bitret), std::distance(this->empty_vec_bool.begin(), boolret)); } } @@ -557,21 +559,21 @@ TYPED_TEST(VectorTest, InsertAtRand3) { auto insert_boolvec = this->random_boolvecs[to_insert]; auto insert_location = generate_random_number(0, this->empty_vec.size()); auto bitret = this->empty_vec.insert( - this->empty_vec.begin() + insert_location, - insert_bitvec.begin(), + this->empty_vec.begin() + insert_location, + insert_bitvec.begin(), insert_bitvec.end()); auto boolret = this->empty_vec_bool.insert( - this->empty_vec_bool.begin() + insert_location, - insert_boolvec.begin(), + this->empty_vec_bool.begin() + insert_location, + insert_boolvec.begin(), insert_boolvec.end()); EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), comparator)); EXPECT_EQ( - std::distance(this->empty_vec.begin(), bitret), + std::distance(this->empty_vec.begin(), bitret), std::distance(this->empty_vec_bool.begin(), boolret)); } } @@ -587,13 +589,13 @@ TYPED_TEST(VectorTest, EraseAtBegin1) { auto bitret = bitvec.erase(bitvec.begin()); auto boolret = boolvec.erase(boolvec.begin()); EXPECT_TRUE(std::equal( - bitvec.begin(), - bitvec.end(), - boolvec.begin(), - boolvec.end(), + bitvec.begin(), + bitvec.end(), + boolvec.begin(), + boolvec.end(), comparator)); EXPECT_EQ( - std::distance(bitvec.begin(), bitret), + std::distance(bitvec.begin(), bitret), std::distance(boolvec.begin(), boolret)); } } @@ -608,13 +610,13 @@ TYPED_TEST(VectorTest, EraseAtEnd1) { auto bitret = bitvec.erase(bitvec.end() - 1); auto boolret = boolvec.erase(boolvec.end() - 1); EXPECT_TRUE(std::equal( - bitvec.begin(), - bitvec.end(), - boolvec.begin(), - boolvec.end(), + bitvec.begin(), + bitvec.end(), + boolvec.begin(), + boolvec.end(), comparator)); EXPECT_EQ( - std::distance(bitvec.begin(), bitret), + std::distance(bitvec.begin(), bitret), std::distance(boolvec.begin(), boolret)); } } @@ -630,13 +632,13 @@ TYPED_TEST(VectorTest, EraseAtRand1) { auto bitret = bitvec.erase(bitvec.begin() + erase_location); auto boolret = boolvec.erase(boolvec.begin() + erase_location); EXPECT_TRUE(std::equal( - bitvec.begin(), - bitvec.end(), - boolvec.begin(), - boolvec.end(), + bitvec.begin(), + bitvec.end(), + boolvec.begin(), + boolvec.end(), comparator)); EXPECT_EQ( - std::distance(bitvec.begin(), bitret), + std::distance(bitvec.begin(), bitret), std::distance(boolvec.begin(), boolret)); } } @@ -652,13 +654,13 @@ TYPED_TEST(VectorTest, EraseAtBegin2) { auto bitret = bitvec.erase(bitvec.begin(), bitvec.begin() + erase_size); auto boolret = boolvec.erase(boolvec.begin(), boolvec.begin() + erase_size); EXPECT_TRUE(std::equal( - bitvec.begin(), - bitvec.end(), - boolvec.begin(), - boolvec.end(), + bitvec.begin(), + bitvec.end(), + boolvec.begin(), + boolvec.end(), comparator)); EXPECT_EQ( - std::distance(bitvec.begin(), bitret), + std::distance(bitvec.begin(), bitret), std::distance(boolvec.begin(), boolret)); } } @@ -674,13 +676,13 @@ TYPED_TEST(VectorTest, EraseAtEnd2) { auto bitret = bitvec.erase(bitvec.begin() + erase_start, bitvec.end()); auto boolret = boolvec.erase(boolvec.begin() + erase_start, boolvec.end()); EXPECT_TRUE(std::equal( - bitvec.begin(), - bitvec.end(), - boolvec.begin(), - boolvec.end(), + bitvec.begin(), + bitvec.end(), + boolvec.begin(), + boolvec.end(), comparator)); EXPECT_EQ( - std::distance(bitvec.begin(), bitret), + std::distance(bitvec.begin(), bitret), std::distance(boolvec.begin(), boolret)); } } @@ -697,13 +699,13 @@ TYPED_TEST(VectorTest, EraseAtRand2) { auto bitret = bitvec.erase(bitvec.begin() + erase_start, bitvec.begin() + erase_end); auto boolret = boolvec.erase(boolvec.begin() + erase_start, boolvec.begin() + erase_end); EXPECT_TRUE(std::equal( - bitvec.begin(), - bitvec.end(), - boolvec.begin(), - boolvec.end(), + bitvec.begin(), + bitvec.end(), + boolvec.begin(), + boolvec.end(), comparator)); EXPECT_EQ( - std::distance(bitvec.begin(), bitret), + std::distance(bitvec.begin(), bitret), std::distance(boolvec.begin(), boolret)); } } @@ -720,10 +722,10 @@ TYPED_TEST(VectorTest, PushBack) { bitvec.push_back(to_insert_bit); boolvec.push_back(to_insert_bool); EXPECT_TRUE(std::equal( - bitvec.begin(), - bitvec.end(), - boolvec.begin(), - boolvec.end(), + bitvec.begin(), + bitvec.end(), + boolvec.begin(), + boolvec.end(), comparator)); } } @@ -739,10 +741,10 @@ TYPED_TEST(VectorTest, PopBack) { bitvec.pop_back(); boolvec.pop_back(); EXPECT_TRUE(std::equal( - bitvec.begin(), - bitvec.end(), - boolvec.begin(), - boolvec.end(), + bitvec.begin(), + bitvec.end(), + boolvec.begin(), + boolvec.end(), comparator)); } } From 0791450d87854d0a63e4d93360e1d5045a2e858b Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Thu, 27 Feb 2025 15:56:53 -0500 Subject: [PATCH 02/85] Add LLM generated test cases --- test/src/test-array.cpp | 148 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/test/src/test-array.cpp b/test/src/test-array.cpp index e940530c..d7d272a0 100644 --- a/test/src/test-array.cpp +++ b/test/src/test-array.cpp @@ -1,3 +1,5 @@ +#include +#include #include "bitlib/bit-containers/bit_array.hpp" @@ -61,3 +63,149 @@ TEST(ArrayTest, ZeroSize) { EXPECT_EQ(0, bitsof(barr)); } +// Test that the default constructor initializes all bits to false. +TEST(BitArrayTest, DefaultInitialization) { + bit_array<8> ba; + for (size_t i = 0; i < ba.size(); ++i) { + EXPECT_FALSE(ba[i]) << "Bit " << i << " should be false by default"; + } +} + +// Test the fill() method. +TEST(BitArrayTest, FillMethod) { + bit_array<10> ba; + ba.fill(true); + for (size_t i = 0; i < ba.size(); ++i) { + EXPECT_TRUE(ba[i]) << "Bit " << i << " should be true after fill(true)"; + } + ba.fill(false); + for (size_t i = 0; i < ba.size(); ++i) { + EXPECT_FALSE(ba[i]) << "Bit " << i << " should be false after fill(false)"; + } +} + +// Test element access via operator[] and at(), including out-of-range checking. +TEST(BitArrayTest, ElementAccess) { + bit_array<5> ba; + ba.fill(false); + ba[2] = true; + EXPECT_TRUE(ba.at(2)); + EXPECT_THROW(ba.at(5), std::out_of_range); +} + +// Test front() and back() member functions. +TEST(BitArrayTest, FrontBackAccess) { + bit_array<4> ba; + ba.fill(false); + ba.front() = true; + ba.back() = true; + EXPECT_TRUE(ba.front()); + EXPECT_TRUE(ba.back()); +} + +// Test iterator functionality (both non-const and range-based). +TEST(BitArrayTest, IteratorFunctionality) { + bit_array<4> ba; + ba.fill(false); + int index = 0; + for (auto it = ba.begin(); it != ba.end(); ++it) { + // Change the second element using the iterator. + if (index == 1) { + *it = true; + } + ++index; + } + EXPECT_TRUE(ba[1]); +} + +// Test const_iterator functionality. +TEST(BitArrayTest, ConstIteratorFunctionality) { + bit_array<4> ba; + ba.fill(true); + const bit_array<4>& const_ba = ba; + for (auto it = const_ba.begin(); it != const_ba.end(); ++it) { + EXPECT_TRUE(*it); + } +} + +// Test the swap() member function. +TEST(BitArrayTest, SwapFunctionality) { + bit_array<4> ba1, ba2; + ba1.fill(false); + ba2.fill(true); + ba1.swap(ba2); + for (size_t i = 0; i < ba1.size(); ++i) { + EXPECT_TRUE(ba1[i]) << "After swap, ba1[" << i << "] should be true"; + EXPECT_FALSE(ba2[i]) << "After swap, ba2[" << i << "] should be false"; + } +} + +// Test comparison operators (== and !=). +TEST(BitArrayTest, ComparisonOperators) { + bit_array<5> ba1 = { true, true, false, false, true }; + bit_array<5> ba2 = { true, true, false, false, true }; + EXPECT_EQ(ba1, ba2); + ba2[2] = true; // Change one element + EXPECT_NE(ba1, ba2); +} + +// Test the data() method to access the underlying storage. +TEST(BitArrayTest, DataAccess) { + bit_array<8> ba; + ba.fill(false); + // Assume data() returns a pointer to a boolean array. + bool* data_ptr = ba.data(); + EXPECT_FALSE(data_ptr[0]); + ba[0] = true; + EXPECT_TRUE(data_ptr[0]); +} + +// Test size() and empty() functions. +TEST(BitArrayTest, SizeAndEmpty) { + bit_array<0> ba_empty; + EXPECT_EQ(ba_empty.size(), 0); + EXPECT_TRUE(ba_empty.empty()); + + bit_array<5> ba; + EXPECT_EQ(ba.size(), 5); + EXPECT_FALSE(ba.empty()); +} + +// Test initializer list construction. +TEST(BitArrayTest, InitializerListConstruction) { + bit_array<3> ba = { true, false, true }; + EXPECT_TRUE(ba[0]); + EXPECT_FALSE(ba[1]); + EXPECT_TRUE(ba[2]); +} + +// Test copy constructor and copy assignment operator. +TEST(BitArrayTest, CopyAndAssignment) { + bit_array<5> ba1 = { true, false, true, false, true }; + bit_array<5> ba_copy(ba1); + EXPECT_EQ(ba1, ba_copy); + + bit_array<5> ba_assigned; + ba_assigned = ba1; + EXPECT_EQ(ba1, ba_assigned); +} + +// Test move semantics (move constructor and move assignment), if implemented. +TEST(BitArrayTest, MoveSemantics) { + bit_array<5> ba1 = { true, false, true, false, true }; + bit_array<5> ba_moved(std::move(ba1)); + // We test the moved-to container's values. The moved-from object is valid but unspecified. + EXPECT_TRUE(ba_moved[0]); + EXPECT_FALSE(ba_moved[1]); + EXPECT_TRUE(ba_moved[2]); + EXPECT_FALSE(ba_moved[3]); + EXPECT_TRUE(ba_moved[4]); + + bit_array<5> ba2 = { false, false, false, false, false }; + ba2 = std::move(ba_moved); + EXPECT_TRUE(ba2[0]); + EXPECT_FALSE(ba2[1]); + EXPECT_TRUE(ba2[2]); + EXPECT_FALSE(ba2[3]); + EXPECT_TRUE(ba2[4]); +} From 022db7cfc03982eceb463bf5754284eb17d95644 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sat, 1 Mar 2025 10:04:01 -0500 Subject: [PATCH 03/85] Add span container. Remove use of std::byte --- .../bit-algorithms/bit_algorithm_details.hpp | 5 +- include/bitlib/bit-containers/bit_array.hpp | 49 ++-- include/bitlib/bit-containers/bit_bitsof.hpp | 10 + include/bitlib/bit-containers/bit_span.hpp | 219 ++++++++++++++++++ include/bitlib/bit-iterator/bit_iterator.hpp | 10 + include/bitlib/bit-iterator/bit_reference.hpp | 10 +- test/CMakeLists.txt | 8 +- test/src/test-array.cpp | 98 ++++---- test/src/test-span.cpp | 203 ++++++++++++++++ utils/test_utils.hpp | 46 ++-- 10 files changed, 567 insertions(+), 91 deletions(-) create mode 100644 include/bitlib/bit-containers/bit_span.hpp create mode 100644 test/src/test-span.cpp diff --git a/include/bitlib/bit-algorithms/bit_algorithm_details.hpp b/include/bitlib/bit-algorithms/bit_algorithm_details.hpp index 984c499f..ab30bc3f 100644 --- a/include/bitlib/bit-algorithms/bit_algorithm_details.hpp +++ b/include/bitlib/bit-algorithms/bit_algorithm_details.hpp @@ -108,8 +108,9 @@ T get_word(bit_iterator first, size_t len=binary_digits::value) using native_word_type = typename bit_iterator::word_type; constexpr T digits = binary_digits::value; assert(digits >= len); - T offset = digits - first.position(); - T ret_word = *first.base() >> first.position(); + using non_const_T = std::remove_cv_t; + non_const_T offset = digits - first.position(); + non_const_T ret_word = *first.base() >> first.position(); // We've already assigned enough bits if (len <= offset) { diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index 829e860b..04c485b2 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -36,7 +36,7 @@ static constexpr std::size_t bits = N; private: using T = bit_value; -using WordType = std::byte; +using WordType = std::uint8_t; static constexpr std::size_t Words = (N * bitsof() + bitsof() - 1) / bitsof(); alignas(alignof(T)) std::array storage; @@ -59,10 +59,10 @@ constexpr bit_array() noexcept; constexpr bit_array(value_type bit_val); constexpr bit_array(const bit_array& other) = default; constexpr bit_array(const bit_array&& other) noexcept; -constexpr bit_array(std::initializer_list init); -constexpr bit_array(std::initializer_list init); -constexpr bit_array(std::initializer_list init); -constexpr bit_array(std::string_view s); +constexpr bit_array(const std::initializer_list init); +constexpr bit_array(const std::initializer_list init); +constexpr bit_array(const std::initializer_list init); +constexpr bit_array(const std::string_view s); ~bit_array() = default; @@ -76,6 +76,8 @@ constexpr bit_array(std::string_view s); constexpr bit_array& operator=(const bit_array& other) = default; constexpr bit_array& operator=(bit_array&& other) noexcept; + constexpr bool operator==(const bit_array& other) const noexcept; + /* * Element Access */ @@ -110,8 +112,8 @@ constexpr bit_array(std::string_view s); /* * Operations */ - //constexpr fill() noexcept; - //constexpr swap() noexcept; + constexpr void fill(value_type bit_val) noexcept; + constexpr void swap(bit_array& other) noexcept; //constexpr std::synth-three-way-result operator<=>() const noexcept; }; @@ -119,7 +121,12 @@ template constexpr bit_array::bit_array() noexcept : storage{} {} template -constexpr bit_array::bit_array(bit_array::value_type bit_val) { +constexpr bit_array::bit_array(bit_array::value_type bit_val) : storage{} { + fill(bit_val); +} + +template +constexpr void bit_array::fill(bit_array::value_type bit_val) noexcept { if (bit_val) { std::fill(storage.begin(), storage.end(), bit_array::WordType(-1)); } else { @@ -128,24 +135,28 @@ constexpr bit_array::bit_array(bit_array::value_type bit_val) { } template -constexpr bit_array::bit_array(std::initializer_list init) { - static_assert(init.size() == bitsof(*this)); +constexpr bit_array::bit_array(const std::initializer_list init) { + if(init.size() != bitsof(*this)) [[unlikely]] { + throw std::invalid_argument("initialize_list contains an invalid number of bits for bit_array."); + } std::copy(init.begin(), init.end(), this->begin()); } template -constexpr bit_array::bit_array(std::initializer_list init) { - static_assert(init.size() == bitsof(*this)); +constexpr bit_array::bit_array(const std::initializer_list init) { + if(init.size() != bitsof(*this)) [[unlikely]] { + throw std::invalid_argument("initialize_list contains an invalid number of bits for bit_array."); + } std::copy(init.begin(), init.end(), this->begin()); } template -constexpr bit_array::bit_array(std::initializer_list init) : storage(init) { +constexpr bit_array::bit_array(const std::initializer_list init) : storage(init) { static_assert(init.size() == storage.size()); } template -constexpr bit_array::bit_array(std::string_view s) { +constexpr bit_array::bit_array(const std::string_view s) { if(bitsof(*this) != (std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))) [[unlikely]] { throw std::invalid_argument("String contains an invalid number of bits for bit_array."); }; @@ -170,6 +181,16 @@ constexpr bit_array& bit_array::operator=(bit_array&& other) noexcept { return *this; } +template +constexpr bool bit_array::operator==(const bit_array& other) const noexcept { + return equal(begin(), end(), other.begin()); +} + +template +constexpr void bit_array::swap(bit_array& other) noexcept { + std::swap(this->storage, other.storage); +} + // -------------------------------------------------------------------------- // /* * Element Access diff --git a/include/bitlib/bit-containers/bit_bitsof.hpp b/include/bitlib/bit-containers/bit_bitsof.hpp index 4a6cf78a..2f7fc88f 100644 --- a/include/bitlib/bit-containers/bit_bitsof.hpp +++ b/include/bitlib/bit-containers/bit_bitsof.hpp @@ -1,6 +1,16 @@ #ifndef _BIT_BITSOF_HPP_INCLUDED #define _BIT_BITSOF_HPP_INCLUDED +/* +A bitsof() template function that resembles sizeof() as much as possible. +Possible calling styles: + - `bitsof(_var_)` + - `bitsof<_type>()` + - `bitsof(_type())` <- Default constructor required + - `bitsof(_type{})` <- Default constructor required +When calling within template code with a type, the callee must use the `bitsof()` or `bitsof(type())` form +*/ + namespace bit { // Forward declare diff --git a/include/bitlib/bit-containers/bit_span.hpp b/include/bitlib/bit-containers/bit_span.hpp new file mode 100644 index 00000000..c807a01a --- /dev/null +++ b/include/bitlib/bit-containers/bit_span.hpp @@ -0,0 +1,219 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +// Project sources +#include "bitlib/bit-iterator/bit.hpp" +#include "bitlib/bit-algorithms/bit_algorithm.hpp" +#include "bitlib/bit-containers/bit_bitsof.hpp" + +namespace bit { + +// Helper storage: for a fixed extent no runtime size is stored. +template +struct bit_span_storage { + constexpr std::size_t size() const noexcept { return Extent; } + bit_span_storage() = default; +}; + +// Specialization for dynamic_extent: store the size at runtime. +template +struct bit_span_storage { + std::size_t size_; + constexpr std::size_t size() const noexcept { return size_; } + bit_span_storage() : size_(0) {} + bit_span_storage(std::size_t size) : size_(size) {} +}; + +template +class bit_span : private bit_span_storage { +public: + using word_type = WordType; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = bit_reference; + using const_reference = const bit_reference; + using pointer = bit_pointer; + using const_pointer = const pointer; + using iterator = bit_iterator::iterator>; + using const_iterator = bit_iterator::const_iterator>; + +private: + // The start of the span, represented as a bit_pointer. + pointer data_; + +public: + // --- Constructors --- + + // Default constructor: + // For dynamic extent, we start with a null pointer and size zero. + // For fixed extent, the pointer is null but the size is always Extent. + constexpr bit_span() noexcept; + + // Construct from a bit_pointer and a bit count. + constexpr bit_span(pointer data, size_type bit_count) noexcept requires(Extent == std::dynamic_extent) ; + constexpr bit_span(pointer data) noexcept; + + // Construct from a WordType pointer and a bit count. + constexpr bit_span(WordType* word_ptr, size_type bit_count) noexcept requires(Extent == std::dynamic_extent); + constexpr bit_span(WordType* word_ptr) noexcept; + + // Construct from a iterator and a bit count. + constexpr bit_span(iterator iter, size_type bit_count) noexcept requires(Extent == std::dynamic_extent); + constexpr bit_span(iterator iter) noexcept; + + // --- Observers --- + + // Returns the number of bits in the span. + constexpr size_type size() const noexcept; + // Checks if the span is empty. + constexpr bool empty() const noexcept; + + // --- Element Access --- + + // Non-const element access. + constexpr reference operator[](size_type pos); + // Const element access returns a const_reference. + constexpr const_reference operator[](size_type pos) const; + // Bounds-checked access + constexpr reference at(size_type idx); + // Bounds-checked access + constexpr const_reference at(size_type idx) const; + + // Non-const front/back access. + constexpr reference front(); + constexpr reference back(); + + // Const front/back access returns a const_reference. + constexpr const_reference front() const; + constexpr const_reference back() const; + + // --- Iterators --- + + // Returns an iterator to the first bit. + constexpr iterator begin() const noexcept; + + // Returns an iterator just past the last bit. + constexpr iterator end() const noexcept; + + // --- Subspan Functionality --- + + // Creates a subspan starting at offset with count bits. + // If count is not specified (or set to -1), it goes to the end. + constexpr bit_span + subspan(size_type offset, size_type count = std::dynamic_extent) const noexcept; +}; + +// --- Constructors --- + +// Default constructor: +// For dynamic extent, we start with a null pointer and size zero. +// For fixed extent, the pointer is null but the size is always Extent. +template +constexpr bit_span::bit_span() noexcept : data_(nullptr), bit_span_storage() { } + +// Construct from a bit_pointer and a bit count. +template +constexpr bit_span::bit_span(pointer data, size_type bit_count) noexcept requires(Extent == std::dynamic_extent) : data_(data), bit_span_storage(bit_count) { } + +// Construct from a WordType pointer and a word count. +// This converts the word count to a bit count using bitsof. +template +constexpr bit_span::bit_span(WordType* word_ptr, size_type bit_count) noexcept requires(Extent == std::dynamic_extent) : data_(pointer(word_ptr, 0)), bit_span_storage(bit_count) { } + +template +constexpr bit_span::bit_span(iterator iter, size_type bit_count) noexcept requires(Extent == std::dynamic_extent) : data_(&(*iter)), bit_span_storage(bit_count) { } + + +// Construct from a bit_pointer and a bit count. +template +constexpr bit_span::bit_span(pointer data) noexcept : data_(data), bit_span_storage() { } + +template +constexpr bit_span::bit_span(WordType* word_ptr) noexcept : data_(pointer(word_ptr, 0)), bit_span_storage() { } + +template +constexpr bit_span::bit_span(iterator iter) noexcept : data_(&(*iter)), bit_span_storage() { } + + +// --- Observers --- + +// Returns the number of bits in the span. +template +constexpr bit_span::size_type bit_span::size() const noexcept { + if constexpr (Extent == std::dynamic_extent) { + return this->size_; + } else { + return Extent; + } +} + +// Checks if the span is empty. +template +constexpr bool bit_span::empty() const noexcept { return size() == 0; } + +// --- Element Access --- + +// Non-const element access. +template +constexpr bit_span::reference bit_span::operator[](size_type pos) { return begin()[pos]; } + +// Const element access returns a const_reference. +template +constexpr bit_span::const_reference bit_span::operator[](size_type pos) const { return begin()[pos]; } + +template +constexpr bit_span::reference bit_span::at(size_type pos) { + if (pos >= size()) { + throw std::out_of_range("span::at - index out of range"); + } + return data_[pos]; +} + +// Const element access returns a const_reference. +template +constexpr bit_span::const_reference bit_span::at(size_type pos) const { + if (pos >= size()) { + throw std::out_of_range("span::at - index out of range"); + } + return data_[pos]; +} + +// Non-const front/back access. +template +constexpr bit_span::reference bit_span::front() { return begin()[0]; } +template +constexpr bit_span::reference bit_span::back() { return begin()[size() - 1]; } + +// Const front/back access returns a const_reference. +template +constexpr bit_span::const_reference bit_span::front() const { return begin()[0]; } +template +constexpr bit_span::const_reference bit_span::back() const { return begin()[size() - 1]; } + +// --- Iterators --- + +// Returns an iterator to the first bit. +template +constexpr bit_span::iterator bit_span::begin() const noexcept { return iterator(data_); } + +// Returns an iterator just past the last bit. +template +constexpr bit_span::iterator bit_span::end() const noexcept { return begin() + size(); } + +// --- Subspan Functionality --- + +// Creates a subspan starting at offset with count bits. +// If count is not specified (or set to -1), it goes to the end. +template +constexpr bit_span bit_span::subspan(size_type offset, size_type count) const noexcept { + size_type new_count = (count == std::dynamic_extent) ? size() - offset : count; + return bit_span(begin() + offset, new_count); +} + +} // namespace bit diff --git a/include/bitlib/bit-iterator/bit_iterator.hpp b/include/bitlib/bit-iterator/bit_iterator.hpp index 235d028b..63b84fe7 100644 --- a/include/bitlib/bit-iterator/bit_iterator.hpp +++ b/include/bitlib/bit-iterator/bit_iterator.hpp @@ -51,6 +51,7 @@ class bit_iterator constexpr bit_iterator(const bit_iterator& other); explicit constexpr bit_iterator(iterator_type i); constexpr bit_iterator(iterator_type i, size_type pos); + explicit constexpr bit_iterator(const pointer& ptr); // Assignment public: @@ -178,6 +179,15 @@ constexpr bit_iterator::bit_iterator( , _position((assert(pos < binary_digits::value), pos)) { } + + +// Explicitly constructs an unaligned bit iterator from a pointer +template +constexpr bit_iterator::bit_iterator(const pointer& ptr) +: _current(ptr->address()) +, _position(ptr->position()) +{ +} // -------------------------------------------------------------------------- // diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index 40573620..136cbfcc 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -53,7 +53,7 @@ class bit_reference constexpr bit_reference& operator=(const bit_reference& other) noexcept; template constexpr bit_reference& operator=(const bit_reference& other) noexcept; - constexpr bit_reference& operator=(bit_value val) noexcept; + constexpr bit_reference& operator=(const bit_value val) noexcept; constexpr bit_reference& assign(word_type val) noexcept; constexpr bit_reference& assign(word_type val, size_type pos); @@ -205,7 +205,7 @@ constexpr bit_reference& bit_reference::operator=( // Assigns a bit value to the bit reference template constexpr bit_reference& bit_reference::operator=( - bit_value val + const bit_value val ) noexcept { val ? set() : reset(); @@ -273,12 +273,6 @@ constexpr bit_reference& bit_reference::operator^=( // ----------------------- BIT REFERENCE: CONVERSION ------------------------ // // Explicitly converts the bit reference to a boolean value -template<> -constexpr bit_reference::operator bool( -) const noexcept -{ - return static_cast(*_ptr & _mask); -} template constexpr bit_reference::operator bool( ) const noexcept diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e84ae521..de4f8c12 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -16,10 +16,10 @@ if (BITLIB_COVERAGE) endif() # ASAN throws no matter what when using Google HWY, not sure why but need to fix -if (NOT BITLIB_HWY) - target_compile_options(bitlib-tests PUBLIC -fsanitize=address) - target_link_options(bitlib-tests PUBLIC -fsanitize=address) -endif() +# if (NOT BITLIB_HWY) +# target_compile_options(bitlib-tests PUBLIC -fsanitize=address) +# target_link_options(bitlib-tests PUBLIC -fsanitize=address) +# endif() # specify test-specific libraries include_directories(${googletest_SOURCE_DIR}/googletest/include/gtest src/utils) diff --git a/test/src/test-array.cpp b/test/src/test-array.cpp index d7d272a0..8fdbb4e1 100644 --- a/test/src/test-array.cpp +++ b/test/src/test-array.cpp @@ -64,54 +64,54 @@ TEST(ArrayTest, ZeroSize) { } // Test that the default constructor initializes all bits to false. -TEST(BitArrayTest, DefaultInitialization) { - bit_array<8> ba; +TEST(ArrayTest, DefaultInitialization) { + bit::bit_array<8> ba; for (size_t i = 0; i < ba.size(); ++i) { EXPECT_FALSE(ba[i]) << "Bit " << i << " should be false by default"; } } // Test the fill() method. -TEST(BitArrayTest, FillMethod) { - bit_array<10> ba; - ba.fill(true); +TEST(ArrayTest, FillMethod) { + bit::bit_array<10> ba; + ba.fill(bit::bit_value(true)); for (size_t i = 0; i < ba.size(); ++i) { EXPECT_TRUE(ba[i]) << "Bit " << i << " should be true after fill(true)"; } - ba.fill(false); + ba.fill(bit::bit_value(false)); for (size_t i = 0; i < ba.size(); ++i) { EXPECT_FALSE(ba[i]) << "Bit " << i << " should be false after fill(false)"; } } // Test element access via operator[] and at(), including out-of-range checking. -TEST(BitArrayTest, ElementAccess) { - bit_array<5> ba; - ba.fill(false); - ba[2] = true; +TEST(ArrayTest, ElementAccess) { + bit::bit_array<5> ba; + ba.fill(bit::bit_value(false)); + ba[2] = bit::bit_value(true); EXPECT_TRUE(ba.at(2)); EXPECT_THROW(ba.at(5), std::out_of_range); } // Test front() and back() member functions. -TEST(BitArrayTest, FrontBackAccess) { - bit_array<4> ba; - ba.fill(false); - ba.front() = true; - ba.back() = true; +TEST(ArrayTest, FrontBackAccess) { + bit::bit_array<4> ba; + ba.fill(bit::bit_value(false)); + ba.front() = bit::bit_value(true); + ba.back() = bit::bit_value(true); EXPECT_TRUE(ba.front()); EXPECT_TRUE(ba.back()); } // Test iterator functionality (both non-const and range-based). -TEST(BitArrayTest, IteratorFunctionality) { - bit_array<4> ba; - ba.fill(false); +TEST(ArrayTest, IteratorFunctionality) { + bit::bit_array<4> ba; + ba.fill(bit::bit_value(false)); int index = 0; for (auto it = ba.begin(); it != ba.end(); ++it) { // Change the second element using the iterator. if (index == 1) { - *it = true; + *it = bit::bit_value(true); } ++index; } @@ -119,20 +119,20 @@ TEST(BitArrayTest, IteratorFunctionality) { } // Test const_iterator functionality. -TEST(BitArrayTest, ConstIteratorFunctionality) { - bit_array<4> ba; - ba.fill(true); - const bit_array<4>& const_ba = ba; +TEST(ArrayTest, ConstIteratorFunctionality) { + bit::bit_array<4> ba; + ba.fill(bit::bit_value(true)); + const bit::bit_array<4>& const_ba = ba; for (auto it = const_ba.begin(); it != const_ba.end(); ++it) { EXPECT_TRUE(*it); } } // Test the swap() member function. -TEST(BitArrayTest, SwapFunctionality) { - bit_array<4> ba1, ba2; - ba1.fill(false); - ba2.fill(true); +TEST(ArrayTest, SwapFunctionality) { + bit::bit_array<4> ba1, ba2; + ba1.fill(bit::bit_value(false)); + ba2.fill(bit::bit_value(true)); ba1.swap(ba2); for (size_t i = 0; i < ba1.size(); ++i) { EXPECT_TRUE(ba1[i]) << "After swap, ba1[" << i << "] should be true"; @@ -141,59 +141,59 @@ TEST(BitArrayTest, SwapFunctionality) { } // Test comparison operators (== and !=). -TEST(BitArrayTest, ComparisonOperators) { - bit_array<5> ba1 = { true, true, false, false, true }; - bit_array<5> ba2 = { true, true, false, false, true }; +TEST(ArrayTest, ComparisonOperators) { + bit::bit_array<5> ba1 = { bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1 }; + bit::bit_array<5> ba2 = { bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1 }; EXPECT_EQ(ba1, ba2); - ba2[2] = true; // Change one element + ba2[2] = bit::bit_value(true); // Change one element EXPECT_NE(ba1, ba2); } // Test the data() method to access the underlying storage. -TEST(BitArrayTest, DataAccess) { - bit_array<8> ba; - ba.fill(false); +TEST(ArrayTest, DataAccess) { + bit::bit_array<8> ba; + ba.fill(bit::bit_value(false)); // Assume data() returns a pointer to a boolean array. - bool* data_ptr = ba.data(); + uint8_t* data_ptr = ba.data(); EXPECT_FALSE(data_ptr[0]); - ba[0] = true; + ba[0] = bit::bit_value(true); EXPECT_TRUE(data_ptr[0]); } // Test size() and empty() functions. -TEST(BitArrayTest, SizeAndEmpty) { - bit_array<0> ba_empty; +TEST(ArrayTest, SizeAndEmpty) { + bit::bit_array<0> ba_empty; EXPECT_EQ(ba_empty.size(), 0); EXPECT_TRUE(ba_empty.empty()); - bit_array<5> ba; + bit::bit_array<5> ba; EXPECT_EQ(ba.size(), 5); EXPECT_FALSE(ba.empty()); } // Test initializer list construction. -TEST(BitArrayTest, InitializerListConstruction) { - bit_array<3> ba = { true, false, true }; +TEST(ArrayTest, InitializerListConstruction) { + bit::bit_array<3> ba = { bit::bit1, bit::bit0, bit::bit1 }; EXPECT_TRUE(ba[0]); EXPECT_FALSE(ba[1]); EXPECT_TRUE(ba[2]); } // Test copy constructor and copy assignment operator. -TEST(BitArrayTest, CopyAndAssignment) { - bit_array<5> ba1 = { true, false, true, false, true }; - bit_array<5> ba_copy(ba1); +TEST(ArrayTest, CopyAndAssignment) { + bit::bit_array<5> ba1 = { bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1 }; + bit::bit_array<5> ba_copy(ba1); EXPECT_EQ(ba1, ba_copy); - bit_array<5> ba_assigned; + bit::bit_array<5> ba_assigned; ba_assigned = ba1; EXPECT_EQ(ba1, ba_assigned); } // Test move semantics (move constructor and move assignment), if implemented. -TEST(BitArrayTest, MoveSemantics) { - bit_array<5> ba1 = { true, false, true, false, true }; - bit_array<5> ba_moved(std::move(ba1)); +TEST(ArrayTest, MoveSemantics) { + bit::bit_array<5> ba1 = { bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1 }; + bit::bit_array<5> ba_moved(std::move(ba1)); // We test the moved-to container's values. The moved-from object is valid but unspecified. EXPECT_TRUE(ba_moved[0]); EXPECT_FALSE(ba_moved[1]); @@ -201,7 +201,7 @@ TEST(BitArrayTest, MoveSemantics) { EXPECT_FALSE(ba_moved[3]); EXPECT_TRUE(ba_moved[4]); - bit_array<5> ba2 = { false, false, false, false, false }; + bit::bit_array<5> ba2 = { bit::bit0, bit::bit0, bit::bit0, bit::bit0, bit::bit0 }; ba2 = std::move(ba_moved); EXPECT_TRUE(ba2[0]); EXPECT_FALSE(ba2[1]); diff --git a/test/src/test-span.cpp b/test/src/test-span.cpp new file mode 100644 index 00000000..0935b341 --- /dev/null +++ b/test/src/test-span.cpp @@ -0,0 +1,203 @@ +#include +#include + +#include "bitlib/bit-containers/bit_span.hpp" + +#include "fixtures.hpp" + +// Third-party libraries +#include "gtest/gtest.h" + +namespace { + // For these tests we use a simple word type. + using WordType = uint8_t; + // We assume that bit::bitsof() returns the number of bits in a WordType. +} + +// ---------- Dynamic Extent Tests ---------- + +TEST(BitSpanTest, DefaultConstructorDynamic) { + bit::bit_span span; + EXPECT_EQ(span.size(), 0u); + EXPECT_TRUE(span.empty()); +} + +TEST(BitSpanTest, ConstructorFromBitPointerDynamic) { + WordType word = 0; + bit::bit_pointer ptr(&word, 0); + bit::bit_span span(ptr, bit::bitsof()); + EXPECT_EQ(span.size(), bit::bitsof()); + EXPECT_FALSE(span.empty()); +} + +TEST(BitSpanTest, ConstructorFromWordPointerDynamic) { + WordType word = 0; + // One word gives bit::bitsof() bits. + bit::bit_span span(&word, 1); + EXPECT_EQ(span.size(), 1); +} + +TEST(BitSpanTest, ElementAccessDynamic) { + WordType word = 0; + bit::bit_span span(&word, 1); + // Initially, all bits are bit::bit0. + EXPECT_EQ(span.front(), bit::bit0); + EXPECT_EQ(span.back(), bit::bit0); + + // Modify first bit. + span[0] = bit::bit1; + EXPECT_EQ(span.front(), bit::bit1); + EXPECT_EQ(span[0], bit::bit1); + + // Modify last bit. + span[bit::bitsof() - 1] = bit::bit1; + EXPECT_EQ(span.back(), bit::bit1); +} + +TEST(BitSpanTest, IteratorsDynamic) { + WordType word = 0; + bit::bit_span span(&word, 1); + + // Set a pattern: even indices = bit::bit1, odd indices = bit::bit0. + for (std::size_t i = 0; i < span.size(); ++i) { + span[i] = (i % 2 == 0) ? bit::bit1 : bit::bit0; + } + + std::size_t idx = 0; + for (auto it = span.begin(); it != span.end(); ++it, ++idx) { + if (idx % 2 == 0) + EXPECT_EQ(*it, bit::bit1); + else + EXPECT_EQ(*it, bit::bit0); + } + EXPECT_EQ(idx, span.size()); +} + +TEST(BitSpanTest, SubspanDynamic) { + WordType word = 0; + bit::bit_span span(&word, 1); + + // Set a known pattern: 1,0,1,0,... + for (std::size_t i = 0; i < span.size(); ++i) { + span[i] = (i % 2 == 0) ? bit::bit1 : bit::bit0; + } + // Create a subspan starting at index 2 with 4 bits. + auto sub = span.subspan(2, 4); + EXPECT_EQ(sub.size(), 4u); + for (std::size_t i = 0; i < sub.size(); ++i) { + EXPECT_EQ(sub[i], span[i + 2]); + } +} + +TEST(BitSpanTest, SubspanDefaultCountDynamic) { + WordType word = 0; + bit::bit_span span(&word, bit::bitsof(word)); + + // Fill span with a simple pattern. + for (std::size_t i = 0; i < span.size(); ++i) { + span[i] = (i % 2 == 0) ? bit::bit1 : bit::bit0; + } + auto sub = span.subspan(3); + EXPECT_EQ(sub.size(), span.size() - 3); + for (std::size_t i = 0; i < sub.size(); ++i) { + EXPECT_EQ(sub[i], span[i + 3]); + } +} + +TEST(BitSpanTest, ConstSpanDynamic) { + WordType word = 0; + bit::bit_span span(&word, 1); + span[0] = bit::bit1; + const bit::bit_span& cspan = span; + EXPECT_EQ(cspan[0], bit::bit1); + EXPECT_EQ(cspan.front(), bit::bit1); + EXPECT_EQ(cspan.back(), bit::bit1); +} + +TEST(BitSpanTest, CopyConstructionDynamic) { + WordType word = 0; + bit::bit_span span1(&word, 1); + span1[0] = bit::bit1; + bit::bit_span span2 = span1; + EXPECT_EQ(span2.size(), span1.size()); + EXPECT_EQ(span2[0], bit::bit1); +} + +// ---------- Fixed Extent Tests ---------- + +TEST(BitSpanTest, DefaultConstructorFixed) { + // For a fixed extent, the size is known at compile time. + bit::bit_span()> span; + EXPECT_EQ(span.size(), bit::bitsof()); + // Do not dereference the span if data is null. +} + +TEST(BitSpanTest, ConstructorFromWordPointerFixed) { + WordType word = 0; + // For a fixed extent, one word is required. + bit::bit_span()> span(&word); + EXPECT_EQ(span.size(), bit::bitsof()); +} + +TEST(BitSpanTest, ElementAccessFixed) { + WordType word = 0; + bit::bit_span()> span(&word); + // Initially, all bits should be bit::bit0. + EXPECT_EQ(span.front(), bit::bit0); + EXPECT_EQ(span.back(), bit::bit0); + + // Modify a bit and check. + span[3] = bit::bit1; + EXPECT_EQ(span[3], bit::bit1); +} + +TEST(BitSpanTest, IteratorsFixed) { + WordType word = 0; + bit::bit_span()> span(&word); + + // Set a pattern: set bit at index 1 and 5. + span[1] = bit::bit1; + span[5] = bit::bit1; + + std::size_t idx = 0; + for (auto it = span.begin(); it != span.end(); ++it, ++idx) { + if (idx == 1 || idx == 5) + EXPECT_EQ(*it, bit::bit1); + else + EXPECT_EQ(*it, bit::bit0); + } + EXPECT_EQ(idx, bit::bitsof()); +} + +TEST(BitSpanTest, CopyConstructionFixed) { + WordType word = 0; + bit::bit_span()> span1(&word); + span1[2] = bit::bit1; + bit::bit_span()> span2 = span1; + EXPECT_EQ(span2.size(), bit::bitsof()); + EXPECT_EQ(span2[2], bit::bit1); +} + +// ---------- Const Reference Alias Tests ---------- + +TEST(BitSpanTest, ConstReferenceAlias) { + WordType word = 0; + bit::bit_span span(&word, 1); + span[0] = bit::bit1; + + // Create a const view. + const bit::bit_span& cspan = span; + + // Use a static_assert to verify that the type of cspan[0] + // is the same as the defined const_reference. + static_assert(std::is_same_v::const_reference>, + "cspan[0] is not of type const_reference"); + + // At runtime, verify the value is as expected. + EXPECT_EQ(cspan[0], bit::bit1); + + // The following assignment should fail to compile if uncommented, + // which confirms that the const_reference is read-only. + // cspan[0] = bit::bit0; +} diff --git a/utils/test_utils.hpp b/utils/test_utils.hpp index 3155fd82..8938abbd 100644 --- a/utils/test_utils.hpp +++ b/utils/test_utils.hpp @@ -3,7 +3,7 @@ // Name: test_utils.hpp // Description: General utilities for testing // Creator: Bryce Kille -// Contributor(s): Bryce Kille [2019], +// Contributor(s): Bryce Kille [2019], // Collin Gress [2019] // License: BSD 3-Clause License // ========================================================================== // @@ -39,10 +39,10 @@ constexpr auto comparator = [](auto b1, auto b2){ template struct rebind_container; // Helper struct for rebind_container -template class Container, - class NewType, +template class Container, + class NewType, class... Parameters > struct rebind_container, NewType> @@ -56,7 +56,7 @@ template auto bitcont_to_boolcont(const Container bitcont){ auto bfirst = bit::bit_iterator(std::begin(bitcont)); auto blast = bit::bit_iterator(std::end(bitcont)); - typename rebind_container::type c(std::distance(bfirst, + typename rebind_container::type c(std::distance(bfirst, blast) ); auto benchmark_it = std::begin(c); @@ -70,9 +70,9 @@ auto bitcont_to_boolcont(const Container bitcont){ // Produces container of random numbers from min to max template Container make_random_container( - std::size_t size, - T min = std::numeric_limits::min(), - T max = std::numeric_limits::max(), + std::size_t size, + T min = std::numeric_limits::min(), + T max = std::numeric_limits::max(), const T& seed = T() ) { @@ -80,7 +80,7 @@ Container make_random_container( std::random_device device; std::mt19937 engine(seed == T() ? device() : seed); std::uniform_int_distribution distribution(min, max); - auto it = std::begin(c); + auto it = std::begin(c); for (std::size_t i = 0; i < size; ++i) { *it = distribution(engine); ++it; @@ -95,15 +95,33 @@ inline unsigned long long generate_random_number(size_t min, size_t max) { // Specify the engine and distribution. std::mt19937 mersenne_engine {rnd_device()}; // Generates random integers std::uniform_int_distribution dist {min, max}; - + return dist(mersenne_engine); } +template +std::array get_random_arr( + WordType min = std::numeric_limits::min(), + WordType max = std::numeric_limits::max() +) { + // First create an instance of an engine. + std::random_device rnd_device; + // Specify the engine and distribution. + std::mt19937 mersenne_engine {rnd_device()}; // Generates random integers + std::uniform_int_distribution dist {min, max}; + + auto gen = [&dist, &mersenne_engine](){ + return dist(mersenne_engine); + }; + std::array arr{}; + generate(begin(arr), end(arr), gen); + return arr; +} template std::vector get_random_vec( unsigned long long int size, - WordType min = std::numeric_limits::min(), + WordType min = std::numeric_limits::min(), WordType max = std::numeric_limits::max() ) { // First create an instance of an engine. @@ -111,7 +129,7 @@ std::vector get_random_vec( // Specify the engine and distribution. std::mt19937 mersenne_engine {rnd_device()}; // Generates random integers std::uniform_int_distribution dist {min, max}; - + auto gen = [&dist, &mersenne_engine](){ return dist(mersenne_engine); }; @@ -136,7 +154,7 @@ template std::vector boolvec_from_bitvec(bit::bit_vector bv) { std::vector ret_vec{}; for (bit::bit_value value : bv) { - ret_vec.push_back(value == bit::bit1 ? true : false); + ret_vec.push_back(value == bit::bit1 ? true : false); } return ret_vec; } From 9e4467ea43d0f553eeebd47b4e5c80ea88995502 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sat, 1 Mar 2025 11:06:04 -0500 Subject: [PATCH 04/85] Elaborate on subspan. Add first, and last --- include/bitlib/bit-containers/bit_span.hpp | 63 ++++++++++++++++++++-- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/include/bitlib/bit-containers/bit_span.hpp b/include/bitlib/bit-containers/bit_span.hpp index c807a01a..3b154d63 100644 --- a/include/bitlib/bit-containers/bit_span.hpp +++ b/include/bitlib/bit-containers/bit_span.hpp @@ -47,6 +47,16 @@ class bit_span : private bit_span_storage { // The start of the span, represented as a bit_pointer. pointer data_; + template + static constexpr size_t subspan_extent() { + if constexpr (NewCount != std::dynamic_extent) + return NewCount; + else if constexpr (Extent != std::dynamic_extent) + return Extent - NewOffset; + else + return std::dynamic_extent; + } + public: // --- Constructors --- @@ -103,10 +113,19 @@ class bit_span : private bit_span_storage { // --- Subspan Functionality --- + // Creates a subspan starting at offset with count bits. - // If count is not specified (or set to -1), it goes to the end. - constexpr bit_span - subspan(size_type offset, size_type count = std::dynamic_extent) const noexcept; + template + constexpr auto subspan() const noexcept -> bit_span()>; + constexpr bit_span subspan(size_type offset, size_type count = std::dynamic_extent) const noexcept requires(Extent == std::dynamic_extent); + + template + constexpr bit_span first() const noexcept requires(NewExtent != std::dynamic_extent); + constexpr bit_span first(size_type offset) const noexcept requires(Extent == std::dynamic_extent); + + template + constexpr bit_span last() const noexcept requires(RevOffset != std::dynamic_extent); + constexpr bit_span last(size_type offset) const noexcept requires(Extent == std::dynamic_extent); }; // --- Constructors --- @@ -208,12 +227,46 @@ constexpr bit_span::iterator bit_span::end() c // --- Subspan Functionality --- +template +template +constexpr auto bit_span::subspan() const noexcept + -> bit_span()> { + + using NewSpanType = bit_span()>; + + if constexpr (NewExtent == std::dynamic_extent) { + return NewSpanType(begin() + NewOffset, size() - NewOffset); + } else { + return NewSpanType(begin() + NewOffset); + } +} + // Creates a subspan starting at offset with count bits. -// If count is not specified (or set to -1), it goes to the end. template -constexpr bit_span bit_span::subspan(size_type offset, size_type count) const noexcept { +constexpr bit_span bit_span::subspan(size_type offset, size_type count) const noexcept requires(Extent == std::dynamic_extent) { size_type new_count = (count == std::dynamic_extent) ? size() - offset : count; return bit_span(begin() + offset, new_count); } +template +template +constexpr bit_span bit_span::first() const noexcept requires(NewExtent != std::dynamic_extent) { + return bit_span(data_); +} + +template +constexpr bit_span bit_span::first(size_type offset) const noexcept requires(Extent == std::dynamic_extent) { + return bit_span(begin() + offset); +} + +template +template +constexpr bit_span bit_span::last() const noexcept requires(RevOffset != std::dynamic_extent) { + return bit_span(begin() + size() - RevOffset); +} + +template +constexpr bit_span bit_span::last(size_type offset) const noexcept requires(Extent == std::dynamic_extent) { +} + } // namespace bit From 1b278bd0c40881698b4c1ec72fd944acf185099f Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sat, 1 Mar 2025 16:37:53 -0500 Subject: [PATCH 05/85] Cmake improvements --- CMakeLists.txt | 8 ++------ test/CMakeLists.txt | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4668a909..ad1028a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,12 +7,6 @@ project(Bit-Vector VERSION 0.3.0) # set output directory of builds #set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) -# set CXX standard -# Things seem to be faster in cxx 20, and there is also std::shift_* -# Should fall back on 17 if 20 is not supported -set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard to be used") -set(CMAKE_CXX_STANDARD_REQUIRED ON) - # set up linters/checkers #set(CMAKE_CXX_CPPCHECK cppcheck;--std=c++17;--file-filter=*bitlib*) #set(CMAKE_CXX_CPPLINT cpplint;--linelength=100;--filter=-whitespace;) @@ -28,6 +22,8 @@ target_include_directories(bitlib INTERFACE $ ) +target_compile_features(bitlib INTERFACE cxx_std_20) + # specify global compiler flags include_directories("include/" "utils/" ) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index de4f8c12..47f763b3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -22,8 +22,8 @@ endif() # endif() # specify test-specific libraries -include_directories(${googletest_SOURCE_DIR}/googletest/include/gtest src/utils) -target_link_libraries(bitlib-tests PUBLIC GTest::gtest GTest::gtest_main -pthread -lgcov --coverage) +target_link_libraries(bitlib-tests PRIVATE bitlib GTest::gtest GTest::gtest_main) +target_link_options(bitlib-tests PRIVATE -pthread -lgcov --coverage) if (NOT BITLIB_GTEST_REPEAT) set(BITLIB_GTEST_REPEAT 1) From a6c81ada9af2bec329314c039ad5ac093c1c5ca5 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sun, 2 Mar 2025 08:48:40 -0500 Subject: [PATCH 06/85] clang-format-19 file --- .clang-format | 308 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..81b6afa0 --- /dev/null +++ b/.clang-format @@ -0,0 +1,308 @@ +--- +Language: Cpp +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCaseArrows: false + AlignCaseColons: false +AlignConsecutiveTableGenBreakingDAGArgColons: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveTableGenCondOperatorColons: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveTableGenDefinitionColons: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowBreakBeforeNoexceptSpecifier: Never +AllowShortBlocksOnASingleLine: Never +AllowShortCaseExpressionOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: true +AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAdjacentStringLiterals: true +BreakAfterAttributes: Leave +BreakAfterJavaFieldAnnotations: false +BreakAfterReturnType: None +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakFunctionDefinitionParameters: false +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +BreakTemplateDeclarations: Yes +ColumnLimit: 0 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: true +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 3 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '([-_](test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: true +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertBraces: true +InsertNewlineAtEOF: false +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLines: + AtEndOfFile: false + AtStartOfBlock: false + AtStartOfFile: true +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: '' +MacroBlockEnd: '' +MainIncludeChar: Quote +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: NextLine +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakScopeResolution: 500 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +PPIndentWidth: -1 +QualifierAlignment: Leave +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + - ParseTestProto + - ParsePartialTestProto + CanonicalDelimiter: pb + BasedOnStyle: google +ReferenceAlignment: Pointer +ReflowComments: false +RemoveBracesLLVM: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SkipMacroDefinitionBody: false +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterPlacementOperator: true + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + ExceptDoubleParentheses: false + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: Auto +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TableGenBreakInsideDAGArg: DontBreak +TabWidth: 2 +UseTab: Never +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +... + From 7136c9d3cd9a27d8d24004f09fc301c5582d8c7b Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sun, 2 Mar 2025 20:25:55 -0500 Subject: [PATCH 07/85] partially dynamic bit-array type --- include/bitlib/bit-containers/bit_array.hpp | 65 +++--- .../bit_array_dynamic_extent.hpp | 216 ++++++++++++++++++ 2 files changed, 246 insertions(+), 35 deletions(-) create mode 100644 include/bitlib/bit-containers/bit_array_dynamic_extent.hpp diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index 04c485b2..28c78099 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -65,56 +65,51 @@ constexpr bit_array(const std::initializer_list init); constexpr bit_array(const std::string_view s); ~bit_array() = default; - -private: - -public: - - /* +/* * Assignment */ - constexpr bit_array& operator=(const bit_array& other) = default; - constexpr bit_array& operator=(bit_array&& other) noexcept; +constexpr bit_array& operator=(const bit_array& other) = default; +constexpr bit_array& operator=(bit_array&& other) noexcept; - constexpr bool operator==(const bit_array& other) const noexcept; +constexpr bool operator==(const bit_array& other) const noexcept; - /* +/* * Element Access */ - constexpr reference operator[](size_type pos); - constexpr const_reference operator[](size_type pos) const; - constexpr reference at(size_type pos); - constexpr const_reference at(size_type pos) const; - constexpr reference front(); - constexpr const_reference front() const; - constexpr reference back(); - constexpr const_reference back() const; - constexpr WordType* data() noexcept; - constexpr const WordType* data() const noexcept; +constexpr reference operator[](size_type pos); +constexpr const_reference operator[](size_type pos) const; +constexpr reference at(size_type pos); +constexpr const_reference at(size_type pos) const; +constexpr reference front(); +constexpr const_reference front() const; +constexpr reference back(); +constexpr const_reference back() const; +constexpr WordType* data() noexcept; +constexpr const WordType* data() const noexcept; - /* +/* * Iterators */ - constexpr iterator begin() noexcept; - constexpr iterator end() noexcept; - constexpr const_iterator begin() const noexcept; - constexpr const_iterator end() const noexcept; - constexpr const_iterator cbegin() const noexcept; - constexpr const_iterator cend() const noexcept; +constexpr iterator begin() noexcept; +constexpr iterator end() noexcept; +constexpr const_iterator begin() const noexcept; +constexpr const_iterator end() const noexcept; +constexpr const_iterator cbegin() const noexcept; +constexpr const_iterator cend() const noexcept; - /* +/* * Capacity */ - constexpr bool empty() const noexcept; - constexpr size_type size() const noexcept; - constexpr size_type max_size() const noexcept; +constexpr bool empty() const noexcept; +constexpr size_type size() const noexcept; +constexpr size_type max_size() const noexcept; - /* +/* * Operations */ - constexpr void fill(value_type bit_val) noexcept; - constexpr void swap(bit_array& other) noexcept; - //constexpr std::synth-three-way-result operator<=>() const noexcept; +constexpr void fill(value_type bit_val) noexcept; +constexpr void swap(bit_array& other) noexcept; +//constexpr std::synth-three-way-result operator<=>() const noexcept; }; template diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp new file mode 100644 index 00000000..25a35a13 --- /dev/null +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -0,0 +1,216 @@ +// ================================= BIT_ARRAY =================================== // +// Project: The Experimental Bit Algorithms Library +// \file bit_array.hpp +// Description: Implementation of bit_array +// Creator: Vincent Reverdy +// Contributor: Peter McLean [2025] +// License: BSD 3-Clause License +// ========================================================================== // +#ifndef _BIT_ARRAY_DYNAMIC_EXTENT_HPP_INCLUDED +#define _BIT_ARRAY_DYNAMIC_EXTENT_HPP_INCLUDED + +#include +#include +#include +#include +#include // std::dynamic_extent +#include + +#include "bitlib/bit-algorithms/bit_algorithm.hpp" +#include "bitlib/bit-containers/bit_bitsof.hpp" +#include "bitlib/bit-iterator/bit.hpp" + +namespace bit { + +template <> +class bit_array; +using dynarray = bit_array; + +template <> +class bit_array { + public: + using T = bit_value; + using WordType = std::uint8_t; + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = bit_reference; + using const_reference = const reference; + using pointer = bit_pointer; + using const_pointer = const pointer; + using iterator = bit_iterator::iterator; + using const_iterator = bit_iterator::const_iterator; + + private: + size_type m_words; + size_type m_size; + std::unique_ptr storage; + + public: + /* + * Constructors, copies and moves... + */ + constexpr bit_array() noexcept; + constexpr bit_array(value_type bit_val); + constexpr bit_arrayconst bit_array& other) = default; + constexpr bit_arrayconst bit_array&& other) noexcept; + constexpr bit_arrayconst std::initializer_list init); + constexpr bit_arrayconst std::initializer_list init); + constexpr bit_arrayconst std::initializer_list init); + constexpr bit_arrayconst std::string_view s); + + ~bit_array() = default; + /* + * Assignment + */ + constexpr bit_array& operator=(const bit_array& other) = default; + constexpr bit_array& operator=(bit_array&& other) noexcept; + + constexpr bool operator==(const bit_array& other) const noexcept; + + /* + * Element Access + */ + constexpr reference operator[](size_type pos); + constexpr const_reference operator[](size_type pos) const; + constexpr reference at(size_type pos); + constexpr const_reference at(size_type pos) const; + constexpr reference front(); + constexpr const_reference front() const; + constexpr reference back(); + constexpr const_reference back() const; + constexpr WordType* data() noexcept; + constexpr const WordType* data() const noexcept; + + /* + * Iterators + */ + constexpr iterator begin() noexcept; + constexpr iterator end() noexcept; + constexpr const_iterator begin() const noexcept; + constexpr const_iterator end() const noexcept; + constexpr const_iterator cbegin() const noexcept; + constexpr const_iterator cend() const noexcept; + + /* + * Capacity + */ + constexpr bool empty() const noexcept; + constexpr size_type size() const noexcept; + constexpr size_type max_size() const noexcept; + + /* + * Operations + */ + constexpr void fill(value_type bit_val) noexcept; + constexpr void swap(bit_array& other) noexcept; + //constexpr std::synth-three-way-result operator<=>() const noexcept; +}; + +// Non-member swap to allow ADL. +template +void swap(dynarray& a, dynarray& b) noexcept { + a.swap(b); +} + +constexpr bool bit_array::operator==(const bit_array& other) const noexcept { + return equal(begin(), end(), other.begin()); +} + +constexpr void bit_array::swap(bit_array& other) noexcept { + std::swap(this->storage, other.storage); +} + +// -------------------------------------------------------------------------- // +/* + * Element Access + */ +constexpr typename bit_array::reference bit_array::operator[](size_type pos) { + return begin()[pos]; +} + +constexpr typename bit_array::const_reference bit_array::operator[](size_type pos) const { + return begin()[pos]; +} + +constexpr typename bit_array::reference bit_array::at(size_type pos) { + if (pos < size()) { + return begin()[pos]; + } else { + throw std::out_of_range("Position is out of range"); + } +} + +constexpr typename bit_array::const_reference bit_array::at(size_type pos) const { + if (pos < size()) { + return begin()[pos]; + } else { + throw std::out_of_range("Position is out of range"); + } +} + +constexpr typename bit_array::reference bit_array::front() { + return begin()[0]; +} + +constexpr typename bit_array::const_reference bit_array::front() const { + return begin()[0]; +} + +constexpr typename bit_array::reference bit_array::back() { + return begin()[size() - 1]; +} + +constexpr typename bit_array::const_reference bit_array::back() const { + return begin()[size() - 1]; +} + +constexpr typename bit_array::WordType* bit_array::data() noexcept { + return size() ? storage.get() : nullptr; +} + +constexpr const typename bit_array::WordType* bit_array::data() const noexcept { + return size() ? storage.get() : nullptr; +} + +// -------------------------------------------------------------------------- // + +constexpr typename bit_array::size_type bit_array::size() const noexcept { return m_size; } + +constexpr typename bit_array::size_type bit_array::max_size() const noexcept { return size(); } + +constexpr bool bit_array::empty() const noexcept { return 0 == size(); } + +// Iterators +// -------------------------------------------------------------------------- // + +constexpr typename bit_array::iterator bit_array::begin() noexcept { + return iterator(storage.get()); +} + +constexpr typename bit_array::iterator bit_array::end() noexcept { + return begin() + size(); +} + +constexpr typename bit_array::const_iterator bit_array::begin() const noexcept { + return const_iterator(storage.get()); +} + +constexpr typename bit_array::const_iterator bit_array::end() const noexcept { + return const_iterator(storage.get()) + size(); +} + +constexpr typename bit_array::const_iterator bit_array::cbegin() const noexcept { + return const_iterator(storage.get()); +} + +constexpr typename bit_array::const_iterator bit_array::cend() const noexcept { + return const_iterator(storage.get()) + size(); +} + +// -------------------------------------------------------------------------- // + +// ========================================================================== // +} // namespace bit +#endif // _BIT_ARRAY_DYNAMIC_EXTENT_HPP_INCLUDED +// ========================================================================== // From 4e89ddc46782d55604d8f6a3e119d4aa9c04ad9b Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Tue, 4 Mar 2025 20:12:14 -0500 Subject: [PATCH 08/85] fsadf --- CMakeLists.txt | 2 +- include/bitlib/bit-containers/bit_array.hpp | 2 +- .../bit_array_dynamic_extent.hpp | 82 +++++++++++++------ include/bitlib/bit-containers/bit_bitsof.hpp | 34 +++++--- test/src/test-array.cpp | 2 +- test/src/vector_test.cpp | 42 +++++----- 6 files changed, 104 insertions(+), 60 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ad1028a0..10972e8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ target_include_directories(bitlib INTERFACE $ ) -target_compile_features(bitlib INTERFACE cxx_std_20) +target_compile_features(bitlib INTERFACE cxx_std_23) # specify global compiler flags diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index 28c78099..152ad6d0 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -46,7 +46,7 @@ using value_type = bit_value; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using reference = bit_reference; -using const_reference = const reference; +using const_reference = const bit_reference; using pointer = bit_pointer; using const_pointer = const pointer; using iterator = bit_iterator::iterator>; diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index 25a35a13..2afe56c5 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -17,15 +17,12 @@ #include #include "bitlib/bit-algorithms/bit_algorithm.hpp" +#include "bitlib/bit-containers/bit_array.hpp" #include "bitlib/bit-containers/bit_bitsof.hpp" #include "bitlib/bit-iterator/bit.hpp" namespace bit { -template <> -class bit_array; -using dynarray = bit_array; - template <> class bit_array { public: @@ -35,38 +32,42 @@ class bit_array { using size_type = std::size_t; using difference_type = std::ptrdiff_t; using reference = bit_reference; - using const_reference = const reference; + using const_reference = const bit_reference; using pointer = bit_pointer; using const_pointer = const pointer; - using iterator = bit_iterator::iterator; - using const_iterator = bit_iterator::const_iterator; + using iterator = bit_iterator; + using const_iterator = bit_iterator; private: size_type m_words; size_type m_size; std::unique_ptr storage; + static constexpr std::size_t Words(std::size_t N) { return (N * bitsof() + bitsof() - 1) / bitsof(); }; public: /* * Constructors, copies and moves... */ - constexpr bit_array() noexcept; - constexpr bit_array(value_type bit_val); - constexpr bit_arrayconst bit_array& other) = default; - constexpr bit_arrayconst bit_array&& other) noexcept; - constexpr bit_arrayconst std::initializer_list init); - constexpr bit_arrayconst std::initializer_list init); - constexpr bit_arrayconst std::initializer_list init); - constexpr bit_arrayconst std::string_view s); - - ~bit_array() = default; + constexpr bit_array() noexcept; + constexpr bit_array(size_type size); + constexpr bit_array(size_type size, value_type bit_val); + constexpr bit_array(const bit_array& other) = default; + constexpr bit_array(const bit_array&& other) noexcept; + constexpr bit_array(const std::initializer_list init); +#if 0 + constexpr bit_array(const std::initializer_list init); +#endif + constexpr bit_array(const std::initializer_list init); + constexpr bit_array(const std::string_view s); + + ~bit_array() = default; /* * Assignment */ constexpr bit_array& operator=(const bit_array& other) = default; constexpr bit_array& operator=(bit_array&& other) noexcept; - constexpr bool operator==(const bit_array& other) const noexcept; + constexpr bool operator==(const bit_array& other) const noexcept; /* * Element Access @@ -103,14 +104,40 @@ class bit_array { * Operations */ constexpr void fill(value_type bit_val) noexcept; - constexpr void swap(bit_array& other) noexcept; + constexpr void swap(bit_array& other) noexcept; //constexpr std::synth-three-way-result operator<=>() const noexcept; }; -// Non-member swap to allow ADL. -template -void swap(dynarray& a, dynarray& b) noexcept { - a.swap(b); +constexpr bit_array::bit_array(size_type size) + : storage(std::make_unique(Words(size))), + m_size(size) { +} + +constexpr bit_array::bit_array(size_type size, value_type bit_val) + : storage(std::make_unique(Words(size))), + m_size(size) { + fill(bit_val); +} + +constexpr bit_array::bit_array(const std::initializer_list init) + : storage(std::make_unique(Words(init.size()))), + m_size(init.size()) { + std::copy(init.begin(), init.end(), this->begin()); +} + +#if 0 +No known conversion from bool to bit_value +bit_value has explicit constructor from bool to bit_value so this doesnt work +constexpr bit_array::bit_array(const std::initializer_list init) + : storage(std::make_unique(Words(init.size()))), + m_size(init.size()) { + std::copy(init.begin(), init.end(), this->begin()); +} +#endif + +constexpr bit_array::bit_array(const std::initializer_list init) + : storage(std::make_unique(init.size())), + m_size(bitsof() * init.size()) { } constexpr bool bit_array::operator==(const bit_array& other) const noexcept { @@ -119,6 +146,15 @@ constexpr bool bit_array::operator==(const bit_array::swap(bit_array& other) noexcept { std::swap(this->storage, other.storage); + std::swap(this->m_size, other.m_size); +} + +constexpr void bit_array::fill(value_type bit_val) noexcept { + if (bit_val) { + std::fill(&storage[0], &storage[Words(m_size)], bit_array::WordType(-1)); + } else { + std::fill(&storage[0], &storage[Words(m_size)], bit_array::WordType(0)); + } } // -------------------------------------------------------------------------- // diff --git a/include/bitlib/bit-containers/bit_bitsof.hpp b/include/bitlib/bit-containers/bit_bitsof.hpp index 2f7fc88f..62a3bdbf 100644 --- a/include/bitlib/bit-containers/bit_bitsof.hpp +++ b/include/bitlib/bit-containers/bit_bitsof.hpp @@ -1,6 +1,8 @@ #ifndef _BIT_BITSOF_HPP_INCLUDED #define _BIT_BITSOF_HPP_INCLUDED +#include + /* A bitsof() template function that resembles sizeof() as much as possible. Possible calling styles: @@ -19,22 +21,29 @@ class bit_array; // Helper trait to detect bit_array types template -struct is_bit_array : std::false_type {}; +struct is_bit_fixed : std::false_type {}; template -struct is_bit_array> : std::true_type {}; +struct is_bit_fixed> : std::true_type {}; + +template <> +struct is_bit_fixed> : std::false_type {}; template -struct is_bit_vector : std::false_type {}; +struct is_bit_dynamic : std::false_type {}; + +template <> +struct is_bit_dynamic> : std::true_type {}; template -struct is_bit_vector> : std::true_type {}; +struct is_bit_dynamic> : std::true_type {}; // General case: for types that are not bit_array, bit_vector, or bit_value. template ::value && - !std::is_same::value && - !is_bit_vector::value, int>::type = 0> + typename std::enable_if::value && + !std::is_same::value && + !is_bit_dynamic::value, + int>::type = 0> constexpr std::size_t bitsof() noexcept { return 8u * sizeof(T); } @@ -48,21 +57,22 @@ constexpr std::size_t bitsof() noexcept { // Overload for bit_array types. template ::value, int>::type = 0> + typename std::enable_if::value, int>::type = 0> constexpr std::size_t bitsof() noexcept { return T::bits; } +#if 0 // Overload for bit_vector template ::value, int>::type = 0> + typename std::enable_if::value, int>::type = 0> std::size_t bitsof(const T& v) noexcept { return v.size(); } +#endif -template ::value, int>::type = 0> -constexpr std::size_t bitsof(const T&) noexcept {return bitsof();} - +template ::value, int>::type = 0> +constexpr std::size_t bitsof(const T&) noexcept { return bitsof(); } } #endif diff --git a/test/src/test-array.cpp b/test/src/test-array.cpp index 8fdbb4e1..c889ce81 100644 --- a/test/src/test-array.cpp +++ b/test/src/test-array.cpp @@ -2,7 +2,7 @@ #include #include "bitlib/bit-containers/bit_array.hpp" - +#include "bitlib/bit-containers/bit_array_dynamic_extent.hpp" #include "fixtures.hpp" // Third-party libraries diff --git a/test/src/vector_test.cpp b/test/src/vector_test.cpp index 4530b5ff..0d714f54 100644 --- a/test/src/vector_test.cpp +++ b/test/src/vector_test.cpp @@ -12,7 +12,6 @@ #include // Project sources #include "bitlib/bit-containers/bit-containers.hpp" -#include "bitlib/bit-containers/bit_bitsof.hpp" #include "fixtures.hpp" // Third-party libraries #include "gtest/gtest.h" @@ -29,27 +28,26 @@ TYPED_TEST(VectorTest, DefaultConstructor) { // Tests the size c'tor. TYPED_TEST(VectorTest, SizeInitializerConstructor) { - EXPECT_EQ(18, this->v2_.size()); - EXPECT_EQ(18, bitsof(this->v2_)); - for (auto bv: this->v2_) { - EXPECT_FALSE(bv); - } - using WordType = typename TestFixture::base_type; - using vec_type = typename TestFixture::vec_type; - for (unsigned int veclen = 0; veclen < 4*this->digits; veclen++) { - this->empty_vec = vec_type(veclen); - for (unsigned int i = 0; i < veclen; ++i) { - EXPECT_FALSE(this->empty_vec[i]); - } - this->empty_vec = bit::bit_vector(veclen); - unsigned int i = 0; - // TODO misplaced test for range-based for loop - for (auto bv: this->empty_vec) { - EXPECT_FALSE(bv); - i++; - } - EXPECT_EQ(i, veclen); - } + EXPECT_EQ(18, this->v2_.size()); + for (auto bv : this->v2_) { + EXPECT_FALSE(bv); + } + using WordType = typename TestFixture::base_type; + using vec_type = typename TestFixture::vec_type; + for (unsigned int veclen = 0; veclen < 4 * this->digits; veclen++) { + this->empty_vec = vec_type(veclen); + for (unsigned int i = 0; i < veclen; ++i) { + EXPECT_FALSE(this->empty_vec[i]); + } + this->empty_vec = bit::bit_vector(veclen); + unsigned int i = 0; + // TODO misplaced test for range-based for loop + for (auto bv : this->empty_vec) { + EXPECT_FALSE(bv); + i++; + } + EXPECT_EQ(i, veclen); + } } TYPED_TEST(VectorTest, CountValueConstructor) { From cc850dacd446ab869c0e324d7eae23468d32a9ac Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Wed, 5 Mar 2025 07:44:23 -0500 Subject: [PATCH 09/85] default to dynamic extent --- include/bitlib/bit-containers/bit_array.hpp | 3 ++- test/src/test-array.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index 152ad6d0..487fa810 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -21,6 +21,7 @@ #include #include #include +#include // Project sources #include "bitlib/bit-iterator/bit.hpp" #include "bitlib/bit-algorithms/bit_algorithm.hpp" @@ -29,7 +30,7 @@ namespace bit { // ========================================================================== // -template +template class bit_array { public: static constexpr std::size_t bits = N; diff --git a/test/src/test-array.cpp b/test/src/test-array.cpp index c889ce81..5efc3f2b 100644 --- a/test/src/test-array.cpp +++ b/test/src/test-array.cpp @@ -39,7 +39,7 @@ TEST(ArrayTest, BasicIteration) { // <-- LSB, apparently šŸ™„ bit::bit_array<11> barr("0110_0101_110"); int i=0; - for(auto bbit : barr) { + for(const auto& bbit : barr) { switch(10 - i++) { case 0: EXPECT_EQ(bit::bit0, bbit); break; case 1: EXPECT_EQ(bit::bit1, bbit); break; From 4af3c5ce3a9f5f20a9eda0579babdba204b736e2 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Wed, 5 Mar 2025 14:53:58 -0500 Subject: [PATCH 10/85] Change bitsof to use concepts --- include/bitlib/bit-containers/bit_bitsof.hpp | 58 +++++--------------- include/bitlib/bit-iterator/bit_value.hpp | 1 + 2 files changed, 16 insertions(+), 43 deletions(-) diff --git a/include/bitlib/bit-containers/bit_bitsof.hpp b/include/bitlib/bit-containers/bit_bitsof.hpp index 62a3bdbf..c74c1573 100644 --- a/include/bitlib/bit-containers/bit_bitsof.hpp +++ b/include/bitlib/bit-containers/bit_bitsof.hpp @@ -2,6 +2,7 @@ #define _BIT_BITSOF_HPP_INCLUDED #include +#include /* A bitsof() template function that resembles sizeof() as much as possible. @@ -19,60 +20,31 @@ namespace bit { template class bit_array; -// Helper trait to detect bit_array types -template -struct is_bit_fixed : std::false_type {}; - -template -struct is_bit_fixed> : std::true_type {}; - -template <> -struct is_bit_fixed> : std::false_type {}; - -template -struct is_bit_dynamic : std::false_type {}; - -template <> -struct is_bit_dynamic> : std::true_type {}; +// Concept to check for a public static constexpr size_t bits member. +template +concept HasBits = requires { + { T::bits } -> std::convertible_to; +}; -template -struct is_bit_dynamic> : std::true_type {}; -// General case: for types that are not bit_array, bit_vector, or bit_value. -template ::value && - !std::is_same::value && - !is_bit_dynamic::value, - int>::type = 0> +// General case: for types that do not have a bits member. +template requires (!HasBits) constexpr std::size_t bitsof() noexcept { return 8u * sizeof(T); } -// Overload for bit_value. -template ::value, int>::type = 0> -constexpr std::size_t bitsof() noexcept { - return 1; -} - -// Overload for bit_array types. -template ::value, int>::type = 0> +// Overload for types with a bits member. +template requires HasBits constexpr std::size_t bitsof() noexcept { return T::bits; } -#if 0 -// Overload for bit_vector -template ::value, int>::type = 0> -std::size_t bitsof(const T& v) noexcept { - return v.size(); +// Overload for objects of any non-bits type. +template +constexpr std::size_t bitsof(const T&) noexcept{ + return bitsof(); } -#endif -template ::value, int>::type = 0> -constexpr std::size_t bitsof(const T&) noexcept { return bitsof(); } -} +} // namespace bit #endif diff --git a/include/bitlib/bit-iterator/bit_value.hpp b/include/bitlib/bit-iterator/bit_value.hpp index 0fd6ee42..f1c1ca11 100644 --- a/include/bitlib/bit-iterator/bit_value.hpp +++ b/include/bitlib/bit-iterator/bit_value.hpp @@ -32,6 +32,7 @@ class bit_value // Types public: + static constexpr std::size_t bits = 1; using size_type = std::size_t; // Lifecycle From d48445f57369e8f3ab18c07bfc09c2714ba67afa Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Wed, 5 Mar 2025 15:24:36 -0500 Subject: [PATCH 11/85] Add basic tests and clean up constructors --- .../bit_array_dynamic_extent.hpp | 70 +++++-- test/src/test-array.cpp | 197 ++++++++++++++++++ 2 files changed, 254 insertions(+), 13 deletions(-) diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index 2afe56c5..22ffcc80 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -39,7 +39,6 @@ class bit_array { using const_iterator = bit_iterator; private: - size_type m_words; size_type m_size; std::unique_ptr storage; static constexpr std::size_t Words(std::size_t N) { return (N * bitsof() + bitsof() - 1) / bitsof(); }; @@ -51,8 +50,8 @@ class bit_array { constexpr bit_array() noexcept; constexpr bit_array(size_type size); constexpr bit_array(size_type size, value_type bit_val); - constexpr bit_array(const bit_array& other) = default; - constexpr bit_array(const bit_array&& other) noexcept; + constexpr bit_array(const bit_array& other); + constexpr bit_array(const bit_array&& other); constexpr bit_array(const std::initializer_list init); #if 0 constexpr bit_array(const std::initializer_list init); @@ -64,8 +63,8 @@ class bit_array { /* * Assignment */ - constexpr bit_array& operator=(const bit_array& other) = default; - constexpr bit_array& operator=(bit_array&& other) noexcept; + constexpr bit_array& operator=(const bit_array& other); + constexpr bit_array& operator=(bit_array&& other); constexpr bool operator==(const bit_array& other) const noexcept; @@ -108,20 +107,36 @@ class bit_array { //constexpr std::synth-three-way-result operator<=>() const noexcept; }; +constexpr bit_array::bit_array() noexcept + : m_size(0), storage(std::unique_ptr(nullptr)) { +} + constexpr bit_array::bit_array(size_type size) - : storage(std::make_unique(Words(size))), - m_size(size) { + : m_size(size), + storage(std::make_unique(Words(size))) { } constexpr bit_array::bit_array(size_type size, value_type bit_val) - : storage(std::make_unique(Words(size))), - m_size(size) { + : m_size(size), + storage(std::make_unique(Words(size))) { fill(bit_val); } +constexpr bit_array::bit_array(const bit_array& other) + : m_size(other.size()), + storage(std::make_unique(Words(m_size))) { + std::copy(other.begin(), other.end(), this->begin()); +} + +constexpr bit_array::bit_array(const bit_array&& other) + : m_size(other.size()), + storage(std::make_unique(Words(m_size))) { + std::copy(other.begin(), other.end(), this->begin()); +} + constexpr bit_array::bit_array(const std::initializer_list init) - : storage(std::make_unique(Words(init.size()))), - m_size(init.size()) { + : m_size(init.size()), + storage(std::make_unique(Words(Words(m_size)))) { std::copy(init.begin(), init.end(), this->begin()); } @@ -136,8 +151,37 @@ constexpr bit_array::bit_array(const std::initializer_list< #endif constexpr bit_array::bit_array(const std::initializer_list init) - : storage(std::make_unique(init.size())), - m_size(bitsof() * init.size()) { + : m_size(bitsof() * init.size()), + storage(std::make_unique(init.size())) { + std::copy(init.begin(), init.end(), &storage[0]); +} + +constexpr bit_array::bit_array(const std::string_view s) + : m_size((std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))), + storage(std::make_unique(Words(m_size))) { + size_type i = 0; + for (char c : s) { + if (c == '0') { + begin()[i++] = bit0; + } else if (c == '1') { + begin()[i++] = bit1; + } + } +} + +constexpr bit_array& bit_array::operator=(const bit_array& other) { + if (nullptr == storage.get() || m_size != other.m_size) { + throw std::invalid_argument("Cannot reassign bit_array size"); + } + std::copy(other.begin(), other.end(), this->begin()); + return *this; +} +constexpr bit_array& bit_array::operator=(bit_array&& other) { + if (nullptr == storage.get() || m_size != other.m_size) { + throw std::invalid_argument("Cannot reassign bit_array size"); + } + std::copy(other.begin(), other.end(), this->begin()); + return *this; } constexpr bool bit_array::operator==(const bit_array& other) const noexcept { diff --git a/test/src/test-array.cpp b/test/src/test-array.cpp index 5efc3f2b..53f45425 100644 --- a/test/src/test-array.cpp +++ b/test/src/test-array.cpp @@ -209,3 +209,200 @@ TEST(ArrayTest, MoveSemantics) { EXPECT_FALSE(ba2[3]); EXPECT_TRUE(ba2[4]); } + +// +// Test Suite for bit::bit_array<> +// + +// As bit_array<> is dynamic, bitsof will not return the number stored bits +// the class contains a size_t and a pointer +TEST(BitArrayDynamicTest, Bitsof) { + bit::bit_array<> arr(23, bit::bit1); + EXPECT_EQ(bitsof(arr), 8 * sizeof(arr)); + EXPECT_EQ(arr.size(), 23); +} + +TEST(BitArrayDynamicTest, DefaultConstructorCreatesEmptyArray) { + bit::bit_array<> arr; + EXPECT_TRUE(arr.empty()); + EXPECT_EQ(arr.size(), 0u); + EXPECT_EQ(arr.begin(), arr.end()); +} + +TEST(BitArrayDynamicTest, SizeConstructorCreatesArrayOfGivenSize) { + const std::size_t size = 10; + bit::bit_array<> arr(size); + EXPECT_EQ(arr.size(), size); + EXPECT_FALSE(arr.empty()); +} + +TEST(BitArrayDynamicTest, SizeAndValueConstructorInitializesCorrectly) { + const std::size_t size = 16; + // Initialize all bits to true. + bit::bit_array<> arr(size, bit::bit_value(true)); + for (std::size_t i = 0; i < size; ++i) { + EXPECT_EQ(arr[i], bit::bit_value(true)); + } +} + +TEST(BitArrayDynamicTest, CopyConstructorCopiesContent) { + const std::size_t size = 8; + bit::bit_array<> original(size, bit::bit_value(true)); + bit::bit_array<> copy(original); + EXPECT_TRUE(copy == original); +} + +TEST(BitArrayDynamicTest, MoveConstructorMovesContent) { + const std::size_t size = 8; + bit::bit_array<> original(size, bit::bit_value(true)); + bit::bit_array<> moved(std::move(original)); + // Check that moved now contains the expected content. + EXPECT_EQ(moved.size(), size); + for (std::size_t i = 0; i < size; ++i) { + EXPECT_EQ(moved[i], bit::bit_value(true)); + } + // The state of original is valid but unspecified. +} + +TEST(BitArrayDynamicTest, InitializerListBitValueConstructorWorks) { + std::initializer_list init = {bit::bit0, bit::bit1, bit::bit0}; + bit::bit_array<> arr(init); + EXPECT_EQ(arr.size(), init.size()); + auto it = arr.begin(); + for (bit::bit_value expected : init) { + EXPECT_EQ(*it, expected); + ++it; + } +} + +TEST(BitArrayDynamicTest, InitializerListWordTypeConstructorWorks) { + // For this test, we assume that the initializer list for WordType initializes the underlying storage. + // Here we use two bytes as an example. + std::initializer_list init = {0b10101010, 0b01010101}; + bit::bit_array<> arr(init); + // Assuming each std::uint8_t provides 8 bits, we expect the size to be the number of initializer elements * 8. + EXPECT_EQ(arr.size(), init.size() * 8u); + // Check that the underlying storage matches the initializer values. + const std::uint8_t* data = arr.data(); + auto wordIt = init.begin(); + for (std::size_t i = 0; i < init.size(); ++i) { + EXPECT_EQ(data[i], *(wordIt++)); + } +} + +TEST(BitArrayDynamicTest, StringViewConstructorWorks) { + // Assuming the string_view constructor interprets a string of '0' and '1' characters. + std::string_view s = "1011001"; + bit::bit_array<> arr(s); + EXPECT_EQ(arr.size(), s.size()); + for (std::size_t i = 0; i < s.size(); ++i) { + // Interpret '1' as true and '0' as false. + bool expected = (s[i] == '1'); + EXPECT_EQ(arr[i], bit::bit_value(expected)); + } +} + +TEST(BitArrayDynamicTest, ElementAccessAtAndBracketConsistency) { + const std::size_t size = 5; + bit::bit_array<> arr(size, bit::bit_value(false)); + // Set a value using operator[] and check with at(). + arr[2] = bit::bit_value(true); + EXPECT_EQ(arr.at(2), bit::bit_value(true)); + EXPECT_EQ(arr[2], bit::bit_value(true)); + + // Test front() and back(). + arr[0] = bit::bit_value(true); + arr[size - 1] = bit::bit_value(true); + EXPECT_EQ(arr.front(), bit::bit_value(true)); + EXPECT_EQ(arr.back(), bit::bit_value(true)); +} + +TEST(BitArrayDynamicTest, IteratorTraversal) { + const std::size_t size = 10; + bit::bit_array<> arr(size, bit::bit_value(false)); + // Set alternate bits to true. + for (std::size_t i = 0; i < size; ++i) { + arr[i] = bit::bit_value(i % 2 == 0); + } + std::size_t index = 0; + for (auto it = arr.begin(); it != arr.end(); ++it, ++index) { + bool expected = (index % 2 == 0); + EXPECT_EQ(*it, bit::bit_value(expected)); + } +} + +TEST(BitArrayDynamicTest, CapacityFunctions) { + bit::bit_array<> arr; + EXPECT_TRUE(arr.empty()); + EXPECT_EQ(arr.size(), 0u); + // max_size() is expected to be >= size. + EXPECT_GE(arr.max_size(), arr.size()); +} + +TEST(BitArrayDynamicTest, FillOperationSetsAllBits) { + const std::size_t size = 20; + bit::bit_array<> arr(size, bit::bit_value(false)); + arr.fill(bit::bit_value(true)); + for (std::size_t i = 0; i < size; ++i) { + EXPECT_EQ(arr[i], bit::bit_value(true)); + } +} + +TEST(BitArrayDynamicTest, SwapOperationSwapsContents) { + const std::size_t size1 = 5; + const std::size_t size2 = 8; + bit::bit_array<> arr1(size1, bit::bit_value(true)); + bit::bit_array<> arr2(size2, bit::bit_value(false)); + + arr1.swap(arr2); + + EXPECT_EQ(arr1.size(), size2); + EXPECT_EQ(arr2.size(), size1); + + for (std::size_t i = 0; i < arr1.size(); ++i) { + EXPECT_EQ(arr1[i], bit::bit_value(false)); + } + for (std::size_t i = 0; i < arr2.size(); ++i) { + EXPECT_EQ(arr2[i], bit::bit_value(true)); + } +} + +TEST(BitArrayDynamicTest, AssignmentOperatorCopiesContent) { + const std::size_t size = 10; + bit::bit_array<> arr1(size, bit::bit_value(true)); + bit::bit_array<> arr2(size); + arr2 = arr1; + EXPECT_TRUE(arr2 == arr1); +} + +TEST(BitArrayDynamicTest, MoveAssignmentOperatorMovesContent) { + const std::size_t size = 10; + bit::bit_array<> arr1(size, bit::bit_value(true)); + bit::bit_array<> arr2(size); + arr2 = std::move(arr1); + EXPECT_EQ(arr2.size(), size); + for (std::size_t i = 0; i < size; ++i) { + EXPECT_EQ(arr2[i], bit::bit_value(true)); + } + // The state of arr1 is valid but unspecified. +} + +struct StructOfBitArrays { + bit::bit_array<> arr1; + bit::bit_array<> arr2; + bit::bit_array<> arr3; + StructOfBitArrays() = delete; + StructOfBitArrays(size_t s1, size_t s2, size_t s3) + : arr1(s1), arr2(s2), arr3(s3) { + } +}; + +TEST(BitArrayDynamicTest, StructOfBitArray) { + size_t s1 = rand(); + size_t s2 = rand(); + size_t s3 = rand(); + StructOfBitArrays s(s1, s2, s3); + EXPECT_EQ(s.arr1.size(), s1); + EXPECT_EQ(s.arr2.size(), s2); + EXPECT_EQ(s.arr3.size(), s3); +} \ No newline at end of file From 4622fba025c1824a94df4b0d40d0866045c47ccf Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sat, 8 Mar 2025 09:40:31 -0500 Subject: [PATCH 12/85] Add ctad for bit span --- include/bitlib/bit-containers/bit_span.hpp | 42 ++++++++++++++++++++++ test/src/test-span.cpp | 26 ++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/include/bitlib/bit-containers/bit_span.hpp b/include/bitlib/bit-containers/bit_span.hpp index 3b154d63..4bc58d2f 100644 --- a/include/bitlib/bit-containers/bit_span.hpp +++ b/include/bitlib/bit-containers/bit_span.hpp @@ -14,6 +14,9 @@ namespace bit { +template +concept Scalar = std::is_scalar_v; + // Helper storage: for a fixed extent no runtime size is stored. template struct bit_span_storage { @@ -72,11 +75,19 @@ class bit_span : private bit_span_storage { // Construct from a WordType pointer and a bit count. constexpr bit_span(WordType* word_ptr, size_type bit_count) noexcept requires(Extent == std::dynamic_extent); constexpr bit_span(WordType* word_ptr) noexcept; + constexpr bit_span(WordType& word_ref, size_type bit_count) noexcept + requires(Extent == std::dynamic_extent); + constexpr bit_span(WordType& word_ref) noexcept; // Construct from a iterator and a bit count. constexpr bit_span(iterator iter, size_type bit_count) noexcept requires(Extent == std::dynamic_extent); constexpr bit_span(iterator iter) noexcept; + constexpr bit_span(WordType& s) + requires(std::is_scalar_v); + constexpr bit_span(WordType* s) + requires(std::is_scalar_v); + // --- Observers --- // Returns the number of bits in the span. @@ -127,6 +138,21 @@ class bit_span : private bit_span_storage { constexpr bit_span last() const noexcept requires(RevOffset != std::dynamic_extent); constexpr bit_span last(size_type offset) const noexcept requires(Extent == std::dynamic_extent); }; +// Class Template Argument Deduction + +// CTAD guide for the constructor taking a WordType&, +// deducing Extent as bitsof(). +template +bit_span(WordType&) -> bit_span()>; +template +bit_span(WordType*) -> bit_span()>; + +// CTAD guide for the constructor taking a WordType* and a size, +// which should always be used when Extent is std::dynamic_extent. +template +bit_span(WordType&, std::size_t) -> bit_span; +template +bit_span(WordType*, std::size_t) -> bit_span; // --- Constructors --- @@ -145,6 +171,11 @@ constexpr bit_span::bit_span(pointer data, size_type bit_count) template constexpr bit_span::bit_span(WordType* word_ptr, size_type bit_count) noexcept requires(Extent == std::dynamic_extent) : data_(pointer(word_ptr, 0)), bit_span_storage(bit_count) { } +template +constexpr bit_span::bit_span(WordType& word_ref, size_type bit_count) noexcept + requires(Extent == std::dynamic_extent) + : bit_span(&word_ref, bit_count) {} + template constexpr bit_span::bit_span(iterator iter, size_type bit_count) noexcept requires(Extent == std::dynamic_extent) : data_(&(*iter)), bit_span_storage(bit_count) { } @@ -159,6 +190,17 @@ constexpr bit_span::bit_span(WordType* word_ptr) noexcept : dat template constexpr bit_span::bit_span(iterator iter) noexcept : data_(&(*iter)), bit_span_storage() { } +template +constexpr bit_span::bit_span(WordType* word_ptr) + requires(std::is_scalar_v) + : data_(word_ptr), bit_span_storage() { +} + +template +constexpr bit_span::bit_span(WordType& word_ref) + requires(std::is_scalar_v) + : bit_span(&word_ref) { +} // --- Observers --- diff --git a/test/src/test-span.cpp b/test/src/test-span.cpp index 0935b341..6cc16781 100644 --- a/test/src/test-span.cpp +++ b/test/src/test-span.cpp @@ -30,6 +30,32 @@ TEST(BitSpanTest, ConstructorFromBitPointerDynamic) { EXPECT_FALSE(span.empty()); } +TEST(BitSpanTest, ConstructorDeductionWordType) { + uint32_t word = 0xDEADBEEF; + bit::bit_span span(word); + EXPECT_EQ(span.size(), 32); + //0xF + EXPECT_EQ(span[0], bit::bit1); + EXPECT_EQ(span[1], bit::bit1); + EXPECT_EQ(span[2], bit::bit1); + EXPECT_EQ(span[3], bit::bit1); + //0xE + EXPECT_EQ(span[4], bit::bit0); + EXPECT_EQ(span[5], bit::bit1); + EXPECT_EQ(span[6], bit::bit1); + EXPECT_EQ(span[7], bit::bit1); + //0xE + EXPECT_EQ(span[8], bit::bit0); + EXPECT_EQ(span[9], bit::bit1); + EXPECT_EQ(span[10], bit::bit1); + EXPECT_EQ(span[11], bit::bit1); + //0xB + EXPECT_EQ(span[12], bit::bit1); + EXPECT_EQ(span[13], bit::bit1); + EXPECT_EQ(span[14], bit::bit0); + EXPECT_EQ(span[15], bit::bit1); +} + TEST(BitSpanTest, ConstructorFromWordPointerDynamic) { WordType word = 0; // One word gives bit::bitsof() bits. From bc148be18ef1ab6f2c89088792b2c62ebd733585 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 14 Mar 2025 19:39:50 -0400 Subject: [PATCH 13/85] Drafting concepts to help compatibility between different bit iterators? --- include/bitlib/bit-algorithms/copy.hpp | 2 +- .../bitlib/bit-algorithms/copy_backward.hpp | 2 +- include/bitlib/bit-algorithms/equal.hpp | 2 +- include/bitlib/bit-algorithms/move.hpp | 2 +- .../bitlib/bit-containers/bit-containers.hpp | 3 + include/bitlib/bit-containers/bit_span.hpp | 7 +- include/bitlib/bit-containers/bit_vector.hpp | 15 +- include/bitlib/bit-iterator/bit_details.hpp | 34 +- include/bitlib/bit-iterator/bit_iterator.hpp | 325 ++++++++---------- test/src/test-usecase.cpp | 12 + 10 files changed, 218 insertions(+), 186 deletions(-) create mode 100644 test/src/test-usecase.cpp diff --git a/include/bitlib/bit-algorithms/copy.hpp b/include/bitlib/bit-algorithms/copy.hpp index 2d507516..b5761f25 100644 --- a/include/bitlib/bit-algorithms/copy.hpp +++ b/include/bitlib/bit-algorithms/copy.hpp @@ -16,7 +16,7 @@ #include #include // Project sources -#include "bitlib/bitlib.hpp" +#include "bitlib/bit-iterator/bit.hpp" // Third-party libraries // Miscellaneous namespace bit { diff --git a/include/bitlib/bit-algorithms/copy_backward.hpp b/include/bitlib/bit-algorithms/copy_backward.hpp index 55bd6f69..67c071f0 100644 --- a/include/bitlib/bit-algorithms/copy_backward.hpp +++ b/include/bitlib/bit-algorithms/copy_backward.hpp @@ -16,7 +16,7 @@ #include #include // Project sources -#include "bitlib/bitlib.hpp" +#include "bitlib/bit-iterator/bit.hpp" // Third-party libraries // Miscellaneous namespace bit { diff --git a/include/bitlib/bit-algorithms/equal.hpp b/include/bitlib/bit-algorithms/equal.hpp index e3fb1449..004c12bf 100644 --- a/include/bitlib/bit-algorithms/equal.hpp +++ b/include/bitlib/bit-algorithms/equal.hpp @@ -15,7 +15,7 @@ #include #include // Project sources -#include "bitlib/bitlib.hpp" +#include "bitlib/bit-iterator/bit.hpp" // Third-party libraries // Miscellaneous namespace bit { diff --git a/include/bitlib/bit-algorithms/move.hpp b/include/bitlib/bit-algorithms/move.hpp index 401cbfdb..0f0e09e4 100644 --- a/include/bitlib/bit-algorithms/move.hpp +++ b/include/bitlib/bit-algorithms/move.hpp @@ -14,7 +14,7 @@ #include #include // Project sources -#include "bitlib/bitlib.hpp" +#include "bitlib/bit-iterator/bit.hpp" // Third-party libraries // Miscellaneous diff --git a/include/bitlib/bit-containers/bit-containers.hpp b/include/bitlib/bit-containers/bit-containers.hpp index bbbd540a..fd885d7f 100644 --- a/include/bitlib/bit-containers/bit-containers.hpp +++ b/include/bitlib/bit-containers/bit-containers.hpp @@ -7,6 +7,9 @@ #ifndef _BIT_CONTAINERS_HPP_INCLUDED #define _BIT_CONTAINERS_HPP_INCLUDED // ============================== PREAMBLE ================================== // +#include "bit_array.hpp" +#include "bit_array_dynamic_extent.hpp" +#include "bit_span.hpp" #include "bit_vector.hpp" // ========================================================================== // #endif diff --git a/include/bitlib/bit-containers/bit_span.hpp b/include/bitlib/bit-containers/bit_span.hpp index 4bc58d2f..319936ff 100644 --- a/include/bitlib/bit-containers/bit_span.hpp +++ b/include/bitlib/bit-containers/bit_span.hpp @@ -1,3 +1,5 @@ +#ifndef _BIT_SPAN_HPP_INCLUDED +#define _BIT_SPAN_HPP_INCLUDED #include #include @@ -44,9 +46,9 @@ class bit_span : private bit_span_storage { using pointer = bit_pointer; using const_pointer = const pointer; using iterator = bit_iterator::iterator>; - using const_iterator = bit_iterator::const_iterator>; + using const_iterator = bit_iterator; -private: + private: // The start of the span, represented as a bit_pointer. pointer data_; @@ -312,3 +314,4 @@ constexpr bit_span bit_span::la } } // namespace bit +#endif diff --git a/include/bitlib/bit-containers/bit_vector.hpp b/include/bitlib/bit-containers/bit_vector.hpp index f971c494..379ff9fd 100644 --- a/include/bitlib/bit-containers/bit_vector.hpp +++ b/include/bitlib/bit-containers/bit_vector.hpp @@ -182,7 +182,10 @@ class bit_vector { constexpr void pop_back(); constexpr void resize(size_type count); constexpr void resize(size_type count, const value_type& value); - + template + constexpr iterator insert_range(const_iterator pos, R&& range); + template + constexpr void append_range(R&& range); /* * Helper functions @@ -627,7 +630,17 @@ constexpr void bit_vector::resize(size_type count, const va } // -------------------------------------------------------------------------- // +template +template +constexpr bit_vector::iterator bit_vector::insert_range(const_iterator pos, R&& range) { + return this->insert(pos, range.begin(), range.end()); +} +template +template +constexpr void bit_vector::append_range(R&& range) { + this->insert(this->end(), range.begin(), range.end()); +} // ------------------------ BIT VECTOR: DEBUGGING -------------------------- // template diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index cb61a814..8f664d86 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -15,16 +15,19 @@ // ================================ PREAMBLE ================================ // // C++ standard library -#include -#include +#include #include +#include +#include #include -#include #include -#include +#include +#include #include +#include #include -#include +#include + // Project sources // Third-party libraries // Miscellaneous @@ -33,9 +36,28 @@ class bit_value; template class bit_reference; template class bit_pointer; template class bit_iterator; -// ========================================================================== // +// ========================================================================== // +template +concept bit_range = + std::ranges::range && + std::convertible_to, bit_value>; + +template +concept bit_contiguous_range = bit_range && + std::contiguous_iterator::iterator_type> && + std::has_unique_object_representations_v::word_type>; + +template +concept bit_sized_range = + bit_range && + std::ranges::sized_range; + +template +concept bit_contiguous_sized_range = + bit_contiguous_range && + std::ranges::sized_range; /* ***************************** BINARY DIGITS ****************************** */ // Binary digits structure definition diff --git a/include/bitlib/bit-iterator/bit_iterator.hpp b/include/bitlib/bit-iterator/bit_iterator.hpp index 63b84fe7..f508d652 100644 --- a/include/bitlib/bit-iterator/bit_iterator.hpp +++ b/include/bitlib/bit-iterator/bit_iterator.hpp @@ -14,125 +14,156 @@ // ================================ PREAMBLE ================================ // // C++ standard library +#include +#include +#include +#include +#include + // Project sources #include "bit_details.hpp" // Third-party libraries // Miscellaneous namespace bit { // ========================================================================== // +template +concept reference_like = + std::copyable && // Copyable (no move-only weirdness) + std::convertible_to && // Can be converted to the value type + std::assignable_from; // Supports assignment from value type + +template +concept bit_iterator_c = requires(It it, It it2, + typename It::difference_type n, + typename It::iterator_type i, + typename It::pointer ptr) { + // Required member types + typename It::iterator_type; + typename It::word_type; + typename It::iterator_category; + typename It::value_type; + requires std::same_as; + typename It::difference_type; // should be std::ptrdiff_t + typename It::pointer; // should be bit_pointer + typename It::reference; // should be bit_reference + typename It::size_type; // should be std::size_t + + // Lifecycle requirements + { It() } -> std::same_as; // default-constructible + { It(it) } -> std::same_as; // copy constructible + { It(i) } -> std::same_as; // constructible from iterator_type + { It(i, static_cast(0)) } -> std::same_as; + { It(ptr) } -> std::same_as; + + { it2 - it } -> std::same_as; + + // Assignment + { it = it2 } -> std::same_as; + + // Element access + { *it } -> std::same_as; + { it.operator->() } -> std::same_as; + { it[n] } -> std::same_as; + + // Increment and decrement operations + { ++it } -> std::same_as; + { --it } -> std::same_as; + { it++ } -> std::same_as; + { it-- } -> std::same_as; + { it + n } -> std::same_as; + { it - n } -> std::same_as; + { it += n } -> std::same_as; + { it -= n } -> std::same_as; + + // Underlying details + { it.base() } -> std::same_as; + { it.position() } -> std::same_as; + { it.mask() } -> std::same_as>; + + // Spaceship operator requirement + //{ it <=> it2 } -> std::convertible_to; +}; - +template +concept bit_contiguous_iterator = bit_iterator_c && + std::contiguous_iterator && + std::has_unique_object_representations_v; /* ****************************** BIT ITERATOR ****************************** */ // Bit iterator class definition template class bit_iterator { - // Assertions - private: - using _traits_t = _cv_iterator_traits; - static_assert(binary_digits::value, ""); - - // Types - public: - using iterator_type = Iterator; - using word_type = typename _traits_t::value_type; - using iterator_category = typename _traits_t::iterator_category; - using value_type = bit_value; - using difference_type = std::ptrdiff_t; - using pointer = bit_pointer; - using reference = bit_reference; - using size_type = std::size_t; - - // Lifecycle - public: - constexpr bit_iterator(); - template - constexpr bit_iterator(const bit_iterator& other); - explicit constexpr bit_iterator(iterator_type i); - constexpr bit_iterator(iterator_type i, size_type pos); - explicit constexpr bit_iterator(const pointer& ptr); - - // Assignment - public: - template - constexpr bit_iterator& operator=(const bit_iterator& other); - - // Access - public: - constexpr reference operator*() const noexcept; - constexpr pointer operator->() const noexcept; - constexpr reference operator[](difference_type n) const; - - // Increment and decrement operators - public: - constexpr bit_iterator& operator++(); - constexpr bit_iterator& operator--(); - constexpr bit_iterator operator++(int); - constexpr bit_iterator operator--(int); - constexpr bit_iterator operator+(difference_type n) const; - constexpr bit_iterator operator-(difference_type n) const; - constexpr bit_iterator& operator+=(difference_type n); - constexpr bit_iterator& operator-=(difference_type n); - - // Underlying details - public: - constexpr iterator_type base() const; - constexpr size_type position() const noexcept; - constexpr typename std::remove_cv::type mask() const noexcept; - - // Implementation details: data members - private: - iterator_type _current; - size_type _position; - - // Non-member arithmetic operators - template - friend constexpr bit_iterator operator+( - typename bit_iterator::difference_type n, - const bit_iterator& i - ); - template - friend constexpr typename std::common_type< - typename bit_iterator::difference_type, - typename bit_iterator::difference_type - >::type operator-( - const bit_iterator& lhs, - const bit_iterator& rhs - ); - - // Comparison operators - public: - template - friend constexpr bool operator==( - const bit_iterator& lhs, - const bit_iterator& rhs - ); - template - friend constexpr bool operator!=( - const bit_iterator& lhs, - const bit_iterator& rhs - ); - template - friend constexpr bool operator<( - const bit_iterator& lhs, - const bit_iterator& rhs - ); - template - friend constexpr bool operator<=( - const bit_iterator& lhs, - const bit_iterator& rhs - ); - template - friend constexpr bool operator>( - const bit_iterator& lhs, - const bit_iterator& rhs - ); - template - friend constexpr bool operator>=( - const bit_iterator& lhs, - const bit_iterator& rhs - ); + // Assertions + private: + using _traits_t = _cv_iterator_traits; + static_assert(binary_digits::value, ""); + + // Types + public: + using iterator_type = Iterator; + using word_type = typename _traits_t::value_type; + using iterator_category = typename _traits_t::iterator_category; + using value_type = bit_value; + using difference_type = std::ptrdiff_t; + using pointer = bit_pointer; + using reference = bit_reference; + using size_type = std::size_t; + + // Lifecycle + public: + constexpr bit_iterator(); + template + constexpr bit_iterator(const bit_iterator& other); + explicit constexpr bit_iterator(iterator_type i); + constexpr bit_iterator(iterator_type i, size_type pos); + explicit constexpr bit_iterator(const pointer& ptr); + + // Assignment + public: + template + constexpr bit_iterator& operator=(const bit_iterator& other); + + // Access + public: + constexpr reference operator*() const noexcept; + constexpr pointer operator->() const noexcept; + constexpr reference operator[](difference_type n) const; + + // Increment and decrement operators + public: + constexpr bit_iterator& operator++(); + constexpr bit_iterator& operator--(); + constexpr bit_iterator operator++(int); + constexpr bit_iterator operator--(int); + constexpr bit_iterator operator+(difference_type n) const; + constexpr bit_iterator operator-(difference_type n) const; + constexpr bit_iterator& operator+=(difference_type n); + constexpr bit_iterator& operator-=(difference_type n); + + // Underlying details + public: + constexpr iterator_type base() const; + constexpr size_type position() const noexcept; + constexpr typename std::remove_cv::type mask() const noexcept; + + // Implementation details: data members + private: + iterator_type _current; + size_type _position; + + // Non-member arithmetic operators + template + friend constexpr bit_iterator operator+( + typename bit_iterator::difference_type n, + const bit_iterator& i); + template + friend constexpr typename std::common_type< + typename bit_iterator::difference_type, + typename bit_iterator::difference_type>::type + operator-( + const bit_iterator& lhs, + const bit_iterator& rhs); }; /* ************************************************************************** */ @@ -417,77 +448,25 @@ constexpr typename std::common_type< const difference_type main = lhs._current - rhs._current; return main * digits + (lhs._position - rhs._position); } -// -------------------------------------------------------------------------- // - - -// ------------------- BIT ITERATOR: COMPARISON OPERATORS ------------------- // -// Checks if the left hand side is equal to the right hand side -template -constexpr bool operator==( - const bit_iterator& lhs, - const bit_iterator& rhs -) -{ - return lhs._current == rhs._current && lhs._position == rhs._position; +static_assert(bit_iterator_c>, "bit_iterator does not satisfy bit_iterator_c concept!"); + +// Define the three-way comparison operator for bit_iterator. +// This template works for bit_iterator and bit_iterator to allow mixed comparisons. +constexpr auto operator<=>( + const bit_iterator_c auto& lhs, + const bit_iterator_c auto& rhs) { + if (auto cmp = lhs.base() <=> rhs.base(); cmp != 0) { + return cmp; + } + return lhs.position() <=> rhs.position(); } -// Checks if the left hand side is non equal to the right hand side -template -constexpr bool operator!=( - const bit_iterator& lhs, - const bit_iterator& rhs -) -{ - return lhs._current != rhs._current || lhs._position != rhs._position; -} - -// Checks if the left hand side is less than the right hand side -template -constexpr bool operator<( - const bit_iterator& lhs, - const bit_iterator& rhs -) -{ - return lhs._current < rhs._current - || (lhs._current == rhs._current && lhs._position < rhs._position); -} - -// Checks if the left hand side is less than or equal to the right hand side -template -constexpr bool operator<=( - const bit_iterator& lhs, - const bit_iterator& rhs -) -{ - return lhs._current < rhs._current - || (lhs._current == rhs._current && lhs._position <= rhs._position); -} - -// Checks if the left hand side is greater than the right hand side -template -constexpr bool operator>( - const bit_iterator& lhs, - const bit_iterator& rhs -) -{ - return lhs._current > rhs._current - || (lhs._current == rhs._current && lhs._position > rhs._position); -} - -// Checks if the left hand side is greater than or equal to the right hand side -template -constexpr bool operator>=( - const bit_iterator& lhs, - const bit_iterator& rhs -) -{ - return lhs._current > rhs._current - || (lhs._current == rhs._current && lhs._position >= rhs._position); +constexpr bool operator==( + const bit_iterator_c auto& lhs, + const bit_iterator_c auto& rhs) { + return (lhs <=> rhs) == 0; // Uses the spaceship operator to determine equality } -// -------------------------------------------------------------------------- // - - // ========================================================================== // } // namespace bit diff --git a/test/src/test-usecase.cpp b/test/src/test-usecase.cpp new file mode 100644 index 00000000..072bcc0e --- /dev/null +++ b/test/src/test-usecase.cpp @@ -0,0 +1,12 @@ +#include "bitlib/bitlib.hpp" +// Third-party libraries +#include "gtest/gtest.h" + +/* +TEST(UseCaseTest, VectorAppend) { + bit::bit_vector vec; + vec.push_back(bit::bit0); + uint16_t test = 0xBEEF; + vec.append_range(bit::bit_span(test)); +} + */ \ No newline at end of file From 01126b9bcf1f6c92185db93ad71e3875dde75331 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sun, 23 Mar 2025 07:20:50 -0400 Subject: [PATCH 14/85] Consolidate concepts --- CMakeLists.txt | 17 +--- benchmark/CMakeLists.txt | 6 +- include/bitlib/bit-containers/bit_array.hpp | 7 ++ .../bit_array_dynamic_extent.hpp | 7 ++ include/bitlib/bit-containers/bit_vector.hpp | 9 +- include/bitlib/bit-iterator/bit_details.hpp | 22 +---- include/bitlib/bit-iterator/bit_iterator.hpp | 82 +--------------- include/bitlib/bit-iterator/bit_pointer.hpp | 2 + include/bitlib/bit-iterator/bit_reference.hpp | 1 + include/bitlib/bit_concepts.hpp | 96 +++++++++++++++++++ profile/CMakeLists.txt | 3 +- test/CMakeLists.txt | 2 +- 12 files changed, 134 insertions(+), 120 deletions(-) create mode 100644 include/bitlib/bit_concepts.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 10972e8e..d0abef6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ project(Bit-Vector VERSION 0.3.0) # set up linters/checkers #set(CMAKE_CXX_CPPCHECK cppcheck;--std=c++17;--file-filter=*bitlib*) #set(CMAKE_CXX_CPPLINT cpplint;--linelength=100;--filter=-whitespace;) -#set(CMAKE_CXX_CLANG_TIDY +#set(CMAKE_CXX_CLANG_TIDY #clang-tidy; #-header-filter=include/;) @@ -24,18 +24,9 @@ target_include_directories(bitlib INTERFACE target_compile_features(bitlib INTERFACE cxx_std_23) - -# specify global compiler flags -include_directories("include/" "utils/" ) - -# Add fmt library (useful for printing words in binary and other debugging stuff) -#include(FetchContent) -#FetchContent_Declare( - #fmt - #GIT_REPOSITORY https://github.com/fmtlib/fmt.git - #GIT_TAG e57ca2e3685b160617d3d95fcd9e789c4e06ca88 #v10.1.0 -#) -#FetchContent_MakeAvailable(fmt) +add_library(bitlib_utils INTERFACE) +target_include_directories(bitlib_utils INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/utils) +target_sources(bitlib_utils INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/utils/test_utils.hpp) option(BITLIB_HWY "Build with google highway SIMD extensions" OFF) diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 6870bea1..732c24b6 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -16,11 +16,11 @@ add_subdirectory(ext/dynamic_bitset) # specify benchmark-specific libraries include_directories( - ${googlebench_SOURCE_DIR}/benchmark/include + ${googlebench_SOURCE_DIR}/benchmark/include src/utils - ext/BitArray + ext/BitArray ext/itsy_bitsy/include) -target_link_libraries(bitlib-bench PRIVATE benchmark::benchmark -pthread ${CMAKE_CURRENT_LIST_DIR}/ext/BitArray/libbitarr.a sul::dynamic_bitset) +target_link_libraries(bitlib-bench PRIVATE bitlib_utils benchmark::benchmark -pthread ${CMAKE_CURRENT_LIST_DIR}/ext/BitArray/libbitarr.a sul::dynamic_bitset) target_compile_options(bitlib-bench PUBLIC -O3 -DNDEBUG -march=native -Wpedantic) install(TARGETS bitlib-bench DESTINATION .) diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index 487fa810..afe2ded8 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -113,6 +113,13 @@ constexpr void swap(bit_array& other) noexcept; //constexpr std::synth-three-way-result operator<=>() const noexcept; }; +static_assert(bit_range>, "bit_array does not satisfy bit_range concept!"); +static_assert(bit_sized_range>, "bit_array does not satisfy bit_sized_range concept!"); +#ifdef CONTIGUOUS_RANGE +static_assert(bit_contiguous_range>, "bit_array does not satisfy bit_contiguous_range concept!"); +static_assert(bit_contiguous_sized_range>, "bit_array does not satisfy bit_contiguous_sized_range concept!"); +#endif + template constexpr bit_array::bit_array() noexcept : storage{} {} diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index 22ffcc80..46d888f8 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -107,6 +107,13 @@ class bit_array { //constexpr std::synth-three-way-result operator<=>() const noexcept; }; +static_assert(bit_range>, "bit_array<> does not satisfy bit_contiguous_range concept!"); +static_assert(bit_sized_range>, "bit_array<> does not satisfy bit_contiguous_sized_range concept!"); +#ifdef CONTIGUOUS_RANGE +static_assert(bit_contiguous_range>, "bit_array<> does not satisfy bit_contiguous_range concept!"); +static_assert(bit_contiguous_sized_range>, "bit_array<> does not satisfy bit_contiguous_sized_range concept!"); +#endif + constexpr bit_array::bit_array() noexcept : m_size(0), storage(std::unique_ptr(nullptr)) { } diff --git a/include/bitlib/bit-containers/bit_vector.hpp b/include/bitlib/bit-containers/bit_vector.hpp index 379ff9fd..a0dc5735 100644 --- a/include/bitlib/bit-containers/bit_vector.hpp +++ b/include/bitlib/bit-containers/bit_vector.hpp @@ -2,7 +2,7 @@ // Project: The Experimental Bit Algorithms Library // \file bit_vector.hpp // Description: Implementation of bit_vector -// Contributor: Bryce Kille +// Contributor: Bryce Kille // License: BSD 3-Clause License // ========================================================================== // #ifndef _BIT_VECTOR_HPP_INCLUDED @@ -198,6 +198,13 @@ class bit_vector { //return os << bv.debug_string(bv.cbegin(), bv.cend());; //} }; +static_assert(bit_range>, "bit_vector does not satisfy bit_range concept!"); +static_assert(bit_sized_range>, "bit_vector does not satisfy bit_sized_range concept!"); +#ifdef CONTIGUOUS_RANGE +static_assert(bit_contiguous_range>, "bit_vector does not satisfy bit_contiguous_range concept!"); +static_assert(bit_contiguous_sized_range>, "bit_vector does not satisfy bit_contiguous_sized_range concept!"); +#endif + /* ************************************************************************** */ diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index 8f664d86..fd7ddbd7 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -28,6 +28,8 @@ #include #include +#include "bitlib/bit_concepts.hpp" + // Project sources // Third-party libraries // Miscellaneous @@ -39,26 +41,6 @@ template class bit_iterator; // ========================================================================== // -template -concept bit_range = - std::ranges::range && - std::convertible_to, bit_value>; - -template -concept bit_contiguous_range = bit_range && - std::contiguous_iterator::iterator_type> && - std::has_unique_object_representations_v::word_type>; - -template -concept bit_sized_range = - bit_range && - std::ranges::sized_range; - -template -concept bit_contiguous_sized_range = - bit_contiguous_range && - std::ranges::sized_range; - /* ***************************** BINARY DIGITS ****************************** */ // Binary digits structure definition // Implementation template: only instantiates static_asserts for non-byte types. diff --git a/include/bitlib/bit-iterator/bit_iterator.hpp b/include/bitlib/bit-iterator/bit_iterator.hpp index f508d652..6a930f1a 100644 --- a/include/bitlib/bit-iterator/bit_iterator.hpp +++ b/include/bitlib/bit-iterator/bit_iterator.hpp @@ -20,74 +20,12 @@ #include #include +#include "bitlib/bit_concepts.hpp" // Project sources #include "bit_details.hpp" // Third-party libraries // Miscellaneous namespace bit { -// ========================================================================== // -template -concept reference_like = - std::copyable && // Copyable (no move-only weirdness) - std::convertible_to && // Can be converted to the value type - std::assignable_from; // Supports assignment from value type - -template -concept bit_iterator_c = requires(It it, It it2, - typename It::difference_type n, - typename It::iterator_type i, - typename It::pointer ptr) { - // Required member types - typename It::iterator_type; - typename It::word_type; - typename It::iterator_category; - typename It::value_type; - requires std::same_as; - typename It::difference_type; // should be std::ptrdiff_t - typename It::pointer; // should be bit_pointer - typename It::reference; // should be bit_reference - typename It::size_type; // should be std::size_t - - // Lifecycle requirements - { It() } -> std::same_as; // default-constructible - { It(it) } -> std::same_as; // copy constructible - { It(i) } -> std::same_as; // constructible from iterator_type - { It(i, static_cast(0)) } -> std::same_as; - { It(ptr) } -> std::same_as; - - { it2 - it } -> std::same_as; - - // Assignment - { it = it2 } -> std::same_as; - - // Element access - { *it } -> std::same_as; - { it.operator->() } -> std::same_as; - { it[n] } -> std::same_as; - - // Increment and decrement operations - { ++it } -> std::same_as; - { --it } -> std::same_as; - { it++ } -> std::same_as; - { it-- } -> std::same_as; - { it + n } -> std::same_as; - { it - n } -> std::same_as; - { it += n } -> std::same_as; - { it -= n } -> std::same_as; - - // Underlying details - { it.base() } -> std::same_as; - { it.position() } -> std::same_as; - { it.mask() } -> std::same_as>; - - // Spaceship operator requirement - //{ it <=> it2 } -> std::convertible_to; -}; - -template -concept bit_contiguous_iterator = bit_iterator_c && - std::contiguous_iterator && - std::has_unique_object_representations_v; /* ****************************** BIT ITERATOR ****************************** */ // Bit iterator class definition @@ -147,6 +85,7 @@ class bit_iterator constexpr size_type position() const noexcept; constexpr typename std::remove_cv::type mask() const noexcept; + auto operator<=>(const bit_iterator&) const = default; // Implementation details: data members private: iterator_type _current; @@ -451,23 +390,6 @@ constexpr typename std::common_type< static_assert(bit_iterator_c>, "bit_iterator does not satisfy bit_iterator_c concept!"); -// Define the three-way comparison operator for bit_iterator. -// This template works for bit_iterator and bit_iterator to allow mixed comparisons. -constexpr auto operator<=>( - const bit_iterator_c auto& lhs, - const bit_iterator_c auto& rhs) { - if (auto cmp = lhs.base() <=> rhs.base(); cmp != 0) { - return cmp; - } - return lhs.position() <=> rhs.position(); -} - -constexpr bool operator==( - const bit_iterator_c auto& lhs, - const bit_iterator_c auto& rhs) { - return (lhs <=> rhs) == 0; // Uses the spaceship operator to determine equality -} - // ========================================================================== // } // namespace bit #endif // _BIT_ITERATOR_HPP_INCLUDED diff --git a/include/bitlib/bit-iterator/bit_pointer.hpp b/include/bitlib/bit-iterator/bit_pointer.hpp index 4a564726..41ba5f51 100644 --- a/include/bitlib/bit-iterator/bit_pointer.hpp +++ b/include/bitlib/bit-iterator/bit_pointer.hpp @@ -128,6 +128,8 @@ class bit_pointer bit_pointer rhs ) noexcept; }; + +static_assert(bit_pointer_c, bit_reference>, "bit_pointer does not satisfy bit_pointer_c concept!"); /* ************************************************************************** */ diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index 136cbfcc..86906645 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -102,6 +102,7 @@ class bit_reference word_type* _ptr; typename std::remove_cv::type _mask; }; +static_assert(bit_reference_c>, "bit_reference does not satisfy bit_reference_c concept!"); // Swap template diff --git a/include/bitlib/bit_concepts.hpp b/include/bitlib/bit_concepts.hpp new file mode 100644 index 00000000..54d71171 --- /dev/null +++ b/include/bitlib/bit_concepts.hpp @@ -0,0 +1,96 @@ +#ifndef _BIT_CONCEPTS_HPP_INCLUDED +#define _BIT_CONCEPTS_HPP_INCLUDED + +#include +#include +#include + +namespace bit { + +class bit_value; + +template +concept reference_like = + std::convertible_to && // Can be converted to the value type + std::assignable_from; // Supports assignment from value type + +static_assert(reference_like, "pointer_like does not match native types"); + +template +concept pointer_like = reference_like && requires(pointer_type proxy, reference_type ref) { + // Must be dereferenceable + { *proxy } -> std::same_as; + + // Must support arrow operator functionality (not necessarily as a member function) + requires(std::is_pointer_v || + requires(pointer_type p) { { p.operator->() } -> std::same_as; }); + + // Must be assignable from reference_type* + { proxy = &ref } -> std::same_as; + + // Should support comparison with other proxies + { proxy == proxy } -> std::convertible_to; + { proxy != proxy } -> std::convertible_to; + + // Should support swapping + { std::swap(proxy, proxy) }; +}; + +static_assert(pointer_like, "pointer_like does not match native types"); + +template +concept bit_reference_c = reference_like; + +template +concept bit_pointer_c = pointer_like; + +template +concept bit_iterator_c = + std::random_access_iterator && + std::same_as::value_type, bit_value> && + bit_reference_c::reference> && + bit_pointer_c::pointer, typename std::iterator_traits::reference> && + requires(It it) { + // Must have a nested size_type, iterator_type. + typename It::size_type; + typename It::iterator_type; + requires std::integral; + requires std::random_access_iterator; + + // It must provide base(), position(), and mask() with proper return types. + { it.base() } -> std::same_as; + { it.position() } -> std::same_as; + { it.mask() } -> std::same_as::value_type>>; + }; + +template +concept bit_contiguous_iterator_c = bit_iterator_c && + std::contiguous_iterator && + std::has_unique_object_representations_v::value_type>; + + +template +concept bit_range = + std::ranges::range && + std::convertible_to, bit_value>; + +template +concept bit_sized_range = + bit_range && + std::ranges::sized_range; + +#ifdef CONTIGUOUS_RANGE +template +concept bit_contiguous_range = bit_range && + std::contiguous_iterator::iterator_type> && + std::has_unique_object_representations_v::iterator_type>::value_type>; + +template +concept bit_contiguous_sized_range = + bit_contiguous_range && + std::ranges::sized_range; +#endif + +} // namespace bit + +#endif diff --git a/profile/CMakeLists.txt b/profile/CMakeLists.txt index 2efc67f3..9fe3a14c 100644 --- a/profile/CMakeLists.txt +++ b/profile/CMakeLists.txt @@ -8,8 +8,7 @@ set(CMAKE_BUILD_TYPE RelWithDebInfo) file(GLOB PROFILE_SOURCES "src/*.cpp") add_executable(bitlib-profile ${PROFILE_SOURCES}) -# specify benchmark-specific libraries -include_directories(src/utils) +target_link_libraries(bitlib-profile PRIVATE bitlib_utils) target_compile_options(bitlib-profile PUBLIC -O2 -ggdb -Wpedantic) install(TARGETS bitlib-profile DESTINATION .) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 47f763b3..8d24c721 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -22,7 +22,7 @@ endif() # endif() # specify test-specific libraries -target_link_libraries(bitlib-tests PRIVATE bitlib GTest::gtest GTest::gtest_main) +target_link_libraries(bitlib-tests PRIVATE bitlib GTest::gtest GTest::gtest_main bitlib_utils) target_link_options(bitlib-tests PRIVATE -pthread -lgcov --coverage) if (NOT BITLIB_GTEST_REPEAT) From 32db04901d93740fd2280a8afb645db566eed718 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sun, 23 Mar 2025 09:11:31 -0400 Subject: [PATCH 15/85] Begin templating for concepts --- include/bitlib/bit-containers/bit_span.hpp | 6 +++ include/bitlib/bit-containers/bit_vector.hpp | 40 ++++++++++---------- include/bitlib/bit-iterator/bit_iterator.hpp | 6 +++ 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/include/bitlib/bit-containers/bit_span.hpp b/include/bitlib/bit-containers/bit_span.hpp index 319936ff..360bbeff 100644 --- a/include/bitlib/bit-containers/bit_span.hpp +++ b/include/bitlib/bit-containers/bit_span.hpp @@ -140,6 +140,12 @@ class bit_span : private bit_span_storage { constexpr bit_span last() const noexcept requires(RevOffset != std::dynamic_extent); constexpr bit_span last(size_type offset) const noexcept requires(Extent == std::dynamic_extent); }; + +static_assert(bit_range>, "bit_span does not satisfy bit_range concept!"); +static_assert(bit_sized_range>, "bit_span does not satisfy bit_sized_range concept!"); +static_assert(bit_range>, "bit_span does not satisfy bit_range concept!"); +static_assert(bit_sized_range>, "bit_span does not satisfy bit_sized_range concept!"); + // Class Template Argument Deduction // CTAD guide for the constructor taking a WordType&, diff --git a/include/bitlib/bit-containers/bit_vector.hpp b/include/bitlib/bit-containers/bit_vector.hpp index a0dc5735..e04ddc75 100644 --- a/include/bitlib/bit-containers/bit_vector.hpp +++ b/include/bitlib/bit-containers/bit_vector.hpp @@ -175,7 +175,8 @@ class bit_vector { constexpr void clear() noexcept; constexpr iterator insert(const_iterator pos, const value_type& value); constexpr iterator insert(const_iterator pos, size_type count, const value_type& value); - constexpr iterator insert(const_iterator pos, iterator first, iterator last); + template + constexpr iterator insert(const_iterator pos, OtherIt first, OtherIt last); constexpr iterator erase(iterator pos); constexpr iterator erase(iterator first, iterator last); constexpr void push_back(const value_type& value); @@ -551,27 +552,28 @@ bit_vector::insert( return begin() + d; } -template +template +template constexpr typename bit_vector::iterator bit_vector::insert( - const_iterator pos, - iterator first, - iterator last) { - const auto d = distance(cbegin(), pos); - const size_t count = distance(first, last); - if (count == 0) { - return begin() + d; - } - const float bits_available = word_vector.size()*digits; - const auto need_to_add = length_ + count > bits_available; - if (need_to_add) { - const auto words_to_add = word_count(length_ + count - bits_available); - word_vector.resize(word_vector.size() + words_to_add); - } - length_ += count; - shift_right(begin() + d, begin() + length_, count); - copy(first, last, begin() + d); + const_iterator pos, + OtherIt first, + OtherIt last) { + const auto d = distance(cbegin(), pos); + const size_t count = distance(first, last); + if (count == 0) { return begin() + d; + } + const float bits_available = word_vector.size() * digits; + const auto need_to_add = length_ + count > bits_available; + if (need_to_add) { + const auto words_to_add = word_count(length_ + count - bits_available); + word_vector.resize(word_vector.size() + words_to_add); + } + length_ += count; + shift_right(begin() + d, begin() + length_, count); + copy(first, last, begin() + d); + return begin() + d; } template diff --git a/include/bitlib/bit-iterator/bit_iterator.hpp b/include/bitlib/bit-iterator/bit_iterator.hpp index 6a930f1a..d3995aa5 100644 --- a/include/bitlib/bit-iterator/bit_iterator.hpp +++ b/include/bitlib/bit-iterator/bit_iterator.hpp @@ -78,6 +78,7 @@ class bit_iterator constexpr bit_iterator operator-(difference_type n) const; constexpr bit_iterator& operator+=(difference_type n); constexpr bit_iterator& operator-=(difference_type n); + constexpr difference_type operator-(const bit_iterator&) const; // Underlying details public: @@ -321,6 +322,11 @@ constexpr bit_iterator& bit_iterator::operator-=( // -------------------------------------------------------------------------- // +template +constexpr bit_iterator::difference_type bit_iterator::operator-(const bit_iterator& other) const { + constexpr difference_type digits = binary_digits::value; + return (_current - other._current) * digits + (_position - other._position); +} // -------------------- BIT ITERATOR: UNDERLYING DETAILS -------------------- // // Returns a copy of the underlying iterator From 9ad0e0c4ba1f8f9fa2e457ec511f4019ce5f8f2f Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sun, 23 Mar 2025 20:48:16 -0400 Subject: [PATCH 16/85] from_range constructor for vector --- include/bitlib/bit-containers/bit_bitsof.hpp | 5 --- include/bitlib/bit-containers/bit_vector.hpp | 40 +++++++++++--------- include/bitlib/bit_concepts.hpp | 9 +++++ 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/include/bitlib/bit-containers/bit_bitsof.hpp b/include/bitlib/bit-containers/bit_bitsof.hpp index c74c1573..6eafe2c0 100644 --- a/include/bitlib/bit-containers/bit_bitsof.hpp +++ b/include/bitlib/bit-containers/bit_bitsof.hpp @@ -16,17 +16,12 @@ When calling within template code with a type, the callee must use the `bitsof -class bit_array; - // Concept to check for a public static constexpr size_t bits member. template concept HasBits = requires { { T::bits } -> std::convertible_to; }; - // General case: for types that do not have a bits member. template requires (!HasBits) constexpr std::size_t bitsof() noexcept { diff --git a/include/bitlib/bit-containers/bit_vector.hpp b/include/bitlib/bit-containers/bit_vector.hpp index e04ddc75..831926e6 100644 --- a/include/bitlib/bit-containers/bit_vector.hpp +++ b/include/bitlib/bit-containers/bit_vector.hpp @@ -13,16 +13,18 @@ // ================================ PREAMBLE ================================ // // C++ standard library -#include +#include #include -#include #include -#include -#include +#include +#include #include +#include // Project sources -#include "bitlib/bit-iterator/bit.hpp" #include "bitlib/bit-algorithms/bit_algorithm.hpp" +#include "bitlib/bit-iterator/bit.hpp" +#include "bitlib/bit_concepts.hpp" + // Third-party libraries // Miscellaneous namespace bit { @@ -100,11 +102,12 @@ class bit_vector { constexpr explicit bit_vector( size_type count, const Allocator& alloc=Allocator()); - template - constexpr bit_vector( - bit_iterator first, - bit_iterator last, - const Allocator& alloc=Allocator()); + template + constexpr bit_vector(Iterator first, Iterator last, const Allocator& alloc = Allocator()); + + template + constexpr bit_vector(std::from_range_t, _Range&& rg, const Allocator& alloc = Allocator()); + template constexpr bit_vector( RandomAccessIt first, @@ -228,16 +231,19 @@ constexpr bit_vector::bit_vector(size_type count, const All : word_vector(word_count(count), alloc), length_(count) {} //TODO needs to work for input iterators -template -template -constexpr bit_vector::bit_vector( - bit_iterator first, - bit_iterator last, - const Allocator& alloc) +template + +template +constexpr bit_vector::bit_vector(Iterator first, Iterator last, const Allocator& alloc) : word_vector(distance(first, last), alloc), length_(distance(first, last)) { - copy(first, last, this->begin()); + copy(first, last, this->begin()); } +template +template +constexpr bit_vector::bit_vector(std::from_range_t, _Range&& rg, const Allocator& alloc) + : bit_vector(rg.begin(), rg.end(), alloc) { +} template template diff --git a/include/bitlib/bit_concepts.hpp b/include/bitlib/bit_concepts.hpp index 54d71171..f5aad7c9 100644 --- a/include/bitlib/bit_concepts.hpp +++ b/include/bitlib/bit_concepts.hpp @@ -5,6 +5,15 @@ #include #include +#if defined(__GNUC__) && (__GNUC__ < 13) +namespace std { +struct from_range_t { + explicit from_range_t() = default; +}; +inline constexpr from_range_t from_range{}; +} // namespace std +#endif + namespace bit { class bit_value; From afeb0fbd3000ae3dbc06afeb497b482cecbe11a9 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sun, 23 Mar 2025 20:48:33 -0400 Subject: [PATCH 17/85] consistent naming in array --- include/bitlib/bit-containers/bit_array.hpp | 167 ++++++++++-------- .../bit_array_dynamic_extent.hpp | 55 +++--- 2 files changed, 119 insertions(+), 103 deletions(-) diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index afe2ded8..47e2437c 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -30,87 +30,87 @@ namespace bit { // ========================================================================== // -template +template class bit_array { -public: -static constexpr std::size_t bits = N; - -private: -using T = bit_value; -using WordType = std::uint8_t; -static constexpr std::size_t Words = (N * bitsof() + bitsof() - 1) / bitsof(); - -alignas(alignof(T)) std::array storage; - -public: -using value_type = bit_value; -using size_type = std::size_t; -using difference_type = std::ptrdiff_t; -using reference = bit_reference; -using const_reference = const bit_reference; -using pointer = bit_pointer; -using const_pointer = const pointer; -using iterator = bit_iterator::iterator>; -using const_iterator = bit_iterator::const_iterator>; - -/* + public: + static constexpr std::size_t bits = N; + using word_type = std::uint8_t; + using value_type = bit_value; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = bit_reference; + using const_reference = const bit_reference; + using pointer = bit_pointer; + using const_pointer = const pointer; + + private: + static constexpr std::size_t Words = (N * bitsof() + bitsof() - 1) / bitsof(); + + alignas(alignof(value_type)) std::array storage; + + public: + using iterator = bit_iterator::iterator>; + using const_iterator = bit_iterator::const_iterator>; + + public: + /* * Constructors, copies and moves... */ -constexpr bit_array() noexcept; -constexpr bit_array(value_type bit_val); -constexpr bit_array(const bit_array& other) = default; -constexpr bit_array(const bit_array&& other) noexcept; -constexpr bit_array(const std::initializer_list init); -constexpr bit_array(const std::initializer_list init); -constexpr bit_array(const std::initializer_list init); -constexpr bit_array(const std::string_view s); - -~bit_array() = default; -/* + constexpr bit_array() noexcept; + constexpr bit_array(value_type bit_val); + constexpr bit_array(const bit_array& other) = default; + constexpr bit_array(const bit_array&& other) noexcept; + constexpr bit_array(const std::initializer_list init); + constexpr bit_array(const std::initializer_list init); + constexpr bit_array(const std::initializer_list init); + constexpr bit_array(const std::string_view s); + + ~bit_array() = default; + /* * Assignment */ -constexpr bit_array& operator=(const bit_array& other) = default; -constexpr bit_array& operator=(bit_array&& other) noexcept; + constexpr bit_array& operator=(const bit_array& other) = default; + constexpr bit_array& operator=(bit_array&& other) noexcept; -constexpr bool operator==(const bit_array& other) const noexcept; + constexpr bool operator==(const bit_array& other) const noexcept; -/* + /* * Element Access */ -constexpr reference operator[](size_type pos); -constexpr const_reference operator[](size_type pos) const; -constexpr reference at(size_type pos); -constexpr const_reference at(size_type pos) const; -constexpr reference front(); -constexpr const_reference front() const; -constexpr reference back(); -constexpr const_reference back() const; -constexpr WordType* data() noexcept; -constexpr const WordType* data() const noexcept; - -/* + constexpr reference operator[](size_type pos); + constexpr const_reference operator[](size_type pos) const; + constexpr reference at(size_type pos); + constexpr const_reference at(size_type pos) const; + constexpr reference front(); + constexpr const_reference front() const; + constexpr reference back(); + constexpr const_reference back() const; + constexpr word_type* data() noexcept; + constexpr const word_type* data() const noexcept; + + /* * Iterators */ -constexpr iterator begin() noexcept; -constexpr iterator end() noexcept; -constexpr const_iterator begin() const noexcept; -constexpr const_iterator end() const noexcept; -constexpr const_iterator cbegin() const noexcept; -constexpr const_iterator cend() const noexcept; - -/* + constexpr iterator begin() noexcept; + constexpr iterator end() noexcept; + constexpr const_iterator begin() const noexcept; + constexpr const_iterator end() const noexcept; + constexpr const_iterator cbegin() const noexcept; + constexpr const_iterator cend() const noexcept; + + /* * Capacity */ -constexpr bool empty() const noexcept; -constexpr size_type size() const noexcept; -constexpr size_type max_size() const noexcept; + constexpr bool empty() const noexcept; + constexpr size_type size() const noexcept; + constexpr size_type max_size() const noexcept; -/* + /* * Operations */ -constexpr void fill(value_type bit_val) noexcept; -constexpr void swap(bit_array& other) noexcept; -//constexpr std::synth-three-way-result operator<=>() const noexcept; + constexpr void fill(value_type bit_val) noexcept; + constexpr void swap(bit_array& other) noexcept; + //constexpr std::synth-three-way-result operator<=>() const noexcept; }; static_assert(bit_range>, "bit_array does not satisfy bit_range concept!"); @@ -120,6 +120,23 @@ static_assert(bit_contiguous_range>, "bit_array does not satisfy b static_assert(bit_contiguous_sized_range>, "bit_array does not satisfy bit_contiguous_sized_range concept!"); #endif +#if 0 +// Class Template Argument Deduction +// CTAD guide for the constructor taking a word_type&, +// deducing Extent as bitsof(). +template +bit_array(word_type&) -> bit_array()>; +template +bit_array(word_type*) -> bit_array()>; + +// CTAD guide for the constructor taking a word_type* and a size, +// which should always be used when Extent is std::dynamic_extent. +template +bit_array(word_type&, std::size_t) -> bit_array; +template +bit_array(word_type*, std::size_t) -> bit_array; +#endif + template constexpr bit_array::bit_array() noexcept : storage{} {} @@ -131,14 +148,14 @@ constexpr bit_array::bit_array(bit_array::value_type bit_val) : storage{} template constexpr void bit_array::fill(bit_array::value_type bit_val) noexcept { if (bit_val) { - std::fill(storage.begin(), storage.end(), bit_array::WordType(-1)); + std::fill(storage.begin(), storage.end(), bit_array::word_type(-1)); } else { - std::fill(storage.begin(), storage.end(), bit_array::WordType(0)); + std::fill(storage.begin(), storage.end(), bit_array::word_type(0)); } } -template -constexpr bit_array::bit_array(const std::initializer_list init) { +template +constexpr bit_array::bit_array(const std::initializer_list init) { if(init.size() != bitsof(*this)) [[unlikely]] { throw std::invalid_argument("initialize_list contains an invalid number of bits for bit_array."); } @@ -153,8 +170,8 @@ constexpr bit_array::bit_array(const std::initializer_list init) { std::copy(init.begin(), init.end(), this->begin()); } -template -constexpr bit_array::bit_array(const std::initializer_list init) : storage(init) { +template +constexpr bit_array::bit_array(const std::initializer_list init) : storage(init) { static_assert(init.size() == storage.size()); } @@ -246,13 +263,13 @@ constexpr typename bit_array::const_reference bit_array::back() const { return begin()[size()-1]; } -template -constexpr typename bit_array::WordType* bit_array::data() noexcept { +template +constexpr typename bit_array::word_type* bit_array::data() noexcept { return size() ? storage.data() : nullptr; } -template -constexpr const typename bit_array::WordType* bit_array::data() const noexcept { +template +constexpr const typename bit_array::word_type* bit_array::data() const noexcept { return size() ? storage.data() : nullptr; } diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index 46d888f8..d09307e5 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -26,22 +26,21 @@ namespace bit { template <> class bit_array { public: - using T = bit_value; - using WordType = std::uint8_t; - using value_type = T; + using word_type = std::uint8_t; + using value_type = bit_value; using size_type = std::size_t; using difference_type = std::ptrdiff_t; - using reference = bit_reference; - using const_reference = const bit_reference; - using pointer = bit_pointer; + using reference = bit_reference; + using const_reference = const bit_reference; + using pointer = bit_pointer; using const_pointer = const pointer; - using iterator = bit_iterator; - using const_iterator = bit_iterator; + using iterator = bit_iterator; + using const_iterator = bit_iterator; private: size_type m_size; - std::unique_ptr storage; - static constexpr std::size_t Words(std::size_t N) { return (N * bitsof() + bitsof() - 1) / bitsof(); }; + std::unique_ptr storage; + static constexpr std::size_t Words(std::size_t N) { return (N * bitsof() + bitsof() - 1) / bitsof(); }; public: /* @@ -56,7 +55,7 @@ class bit_array { #if 0 constexpr bit_array(const std::initializer_list init); #endif - constexpr bit_array(const std::initializer_list init); + constexpr bit_array(const std::initializer_list init); constexpr bit_array(const std::string_view s); ~bit_array() = default; @@ -79,8 +78,8 @@ class bit_array { constexpr const_reference front() const; constexpr reference back(); constexpr const_reference back() const; - constexpr WordType* data() noexcept; - constexpr const WordType* data() const noexcept; + constexpr word_type* data() noexcept; + constexpr const word_type* data() const noexcept; /* * Iterators @@ -115,35 +114,35 @@ static_assert(bit_contiguous_sized_range>, "bit_array<> does not sat #endif constexpr bit_array::bit_array() noexcept - : m_size(0), storage(std::unique_ptr(nullptr)) { + : m_size(0), storage(std::unique_ptr(nullptr)) { } constexpr bit_array::bit_array(size_type size) : m_size(size), - storage(std::make_unique(Words(size))) { + storage(std::make_unique(Words(size))) { } constexpr bit_array::bit_array(size_type size, value_type bit_val) : m_size(size), - storage(std::make_unique(Words(size))) { + storage(std::make_unique(Words(size))) { fill(bit_val); } constexpr bit_array::bit_array(const bit_array& other) : m_size(other.size()), - storage(std::make_unique(Words(m_size))) { + storage(std::make_unique(Words(m_size))) { std::copy(other.begin(), other.end(), this->begin()); } constexpr bit_array::bit_array(const bit_array&& other) : m_size(other.size()), - storage(std::make_unique(Words(m_size))) { + storage(std::make_unique(Words(m_size))) { std::copy(other.begin(), other.end(), this->begin()); } constexpr bit_array::bit_array(const std::initializer_list init) : m_size(init.size()), - storage(std::make_unique(Words(Words(m_size)))) { + storage(std::make_unique(Words(Words(m_size)))) { std::copy(init.begin(), init.end(), this->begin()); } @@ -151,21 +150,21 @@ constexpr bit_array::bit_array(const std::initializer_list< No known conversion from bool to bit_value bit_value has explicit constructor from bool to bit_value so this doesnt work constexpr bit_array::bit_array(const std::initializer_list init) - : storage(std::make_unique(Words(init.size()))), + : storage(std::make_unique(Words(init.size()))), m_size(init.size()) { std::copy(init.begin(), init.end(), this->begin()); } #endif -constexpr bit_array::bit_array(const std::initializer_list init) - : m_size(bitsof() * init.size()), - storage(std::make_unique(init.size())) { +constexpr bit_array::bit_array(const std::initializer_list init) + : m_size(bitsof() * init.size()), + storage(std::make_unique(init.size())) { std::copy(init.begin(), init.end(), &storage[0]); } constexpr bit_array::bit_array(const std::string_view s) : m_size((std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))), - storage(std::make_unique(Words(m_size))) { + storage(std::make_unique(Words(m_size))) { size_type i = 0; for (char c : s) { if (c == '0') { @@ -202,9 +201,9 @@ constexpr void bit_array::swap(bit_array::fill(value_type bit_val) noexcept { if (bit_val) { - std::fill(&storage[0], &storage[Words(m_size)], bit_array::WordType(-1)); + std::fill(&storage[0], &storage[Words(m_size)], bit_array::word_type(-1)); } else { - std::fill(&storage[0], &storage[Words(m_size)], bit_array::WordType(0)); + std::fill(&storage[0], &storage[Words(m_size)], bit_array::word_type(0)); } } @@ -252,11 +251,11 @@ constexpr typename bit_array::const_reference bit_array::WordType* bit_array::data() noexcept { +constexpr typename bit_array::word_type* bit_array::data() noexcept { return size() ? storage.get() : nullptr; } -constexpr const typename bit_array::WordType* bit_array::data() const noexcept { +constexpr const typename bit_array::word_type* bit_array::data() const noexcept { return size() ? storage.get() : nullptr; } From bc28d8ab87b3253ca2622a6b3d96e5224769ed04 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sun, 23 Mar 2025 21:47:37 -0400 Subject: [PATCH 18/85] Extend array to allow alignment --- include/bitlib/bit-containers/bit_array.hpp | 146 +++++++++--------- .../bit_array_dynamic_extent.hpp | 145 ++++++++++------- 2 files changed, 161 insertions(+), 130 deletions(-) diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index 47e2437c..3d820197 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -30,11 +30,11 @@ namespace bit { // ========================================================================== // -template +template class bit_array { public: static constexpr std::size_t bits = N; - using word_type = std::uint8_t; + using word_type = W; using value_type = bit_value; using size_type = std::size_t; using difference_type = std::ptrdiff_t; @@ -44,9 +44,9 @@ class bit_array { using const_pointer = const pointer; private: - static constexpr std::size_t Words = (N * bitsof() + bitsof() - 1) / bitsof(); + static constexpr std::size_t Words = (N * bitsof() + bitsof() - 1) / bitsof(); - alignas(alignof(value_type)) std::array storage; + alignas(alignof(V)) std::array storage; public: using iterator = bit_iterator::iterator>; @@ -58,8 +58,8 @@ class bit_array { */ constexpr bit_array() noexcept; constexpr bit_array(value_type bit_val); - constexpr bit_array(const bit_array& other) = default; - constexpr bit_array(const bit_array&& other) noexcept; + constexpr bit_array(const bit_array& other) = default; + constexpr bit_array(const bit_array&& other) noexcept; constexpr bit_array(const std::initializer_list init); constexpr bit_array(const std::initializer_list init); constexpr bit_array(const std::initializer_list init); @@ -69,10 +69,10 @@ class bit_array { /* * Assignment */ - constexpr bit_array& operator=(const bit_array& other) = default; - constexpr bit_array& operator=(bit_array&& other) noexcept; + constexpr bit_array& operator=(const bit_array& other) = default; + constexpr bit_array& operator=(bit_array&& other) noexcept; - constexpr bool operator==(const bit_array& other) const noexcept; + constexpr bool operator==(const bit_array& other) const noexcept; /* * Element Access @@ -109,7 +109,7 @@ class bit_array { * Operations */ constexpr void fill(value_type bit_val) noexcept; - constexpr void swap(bit_array& other) noexcept; + constexpr void swap(bit_array& other) noexcept; //constexpr std::synth-three-way-result operator<=>() const noexcept; }; @@ -137,46 +137,46 @@ template bit_array(word_type*, std::size_t) -> bit_array; #endif -template -constexpr bit_array::bit_array() noexcept : storage{} {} +template +constexpr bit_array::bit_array() noexcept : storage{} {} -template -constexpr bit_array::bit_array(bit_array::value_type bit_val) : storage{} { +template +constexpr bit_array::bit_array(bit_array::value_type bit_val) : storage{} { fill(bit_val); } -template -constexpr void bit_array::fill(bit_array::value_type bit_val) noexcept { +template +constexpr void bit_array::fill(bit_array::value_type bit_val) noexcept { if (bit_val) { - std::fill(storage.begin(), storage.end(), bit_array::word_type(-1)); + std::fill(storage.begin(), storage.end(), bit_array::word_type(-1)); } else { - std::fill(storage.begin(), storage.end(), bit_array::word_type(0)); + std::fill(storage.begin(), storage.end(), bit_array::word_type(0)); } } -template -constexpr bit_array::bit_array(const std::initializer_list init) { +template +constexpr bit_array::bit_array(const std::initializer_list init) { if(init.size() != bitsof(*this)) [[unlikely]] { throw std::invalid_argument("initialize_list contains an invalid number of bits for bit_array."); } std::copy(init.begin(), init.end(), this->begin()); } -template -constexpr bit_array::bit_array(const std::initializer_list init) { +template +constexpr bit_array::bit_array(const std::initializer_list init) { if(init.size() != bitsof(*this)) [[unlikely]] { throw std::invalid_argument("initialize_list contains an invalid number of bits for bit_array."); } std::copy(init.begin(), init.end(), this->begin()); } -template -constexpr bit_array::bit_array(const std::initializer_list init) : storage(init) { +template +constexpr bit_array::bit_array(const std::initializer_list init) : storage(init) { static_assert(init.size() == storage.size()); } -template -constexpr bit_array::bit_array(const std::string_view s) { +template +constexpr bit_array::bit_array(const std::string_view s) { if(bitsof(*this) != (std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))) [[unlikely]] { throw std::invalid_argument("String contains an invalid number of bits for bit_array."); }; @@ -190,24 +190,23 @@ constexpr bit_array::bit_array(const std::string_view s) { } } -template -constexpr bit_array::bit_array(const bit_array&& other) noexcept +template +constexpr bit_array::bit_array(const bit_array&& other) noexcept : storage(other.storage) {} - -template -constexpr bit_array& bit_array::operator=(bit_array&& other) noexcept { +template +constexpr bit_array& bit_array::operator=(bit_array&& other) noexcept { std::copy(other.storage.begin(), other.storage.end(), storage.begin()); return *this; } -template -constexpr bool bit_array::operator==(const bit_array& other) const noexcept { +template +constexpr bool bit_array::operator==(const bit_array& other) const noexcept { return equal(begin(), end(), other.begin()); } -template -constexpr void bit_array::swap(bit_array& other) noexcept { +template +constexpr void bit_array::swap(bit_array& other) noexcept { std::swap(this->storage, other.storage); } @@ -215,18 +214,18 @@ constexpr void bit_array::swap(bit_array& other) noexcept { /* * Element Access */ -template -constexpr typename bit_array::reference bit_array::operator[](size_type pos) { +template +constexpr typename bit_array::reference bit_array::operator[](size_type pos) { return begin()[pos]; } -template -constexpr typename bit_array::const_reference bit_array::operator[](size_type pos) const { +template +constexpr typename bit_array::const_reference bit_array::operator[](size_type pos) const { return begin()[pos]; } -template -constexpr typename bit_array::reference bit_array::at(size_type pos) { +template +constexpr typename bit_array::reference bit_array::at(size_type pos) { if (pos < size()) { return begin()[pos]; } else { @@ -234,8 +233,8 @@ constexpr typename bit_array::reference bit_array::at(size_type pos) { } } -template -constexpr typename bit_array::const_reference bit_array::at(size_type pos) const { +template +constexpr typename bit_array::const_reference bit_array::at(size_type pos) const { if (pos < size()) { return begin()[pos]; } else { @@ -243,78 +242,77 @@ constexpr typename bit_array::const_reference bit_array::at(size_type pos) } } -template -constexpr typename bit_array::reference bit_array::front() { +template +constexpr typename bit_array::reference bit_array::front() { return begin()[0]; } -template -constexpr typename bit_array::const_reference bit_array::front() const { +template +constexpr typename bit_array::const_reference bit_array::front() const { return begin()[0]; } -template -constexpr typename bit_array::reference bit_array::back() { +template +constexpr typename bit_array::reference bit_array::back() { return begin()[size()-1]; } -template -constexpr typename bit_array::const_reference bit_array::back() const { +template +constexpr typename bit_array::const_reference bit_array::back() const { return begin()[size()-1]; } -template -constexpr typename bit_array::word_type* bit_array::data() noexcept { +template +constexpr typename bit_array::word_type* bit_array::data() noexcept { return size() ? storage.data() : nullptr; } -template -constexpr const typename bit_array::word_type* bit_array::data() const noexcept { +template +constexpr const typename bit_array::word_type* bit_array::data() const noexcept { return size() ? storage.data() : nullptr; } // -------------------------------------------------------------------------- // +template +constexpr typename bit_array::size_type bit_array::size() const noexcept { return N; } -template -constexpr typename bit_array::size_type bit_array::size() const noexcept {return N;} - -template -constexpr typename bit_array::size_type bit_array::max_size() const noexcept {return size();} +template +constexpr typename bit_array::size_type bit_array::max_size() const noexcept { return size(); } -template -constexpr bool bit_array::empty() const noexcept {return 0 == size();} +template +constexpr bool bit_array::empty() const noexcept { return 0 == size(); } // Iterators // -------------------------------------------------------------------------- // -template -constexpr typename bit_array::iterator bit_array::begin() noexcept { +template +constexpr typename bit_array::iterator bit_array::begin() noexcept { return iterator(storage.begin()); } -template -constexpr typename bit_array::iterator bit_array::end() noexcept { +template +constexpr typename bit_array::iterator bit_array::end() noexcept { return begin() + size(); } -template -constexpr typename bit_array::const_iterator bit_array::begin() const noexcept { +template +constexpr typename bit_array::const_iterator bit_array::begin() const noexcept { return const_iterator(storage.begin()); } -template -constexpr typename bit_array::const_iterator bit_array::end() const noexcept { +template +constexpr typename bit_array::const_iterator bit_array::end() const noexcept { return const_iterator(storage.begin()) + size(); } -template -constexpr typename bit_array::const_iterator bit_array::cbegin() const noexcept { +template +constexpr typename bit_array::const_iterator bit_array::cbegin() const noexcept { return const_iterator(storage.begin()); } -template -constexpr typename bit_array::const_iterator bit_array::cend() const noexcept { +template +constexpr typename bit_array::const_iterator bit_array::cend() const noexcept { return const_iterator(storage.begin()) + size(); } diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index d09307e5..4ba62ace 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -23,10 +23,10 @@ namespace bit { -template <> -class bit_array { +template +class bit_array { public: - using word_type = std::uint8_t; + using word_type = W; using value_type = bit_value; using size_type = std::size_t; using difference_type = std::ptrdiff_t; @@ -39,8 +39,8 @@ class bit_array { private: size_type m_size; - std::unique_ptr storage; - static constexpr std::size_t Words(std::size_t N) { return (N * bitsof() + bitsof() - 1) / bitsof(); }; + std::unique_ptr storage; + static constexpr std::size_t Words(std::size_t N) { return (N * bitsof() + bitsof() - 1) / bitsof(); }; public: /* @@ -49,8 +49,8 @@ class bit_array { constexpr bit_array() noexcept; constexpr bit_array(size_type size); constexpr bit_array(size_type size, value_type bit_val); - constexpr bit_array(const bit_array& other); - constexpr bit_array(const bit_array&& other); + constexpr bit_array(const bit_array& other); + constexpr bit_array(const bit_array&& other); constexpr bit_array(const std::initializer_list init); #if 0 constexpr bit_array(const std::initializer_list init); @@ -62,10 +62,10 @@ class bit_array { /* * Assignment */ - constexpr bit_array& operator=(const bit_array& other); - constexpr bit_array& operator=(bit_array&& other); + constexpr bit_array& operator=(const bit_array& other); + constexpr bit_array& operator=(bit_array&& other); - constexpr bool operator==(const bit_array& other) const noexcept; + constexpr bool operator==(const bit_array& other) const noexcept; /* * Element Access @@ -102,7 +102,7 @@ class bit_array { * Operations */ constexpr void fill(value_type bit_val) noexcept; - constexpr void swap(bit_array& other) noexcept; + constexpr void swap(bit_array& other) noexcept; //constexpr std::synth-three-way-result operator<=>() const noexcept; }; @@ -113,58 +113,66 @@ static_assert(bit_contiguous_range>, "bit_array<> does not satisfy b static_assert(bit_contiguous_sized_range>, "bit_array<> does not satisfy bit_contiguous_sized_range concept!"); #endif -constexpr bit_array::bit_array() noexcept - : m_size(0), storage(std::unique_ptr(nullptr)) { +template +constexpr bit_array::bit_array() noexcept + : m_size(0), storage(std::unique_ptr(nullptr, &std::free)) { } -constexpr bit_array::bit_array(size_type size) +template +constexpr bit_array::bit_array(size_type size) : m_size(size), - storage(std::make_unique(Words(size))) { + storage(std::unique_ptr(static_cast(std::aligned_alloc(alignof(V), sizeof(word_type) * Words(m_size))), &std::free)) { } -constexpr bit_array::bit_array(size_type size, value_type bit_val) +template +constexpr bit_array::bit_array(size_type size, value_type bit_val) : m_size(size), - storage(std::make_unique(Words(size))) { + storage(std::unique_ptr(static_cast(std::aligned_alloc(alignof(V), sizeof(word_type) * Words(m_size))), &std::free)) { fill(bit_val); } -constexpr bit_array::bit_array(const bit_array& other) +template +constexpr bit_array::bit_array(const bit_array& other) : m_size(other.size()), - storage(std::make_unique(Words(m_size))) { + storage(std::unique_ptr(static_cast(std::aligned_alloc(alignof(V), sizeof(word_type) * Words(m_size))), &std::free)) { std::copy(other.begin(), other.end(), this->begin()); } -constexpr bit_array::bit_array(const bit_array&& other) +template +constexpr bit_array::bit_array(const bit_array&& other) : m_size(other.size()), - storage(std::make_unique(Words(m_size))) { + storage(std::unique_ptr(static_cast(std::aligned_alloc(alignof(V), sizeof(word_type) * Words(m_size))), &std::free)) { std::copy(other.begin(), other.end(), this->begin()); } -constexpr bit_array::bit_array(const std::initializer_list init) +template +constexpr bit_array::bit_array(const std::initializer_list init) : m_size(init.size()), - storage(std::make_unique(Words(Words(m_size)))) { + storage(std::unique_ptr(static_cast(std::aligned_alloc(alignof(V), sizeof(word_type) * Words(m_size))), &std::free)) { std::copy(init.begin(), init.end(), this->begin()); } #if 0 No known conversion from bool to bit_value bit_value has explicit constructor from bool to bit_value so this doesnt work -constexpr bit_array::bit_array(const std::initializer_list init) +constexpr bit_array::bit_array(const std::initializer_list init) : storage(std::make_unique(Words(init.size()))), m_size(init.size()) { std::copy(init.begin(), init.end(), this->begin()); } #endif -constexpr bit_array::bit_array(const std::initializer_list init) +template +constexpr bit_array::bit_array(const std::initializer_list init) : m_size(bitsof() * init.size()), - storage(std::make_unique(init.size())) { + storage(std::unique_ptr(static_cast(std::aligned_alloc(alignof(V), sizeof(word_type) * Words(m_size))), &std::free)) { std::copy(init.begin(), init.end(), &storage[0]); } -constexpr bit_array::bit_array(const std::string_view s) +template +constexpr bit_array::bit_array(const std::string_view s) : m_size((std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))), - storage(std::make_unique(Words(m_size))) { + storage(std::unique_ptr(static_cast(std::aligned_alloc(alignof(V), sizeof(word_type) * Words(m_size))), &std::free)) { size_type i = 0; for (char c : s) { if (c == '0') { @@ -175,35 +183,41 @@ constexpr bit_array::bit_array(const std::string_view s) } } -constexpr bit_array& bit_array::operator=(const bit_array& other) { +template +constexpr bit_array& bit_array::operator=(const bit_array& other) { if (nullptr == storage.get() || m_size != other.m_size) { - throw std::invalid_argument("Cannot reassign bit_array size"); + throw std::invalid_argument("Cannot reassign bit_array size"); } std::copy(other.begin(), other.end(), this->begin()); return *this; } -constexpr bit_array& bit_array::operator=(bit_array&& other) { + +template +constexpr bit_array& bit_array::operator=(bit_array&& other) { if (nullptr == storage.get() || m_size != other.m_size) { - throw std::invalid_argument("Cannot reassign bit_array size"); + throw std::invalid_argument("Cannot reassign bit_array size"); } std::copy(other.begin(), other.end(), this->begin()); return *this; } -constexpr bool bit_array::operator==(const bit_array& other) const noexcept { +template +constexpr bool bit_array::operator==(const bit_array& other) const noexcept { return equal(begin(), end(), other.begin()); } -constexpr void bit_array::swap(bit_array& other) noexcept { +template +constexpr void bit_array::swap(bit_array& other) noexcept { std::swap(this->storage, other.storage); std::swap(this->m_size, other.m_size); } -constexpr void bit_array::fill(value_type bit_val) noexcept { +template +constexpr void bit_array::fill(value_type bit_val) noexcept { if (bit_val) { - std::fill(&storage[0], &storage[Words(m_size)], bit_array::word_type(-1)); + std::fill(&storage[0], &storage[Words(m_size)], bit_array::word_type(-1)); } else { - std::fill(&storage[0], &storage[Words(m_size)], bit_array::word_type(0)); + std::fill(&storage[0], &storage[Words(m_size)], bit_array::word_type(0)); } } @@ -211,15 +225,18 @@ constexpr void bit_array::fill(value_type bit_val) noexcept /* * Element Access */ -constexpr typename bit_array::reference bit_array::operator[](size_type pos) { +template +constexpr typename bit_array::reference bit_array::operator[](size_type pos) { return begin()[pos]; } -constexpr typename bit_array::const_reference bit_array::operator[](size_type pos) const { +template +constexpr typename bit_array::const_reference bit_array::operator[](size_type pos) const { return begin()[pos]; } -constexpr typename bit_array::reference bit_array::at(size_type pos) { +template +constexpr typename bit_array::reference bit_array::at(size_type pos) { if (pos < size()) { return begin()[pos]; } else { @@ -227,7 +244,8 @@ constexpr typename bit_array::reference bit_array::const_reference bit_array::at(size_type pos) const { +template +constexpr typename bit_array::const_reference bit_array::at(size_type pos) const { if (pos < size()) { return begin()[pos]; } else { @@ -235,62 +253,77 @@ constexpr typename bit_array::const_reference bit_array::reference bit_array::front() { +template +constexpr typename bit_array::reference bit_array::front() { return begin()[0]; } -constexpr typename bit_array::const_reference bit_array::front() const { +template +constexpr typename bit_array::const_reference bit_array::front() const { return begin()[0]; } -constexpr typename bit_array::reference bit_array::back() { +template +constexpr typename bit_array::reference bit_array::back() { return begin()[size() - 1]; } -constexpr typename bit_array::const_reference bit_array::back() const { +template +constexpr typename bit_array::const_reference bit_array::back() const { return begin()[size() - 1]; } -constexpr typename bit_array::word_type* bit_array::data() noexcept { +template +constexpr typename bit_array::word_type* bit_array::data() noexcept { return size() ? storage.get() : nullptr; } -constexpr const typename bit_array::word_type* bit_array::data() const noexcept { +template +constexpr const typename bit_array::word_type* bit_array::data() const noexcept { return size() ? storage.get() : nullptr; } // -------------------------------------------------------------------------- // -constexpr typename bit_array::size_type bit_array::size() const noexcept { return m_size; } +template +constexpr typename bit_array::size_type bit_array::size() const noexcept { return m_size; } -constexpr typename bit_array::size_type bit_array::max_size() const noexcept { return size(); } +template +constexpr typename bit_array::size_type bit_array::max_size() const noexcept { return size(); } -constexpr bool bit_array::empty() const noexcept { return 0 == size(); } +template +constexpr bool bit_array::empty() const noexcept { return 0 == size(); } // Iterators // -------------------------------------------------------------------------- // -constexpr typename bit_array::iterator bit_array::begin() noexcept { +template +constexpr typename bit_array::iterator bit_array::begin() noexcept { return iterator(storage.get()); } -constexpr typename bit_array::iterator bit_array::end() noexcept { +template +constexpr typename bit_array::iterator bit_array::end() noexcept { return begin() + size(); } -constexpr typename bit_array::const_iterator bit_array::begin() const noexcept { +template +constexpr typename bit_array::const_iterator bit_array::begin() const noexcept { return const_iterator(storage.get()); } -constexpr typename bit_array::const_iterator bit_array::end() const noexcept { +template +constexpr typename bit_array::const_iterator bit_array::end() const noexcept { return const_iterator(storage.get()) + size(); } -constexpr typename bit_array::const_iterator bit_array::cbegin() const noexcept { +template +constexpr typename bit_array::const_iterator bit_array::cbegin() const noexcept { return const_iterator(storage.get()); } -constexpr typename bit_array::const_iterator bit_array::cend() const noexcept { +template +constexpr typename bit_array::const_iterator bit_array::cend() const noexcept { return const_iterator(storage.get()) + size(); } From 39adffe45cc72ebd88a21bc665db58973225b9dd Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Mon, 24 Mar 2025 19:35:21 -0400 Subject: [PATCH 19/85] Alignment as template type instead of type --- include/bitlib/bit-containers/bit_array.hpp | 66 +++++++------- .../bit_array_dynamic_extent.hpp | 89 ++++++++++--------- 2 files changed, 81 insertions(+), 74 deletions(-) diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index 3d820197..731534ad 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -30,7 +30,7 @@ namespace bit { // ========================================================================== // -template +template class bit_array { public: static constexpr std::size_t bits = N; @@ -44,9 +44,9 @@ class bit_array { using const_pointer = const pointer; private: - static constexpr std::size_t Words = (N * bitsof() + bitsof() - 1) / bitsof(); + static constexpr std::size_t Words = (N * bitsof() + bitsof() - 1) / bitsof(); - alignas(alignof(V)) std::array storage; + alignas(V) std::array storage; public: using iterator = bit_iterator::iterator>; @@ -137,15 +137,15 @@ template bit_array(word_type*, std::size_t) -> bit_array; #endif -template +template constexpr bit_array::bit_array() noexcept : storage{} {} -template +template constexpr bit_array::bit_array(bit_array::value_type bit_val) : storage{} { fill(bit_val); } -template +template constexpr void bit_array::fill(bit_array::value_type bit_val) noexcept { if (bit_val) { std::fill(storage.begin(), storage.end(), bit_array::word_type(-1)); @@ -154,7 +154,7 @@ constexpr void bit_array::fill(bit_array::value_type bit_val) } } -template +template constexpr bit_array::bit_array(const std::initializer_list init) { if(init.size() != bitsof(*this)) [[unlikely]] { throw std::invalid_argument("initialize_list contains an invalid number of bits for bit_array."); @@ -162,7 +162,7 @@ constexpr bit_array::bit_array(const std::initializer_list std::copy(init.begin(), init.end(), this->begin()); } -template +template constexpr bit_array::bit_array(const std::initializer_list init) { if(init.size() != bitsof(*this)) [[unlikely]] { throw std::invalid_argument("initialize_list contains an invalid number of bits for bit_array."); @@ -170,12 +170,12 @@ constexpr bit_array::bit_array(const std::initializer_list init) std::copy(init.begin(), init.end(), this->begin()); } -template +template constexpr bit_array::bit_array(const std::initializer_list init) : storage(init) { static_assert(init.size() == storage.size()); } -template +template constexpr bit_array::bit_array(const std::string_view s) { if(bitsof(*this) != (std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))) [[unlikely]] { throw std::invalid_argument("String contains an invalid number of bits for bit_array."); @@ -190,22 +190,22 @@ constexpr bit_array::bit_array(const std::string_view s) { } } -template +template constexpr bit_array::bit_array(const bit_array&& other) noexcept : storage(other.storage) {} -template +template constexpr bit_array& bit_array::operator=(bit_array&& other) noexcept { std::copy(other.storage.begin(), other.storage.end(), storage.begin()); return *this; } -template +template constexpr bool bit_array::operator==(const bit_array& other) const noexcept { return equal(begin(), end(), other.begin()); } -template +template constexpr void bit_array::swap(bit_array& other) noexcept { std::swap(this->storage, other.storage); } @@ -214,17 +214,17 @@ constexpr void bit_array::swap(bit_array& other) noexcept { /* * Element Access */ -template +template constexpr typename bit_array::reference bit_array::operator[](size_type pos) { return begin()[pos]; } -template +template constexpr typename bit_array::const_reference bit_array::operator[](size_type pos) const { return begin()[pos]; } -template +template constexpr typename bit_array::reference bit_array::at(size_type pos) { if (pos < size()) { return begin()[pos]; @@ -233,7 +233,7 @@ constexpr typename bit_array::reference bit_array::at(size_typ } } -template +template constexpr typename bit_array::const_reference bit_array::at(size_type pos) const { if (pos < size()) { return begin()[pos]; @@ -242,76 +242,76 @@ constexpr typename bit_array::const_reference bit_array::at(si } } -template +template constexpr typename bit_array::reference bit_array::front() { return begin()[0]; } -template +template constexpr typename bit_array::const_reference bit_array::front() const { return begin()[0]; } -template +template constexpr typename bit_array::reference bit_array::back() { return begin()[size()-1]; } -template +template constexpr typename bit_array::const_reference bit_array::back() const { return begin()[size()-1]; } -template +template constexpr typename bit_array::word_type* bit_array::data() noexcept { return size() ? storage.data() : nullptr; } -template +template constexpr const typename bit_array::word_type* bit_array::data() const noexcept { return size() ? storage.data() : nullptr; } // -------------------------------------------------------------------------- // -template +template constexpr typename bit_array::size_type bit_array::size() const noexcept { return N; } -template +template constexpr typename bit_array::size_type bit_array::max_size() const noexcept { return size(); } -template +template constexpr bool bit_array::empty() const noexcept { return 0 == size(); } // Iterators // -------------------------------------------------------------------------- // -template +template constexpr typename bit_array::iterator bit_array::begin() noexcept { return iterator(storage.begin()); } -template +template constexpr typename bit_array::iterator bit_array::end() noexcept { return begin() + size(); } -template +template constexpr typename bit_array::const_iterator bit_array::begin() const noexcept { return const_iterator(storage.begin()); } -template +template constexpr typename bit_array::const_iterator bit_array::end() const noexcept { return const_iterator(storage.begin()) + size(); } -template +template constexpr typename bit_array::const_iterator bit_array::cbegin() const noexcept { return const_iterator(storage.begin()); } -template +template constexpr typename bit_array::const_iterator bit_array::cend() const noexcept { return const_iterator(storage.begin()) + size(); } diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index 4ba62ace..a06c02cc 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -23,7 +23,7 @@ namespace bit { -template +template class bit_array { public: using word_type = W; @@ -40,7 +40,7 @@ class bit_array { private: size_type m_size; std::unique_ptr storage; - static constexpr std::size_t Words(std::size_t N) { return (N * bitsof() + bitsof() - 1) / bitsof(); }; + static constexpr std::size_t Words(std::size_t N) { return (N * bitsof() + bitsof() - 1) / bitsof(); }; public: /* @@ -113,42 +113,47 @@ static_assert(bit_contiguous_range>, "bit_array<> does not satisfy b static_assert(bit_contiguous_sized_range>, "bit_array<> does not satisfy bit_contiguous_sized_range concept!"); #endif -template +template constexpr bit_array::bit_array() noexcept : m_size(0), storage(std::unique_ptr(nullptr, &std::free)) { } -template +template constexpr bit_array::bit_array(size_type size) : m_size(size), - storage(std::unique_ptr(static_cast(std::aligned_alloc(alignof(V), sizeof(word_type) * Words(m_size))), &std::free)) { + storage(std::unique_ptr( + static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { } -template +template constexpr bit_array::bit_array(size_type size, value_type bit_val) : m_size(size), - storage(std::unique_ptr(static_cast(std::aligned_alloc(alignof(V), sizeof(word_type) * Words(m_size))), &std::free)) { + storage(std::unique_ptr( + static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { fill(bit_val); } -template +template constexpr bit_array::bit_array(const bit_array& other) : m_size(other.size()), - storage(std::unique_ptr(static_cast(std::aligned_alloc(alignof(V), sizeof(word_type) * Words(m_size))), &std::free)) { + storage(std::unique_ptr( + static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { std::copy(other.begin(), other.end(), this->begin()); } -template +template constexpr bit_array::bit_array(const bit_array&& other) : m_size(other.size()), - storage(std::unique_ptr(static_cast(std::aligned_alloc(alignof(V), sizeof(word_type) * Words(m_size))), &std::free)) { + storage(std::unique_ptr( + static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { std::copy(other.begin(), other.end(), this->begin()); } -template +template constexpr bit_array::bit_array(const std::initializer_list init) : m_size(init.size()), - storage(std::unique_ptr(static_cast(std::aligned_alloc(alignof(V), sizeof(word_type) * Words(m_size))), &std::free)) { + storage(std::unique_ptr( + static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { std::copy(init.begin(), init.end(), this->begin()); } @@ -162,17 +167,19 @@ constexpr bit_array::bit_array(const std::initializer_lis } #endif -template +template constexpr bit_array::bit_array(const std::initializer_list init) : m_size(bitsof() * init.size()), - storage(std::unique_ptr(static_cast(std::aligned_alloc(alignof(V), sizeof(word_type) * Words(m_size))), &std::free)) { + storage(std::unique_ptr( + static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { std::copy(init.begin(), init.end(), &storage[0]); } -template +template constexpr bit_array::bit_array(const std::string_view s) : m_size((std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))), - storage(std::unique_ptr(static_cast(std::aligned_alloc(alignof(V), sizeof(word_type) * Words(m_size))), &std::free)) { + storage(std::unique_ptr( + static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { size_type i = 0; for (char c : s) { if (c == '0') { @@ -183,7 +190,7 @@ constexpr bit_array::bit_array(const std::string_view } } -template +template constexpr bit_array& bit_array::operator=(const bit_array& other) { if (nullptr == storage.get() || m_size != other.m_size) { throw std::invalid_argument("Cannot reassign bit_array size"); @@ -192,7 +199,7 @@ constexpr bit_array& bit_array +template constexpr bit_array& bit_array::operator=(bit_array&& other) { if (nullptr == storage.get() || m_size != other.m_size) { throw std::invalid_argument("Cannot reassign bit_array size"); @@ -201,18 +208,18 @@ constexpr bit_array& bit_array +template constexpr bool bit_array::operator==(const bit_array& other) const noexcept { return equal(begin(), end(), other.begin()); } -template +template constexpr void bit_array::swap(bit_array& other) noexcept { std::swap(this->storage, other.storage); std::swap(this->m_size, other.m_size); } -template +template constexpr void bit_array::fill(value_type bit_val) noexcept { if (bit_val) { std::fill(&storage[0], &storage[Words(m_size)], bit_array::word_type(-1)); @@ -225,17 +232,17 @@ constexpr void bit_array::fill(value_type bit_val) no /* * Element Access */ -template +template constexpr typename bit_array::reference bit_array::operator[](size_type pos) { return begin()[pos]; } -template +template constexpr typename bit_array::const_reference bit_array::operator[](size_type pos) const { return begin()[pos]; } -template +template constexpr typename bit_array::reference bit_array::at(size_type pos) { if (pos < size()) { return begin()[pos]; @@ -244,7 +251,7 @@ constexpr typename bit_array::reference bit_array +template constexpr typename bit_array::const_reference bit_array::at(size_type pos) const { if (pos < size()) { return begin()[pos]; @@ -253,76 +260,76 @@ constexpr typename bit_array::const_reference bit_arr } } -template +template constexpr typename bit_array::reference bit_array::front() { return begin()[0]; } -template +template constexpr typename bit_array::const_reference bit_array::front() const { return begin()[0]; } -template +template constexpr typename bit_array::reference bit_array::back() { return begin()[size() - 1]; } -template +template constexpr typename bit_array::const_reference bit_array::back() const { return begin()[size() - 1]; } -template +template constexpr typename bit_array::word_type* bit_array::data() noexcept { return size() ? storage.get() : nullptr; } -template +template constexpr const typename bit_array::word_type* bit_array::data() const noexcept { return size() ? storage.get() : nullptr; } // -------------------------------------------------------------------------- // -template +template constexpr typename bit_array::size_type bit_array::size() const noexcept { return m_size; } -template +template constexpr typename bit_array::size_type bit_array::max_size() const noexcept { return size(); } -template +template constexpr bool bit_array::empty() const noexcept { return 0 == size(); } // Iterators // -------------------------------------------------------------------------- // -template +template constexpr typename bit_array::iterator bit_array::begin() noexcept { return iterator(storage.get()); } -template +template constexpr typename bit_array::iterator bit_array::end() noexcept { return begin() + size(); } -template +template constexpr typename bit_array::const_iterator bit_array::begin() const noexcept { return const_iterator(storage.get()); } -template +template constexpr typename bit_array::const_iterator bit_array::end() const noexcept { return const_iterator(storage.get()) + size(); } -template +template constexpr typename bit_array::const_iterator bit_array::cbegin() const noexcept { return const_iterator(storage.get()); } -template +template constexpr typename bit_array::const_iterator bit_array::cend() const noexcept { return const_iterator(storage.get()) + size(); } From cf879184de17202ee1a6b1a7e5c6830f319c684a Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Wed, 26 Mar 2025 07:59:37 -0400 Subject: [PATCH 20/85] Simple backup copy for differing words --- include/bitlib/bit-algorithms/copy.hpp | 11 ++++++- test/src/test-usecase.cpp | 43 ++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/include/bitlib/bit-algorithms/copy.hpp b/include/bitlib/bit-algorithms/copy.hpp index b5761f25..d4846026 100644 --- a/include/bitlib/bit-algorithms/copy.hpp +++ b/include/bitlib/bit-algorithms/copy.hpp @@ -36,13 +36,22 @@ constexpr bit_iterator copy(bit_iterator first // Types and constants using dst_word_type = typename bit_iterator::word_type; using src_word_type = typename bit_iterator::word_type; + + // This checks for differing word types and uses an unoptimized copy in that event + if (!::std::is_same::value) { + while (first != last) { + *d_first = *first; + first++; + d_first++; + } + return d_first; + } using word_type = dst_word_type; using size_type = typename bit_iterator::size_type; constexpr size_type digits = binary_digits::value; // Assertions _assert_range_viability(first, last); - static_assert(::std::is_same::value, "Underlying word types must be equal"); if (first == last) return d_first; diff --git a/test/src/test-usecase.cpp b/test/src/test-usecase.cpp index 072bcc0e..8962965b 100644 --- a/test/src/test-usecase.cpp +++ b/test/src/test-usecase.cpp @@ -1,12 +1,43 @@ #include "bitlib/bitlib.hpp" // Third-party libraries +#include + #include "gtest/gtest.h" -/* TEST(UseCaseTest, VectorAppend) { - bit::bit_vector vec; - vec.push_back(bit::bit0); - uint16_t test = 0xBEEF; - vec.append_range(bit::bit_span(test)); + bit::bit_vector vec; + uint16_t test = 0xBEEF; + vec.append_range(bit::bit_span(test)); + EXPECT_EQ(vec.size(), 16); + EXPECT_EQ(vec[0], bit::bit1); + EXPECT_EQ(vec[4], bit::bit0); + EXPECT_EQ(vec[8], bit::bit0); + EXPECT_EQ(vec[12], bit::bit1); + vec.push_back(bit::bit0); + EXPECT_EQ(vec.size(), 17); + EXPECT_EQ(vec[16], bit::bit0); + + uint32_t test32 = 0xABBADABA; + vec.append_range(bit::bit_span(test32)); + EXPECT_EQ(vec[17 + 0], bit::bit0); + EXPECT_EQ(vec[17 + 4], bit::bit1); + EXPECT_EQ(vec[17 + 8], bit::bit0); + EXPECT_EQ(vec[17 + 12], bit::bit1); + EXPECT_EQ(vec[17 + 16], bit::bit0); + EXPECT_EQ(vec[17 + 20], bit::bit1); + EXPECT_EQ(vec[17 + 24], bit::bit1); + EXPECT_EQ(vec[17 + 28], bit::bit0); +} + +/* +c++23 +TEST(UseCaseTest, StartLifeAs) { + uint16_t test = 0xBEEF; + auto ba = std::start_lifetime_as>(&test); + EXPECT_EQ(ba.size(), 16); + EXPECT_EQ(ba[0], bit::bit1); + EXPECT_EQ(ba[4], bit::bit0); + EXPECT_EQ(ba[8], bit::bit0); + EXPECT_EQ(ba[12], bit::bit1); } - */ \ No newline at end of file +*/ From 7dbb7df871039769e3ac1a771873b27395e3f4e0 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 28 Mar 2025 19:37:54 -0400 Subject: [PATCH 21/85] Order template types like std::array to allow for nested dynamic array<> --- include/bitlib/bit-containers/bit_array.hpp | 168 +++++++------ .../bit_array_dynamic_extent.hpp | 200 ++++++++------- include/bitlib/bit-iterator/bit_details.hpp | 15 +- test/src/test-array.cpp | 228 +++++++++--------- 4 files changed, 334 insertions(+), 277 deletions(-) diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index 731534ad..07fa433c 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -30,17 +30,20 @@ namespace bit { // ========================================================================== // -template +template , uint8_t, T>::type> class bit_array { public: static constexpr std::size_t bits = N; using word_type = W; - using value_type = bit_value; + using value_type = T; using size_type = std::size_t; using difference_type = std::ptrdiff_t; - using reference = bit_reference; - using const_reference = const bit_reference; - using pointer = bit_pointer; + using reference = typename std::conditional, bit_reference, T&>::type; + using const_reference = typename std::conditional, const bit_reference, const T&>::type; + using pointer = typename std::conditional, bit_pointer, T&>::type; using const_pointer = const pointer; private: @@ -49,18 +52,21 @@ class bit_array { alignas(V) std::array storage; public: - using iterator = bit_iterator::iterator>; - using const_iterator = bit_iterator::const_iterator>; - - public: + using iterator = typename std::conditional, + bit_iterator::iterator>, + typename std::array::iterator>::type; + using const_iterator = typename std::conditional, + bit_iterator::const_iterator>, + typename std::array::const_iterator>::type; /* * Constructors, copies and moves... */ constexpr bit_array() noexcept; constexpr bit_array(value_type bit_val); - constexpr bit_array(const bit_array& other) = default; - constexpr bit_array(const bit_array&& other) noexcept; - constexpr bit_array(const std::initializer_list init); + constexpr bit_array(const bit_array& other) = default; + constexpr bit_array(const bit_array&& other) noexcept; + constexpr bit_array(const std::initializer_list init) + requires(!std::is_same_v); constexpr bit_array(const std::initializer_list init); constexpr bit_array(const std::initializer_list init); constexpr bit_array(const std::string_view s); @@ -69,10 +75,10 @@ class bit_array { /* * Assignment */ - constexpr bit_array& operator=(const bit_array& other) = default; - constexpr bit_array& operator=(bit_array&& other) noexcept; + constexpr bit_array& operator=(const bit_array& other) = default; + constexpr bit_array& operator=(bit_array&& other) noexcept; - constexpr bool operator==(const bit_array& other) const noexcept; + constexpr bool operator==(const bit_array& other) const noexcept; /* * Element Access @@ -109,12 +115,12 @@ class bit_array { * Operations */ constexpr void fill(value_type bit_val) noexcept; - constexpr void swap(bit_array& other) noexcept; + constexpr void swap(bit_array& other) noexcept; //constexpr std::synth-three-way-result operator<=>() const noexcept; }; -static_assert(bit_range>, "bit_array does not satisfy bit_range concept!"); -static_assert(bit_sized_range>, "bit_array does not satisfy bit_sized_range concept!"); +static_assert(bit_range>, "bit_array does not satisfy bit_range concept!"); +static_assert(bit_sized_range>, "bit_array does not satisfy bit_sized_range concept!"); #ifdef CONTIGUOUS_RANGE static_assert(bit_contiguous_range>, "bit_array does not satisfy bit_contiguous_range concept!"); static_assert(bit_contiguous_sized_range>, "bit_array does not satisfy bit_contiguous_sized_range concept!"); @@ -137,46 +143,48 @@ template bit_array(word_type*, std::size_t) -> bit_array; #endif -template -constexpr bit_array::bit_array() noexcept : storage{} {} +template +constexpr bit_array::bit_array() noexcept : storage{} {} -template -constexpr bit_array::bit_array(bit_array::value_type bit_val) : storage{} { +template +constexpr bit_array::bit_array(bit_array::value_type bit_val) : storage{} { fill(bit_val); } -template -constexpr void bit_array::fill(bit_array::value_type bit_val) noexcept { +template +constexpr void bit_array::fill(bit_array::value_type bit_val) noexcept { if (bit_val) { - std::fill(storage.begin(), storage.end(), bit_array::word_type(-1)); + std::fill(storage.begin(), storage.end(), bit_array::word_type(-1)); } else { - std::fill(storage.begin(), storage.end(), bit_array::word_type(0)); + std::fill(storage.begin(), storage.end(), bit_array::word_type(0)); } } -template -constexpr bit_array::bit_array(const std::initializer_list init) { +template +constexpr bit_array::bit_array(const std::initializer_list init) + requires(!std::is_same_v) +{ if(init.size() != bitsof(*this)) [[unlikely]] { throw std::invalid_argument("initialize_list contains an invalid number of bits for bit_array."); } std::copy(init.begin(), init.end(), this->begin()); } -template -constexpr bit_array::bit_array(const std::initializer_list init) { +template +constexpr bit_array::bit_array(const std::initializer_list init) { if(init.size() != bitsof(*this)) [[unlikely]] { throw std::invalid_argument("initialize_list contains an invalid number of bits for bit_array."); } std::copy(init.begin(), init.end(), this->begin()); } -template -constexpr bit_array::bit_array(const std::initializer_list init) : storage(init) { +template +constexpr bit_array::bit_array(const std::initializer_list init) : storage(init) { static_assert(init.size() == storage.size()); } -template -constexpr bit_array::bit_array(const std::string_view s) { +template +constexpr bit_array::bit_array(const std::string_view s) { if(bitsof(*this) != (std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))) [[unlikely]] { throw std::invalid_argument("String contains an invalid number of bits for bit_array."); }; @@ -190,23 +198,23 @@ constexpr bit_array::bit_array(const std::string_view s) { } } -template -constexpr bit_array::bit_array(const bit_array&& other) noexcept +template +constexpr bit_array::bit_array(const bit_array&& other) noexcept : storage(other.storage) {} -template -constexpr bit_array& bit_array::operator=(bit_array&& other) noexcept { +template +constexpr bit_array& bit_array::operator=(bit_array&& other) noexcept { std::copy(other.storage.begin(), other.storage.end(), storage.begin()); return *this; } -template -constexpr bool bit_array::operator==(const bit_array& other) const noexcept { +template +constexpr bool bit_array::operator==(const bit_array& other) const noexcept { return equal(begin(), end(), other.begin()); } -template -constexpr void bit_array::swap(bit_array& other) noexcept { +template +constexpr void bit_array::swap(bit_array& other) noexcept { std::swap(this->storage, other.storage); } @@ -214,18 +222,18 @@ constexpr void bit_array::swap(bit_array& other) noexcept { /* * Element Access */ -template -constexpr typename bit_array::reference bit_array::operator[](size_type pos) { +template +constexpr typename bit_array::reference bit_array::operator[](size_type pos) { return begin()[pos]; } -template -constexpr typename bit_array::const_reference bit_array::operator[](size_type pos) const { +template +constexpr typename bit_array::const_reference bit_array::operator[](size_type pos) const { return begin()[pos]; } -template -constexpr typename bit_array::reference bit_array::at(size_type pos) { +template +constexpr typename bit_array::reference bit_array::at(size_type pos) { if (pos < size()) { return begin()[pos]; } else { @@ -233,8 +241,8 @@ constexpr typename bit_array::reference bit_array::at(size_typ } } -template -constexpr typename bit_array::const_reference bit_array::at(size_type pos) const { +template +constexpr typename bit_array::const_reference bit_array::at(size_type pos) const { if (pos < size()) { return begin()[pos]; } else { @@ -242,77 +250,77 @@ constexpr typename bit_array::const_reference bit_array::at(si } } -template -constexpr typename bit_array::reference bit_array::front() { +template +constexpr typename bit_array::reference bit_array::front() { return begin()[0]; } -template -constexpr typename bit_array::const_reference bit_array::front() const { +template +constexpr typename bit_array::const_reference bit_array::front() const { return begin()[0]; } -template -constexpr typename bit_array::reference bit_array::back() { +template +constexpr typename bit_array::reference bit_array::back() { return begin()[size()-1]; } -template -constexpr typename bit_array::const_reference bit_array::back() const { +template +constexpr typename bit_array::const_reference bit_array::back() const { return begin()[size()-1]; } -template -constexpr typename bit_array::word_type* bit_array::data() noexcept { +template +constexpr typename bit_array::word_type* bit_array::data() noexcept { return size() ? storage.data() : nullptr; } -template -constexpr const typename bit_array::word_type* bit_array::data() const noexcept { +template +constexpr const typename bit_array::word_type* bit_array::data() const noexcept { return size() ? storage.data() : nullptr; } // -------------------------------------------------------------------------- // -template -constexpr typename bit_array::size_type bit_array::size() const noexcept { return N; } +template +constexpr typename bit_array::size_type bit_array::size() const noexcept { return N; } -template -constexpr typename bit_array::size_type bit_array::max_size() const noexcept { return size(); } +template +constexpr typename bit_array::size_type bit_array::max_size() const noexcept { return size(); } -template -constexpr bool bit_array::empty() const noexcept { return 0 == size(); } +template +constexpr bool bit_array::empty() const noexcept { return 0 == size(); } // Iterators // -------------------------------------------------------------------------- // -template -constexpr typename bit_array::iterator bit_array::begin() noexcept { +template +constexpr typename bit_array::iterator bit_array::begin() noexcept { return iterator(storage.begin()); } -template -constexpr typename bit_array::iterator bit_array::end() noexcept { +template +constexpr typename bit_array::iterator bit_array::end() noexcept { return begin() + size(); } -template -constexpr typename bit_array::const_iterator bit_array::begin() const noexcept { +template +constexpr typename bit_array::const_iterator bit_array::begin() const noexcept { return const_iterator(storage.begin()); } -template -constexpr typename bit_array::const_iterator bit_array::end() const noexcept { +template +constexpr typename bit_array::const_iterator bit_array::end() const noexcept { return const_iterator(storage.begin()) + size(); } -template -constexpr typename bit_array::const_iterator bit_array::cbegin() const noexcept { +template +constexpr typename bit_array::const_iterator bit_array::cbegin() const noexcept { return const_iterator(storage.begin()); } -template -constexpr typename bit_array::const_iterator bit_array::cend() const noexcept { +template +constexpr typename bit_array::const_iterator bit_array::cend() const noexcept { return const_iterator(storage.begin()) + size(); } diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index a06c02cc..84240197 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -23,19 +23,23 @@ namespace bit { -template -class bit_array { +template +class bit_array { public: using word_type = W; - using value_type = bit_value; + using value_type = T; using size_type = std::size_t; using difference_type = std::ptrdiff_t; - using reference = bit_reference; - using const_reference = const bit_reference; - using pointer = bit_pointer; + using reference = typename std::conditional, bit_reference, T&>::type; + using const_reference = typename std::conditional, const bit_reference, const T&>::type; + using pointer = typename std::conditional, bit_pointer, T&>::type; using const_pointer = const pointer; - using iterator = bit_iterator; - using const_iterator = bit_iterator; + using iterator = typename std::conditional, + bit_iterator, + word_type*>::type; + using const_iterator = typename std::conditional, + bit_iterator, + const word_type*>::type; private: size_type m_size; @@ -49,9 +53,10 @@ class bit_array { constexpr bit_array() noexcept; constexpr bit_array(size_type size); constexpr bit_array(size_type size, value_type bit_val); - constexpr bit_array(const bit_array& other); - constexpr bit_array(const bit_array&& other); - constexpr bit_array(const std::initializer_list init); + constexpr bit_array(const bit_array& other); + constexpr bit_array(const bit_array&& other); + constexpr bit_array(const std::initializer_list init) + requires(!std::is_same_v); #if 0 constexpr bit_array(const std::initializer_list init); #endif @@ -62,10 +67,10 @@ class bit_array { /* * Assignment */ - constexpr bit_array& operator=(const bit_array& other); - constexpr bit_array& operator=(bit_array&& other); + constexpr bit_array& operator=(const bit_array& other); + constexpr bit_array& operator=(bit_array&& other); - constexpr bool operator==(const bit_array& other) const noexcept; + constexpr bool operator==(const bit_array& other) const noexcept; /* * Element Access @@ -102,7 +107,7 @@ class bit_array { * Operations */ constexpr void fill(value_type bit_val) noexcept; - constexpr void swap(bit_array& other) noexcept; + constexpr void swap(bit_array& other) noexcept; //constexpr std::synth-three-way-result operator<=>() const noexcept; }; @@ -113,44 +118,62 @@ static_assert(bit_contiguous_range>, "bit_array<> does not satisfy b static_assert(bit_contiguous_sized_range>, "bit_array<> does not satisfy bit_contiguous_sized_range concept!"); #endif -template -constexpr bit_array::bit_array() noexcept +template +constexpr bit_array::bit_array() noexcept : m_size(0), storage(std::unique_ptr(nullptr, &std::free)) { } -template -constexpr bit_array::bit_array(size_type size) +template +constexpr bit_array::bit_array(size_type size) : m_size(size), storage(std::unique_ptr( static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { + //std::uninitialized_fill_n(this->begin(), Words(m_size), word_type()); + for (std::size_t i = 0; i < Words(m_size); ++i) { + new (storage.get() + i) word_type(); + } } -template -constexpr bit_array::bit_array(size_type size, value_type bit_val) +template +constexpr bit_array::bit_array(size_type size, value_type bit_val) : m_size(size), storage(std::unique_ptr( static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { - fill(bit_val); + if constexpr (std::is_same::value) { + for (std::size_t i = 0; i < Words(m_size); ++i) { + new (storage.get() + i) word_type(bit_val); + } + } else { + for (std::size_t i = 0; i < Words(m_size); ++i) { + new (storage.get() + i) word_type(); + } + fill(bit_val); + } } -template -constexpr bit_array::bit_array(const bit_array& other) +template +constexpr bit_array::bit_array(const bit_array& other) : m_size(other.size()), storage(std::unique_ptr( static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { - std::copy(other.begin(), other.end(), this->begin()); + for (std::size_t i = 0; i < Words(m_size); ++i) { + new (storage.get() + i) word_type(other.storage[i]); + } } -template -constexpr bit_array::bit_array(const bit_array&& other) +template +constexpr bit_array::bit_array(const bit_array&& other) : m_size(other.size()), storage(std::unique_ptr( static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { - std::copy(other.begin(), other.end(), this->begin()); + for (std::size_t i = 0; i < Words(m_size); ++i) { + new (storage.get() + i) word_type(other.storage[i]); + } } -template -constexpr bit_array::bit_array(const std::initializer_list init) +template +constexpr bit_array::bit_array(const std::initializer_list init) + requires(!std::is_same_v) : m_size(init.size()), storage(std::unique_ptr( static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { @@ -167,16 +190,20 @@ constexpr bit_array::bit_array(const std::initializer_lis } #endif -template -constexpr bit_array::bit_array(const std::initializer_list init) +template +constexpr bit_array::bit_array(const std::initializer_list init) : m_size(bitsof() * init.size()), storage(std::unique_ptr( static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { - std::copy(init.begin(), init.end(), &storage[0]); + std::size_t i = 0; + auto&& it = init.begin(); + for (; i < Words(m_size); ++i, ++it) { + new (storage.get() + i) word_type(*it); + } } -template -constexpr bit_array::bit_array(const std::string_view s) +template +constexpr bit_array::bit_array(const std::string_view s) : m_size((std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))), storage(std::unique_ptr( static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { @@ -190,8 +217,8 @@ constexpr bit_array::bit_array(const std::string_view } } -template -constexpr bit_array& bit_array::operator=(const bit_array& other) { +template +constexpr bit_array& bit_array::operator=(const bit_array& other) { if (nullptr == storage.get() || m_size != other.m_size) { throw std::invalid_argument("Cannot reassign bit_array size"); } @@ -199,8 +226,8 @@ constexpr bit_array& bit_array -constexpr bit_array& bit_array::operator=(bit_array&& other) { +template +constexpr bit_array& bit_array::operator=(bit_array&& other) { if (nullptr == storage.get() || m_size != other.m_size) { throw std::invalid_argument("Cannot reassign bit_array size"); } @@ -208,42 +235,43 @@ constexpr bit_array& bit_array -constexpr bool bit_array::operator==(const bit_array& other) const noexcept { +template +constexpr bool bit_array::operator==(const bit_array& other) const noexcept { return equal(begin(), end(), other.begin()); } -template -constexpr void bit_array::swap(bit_array& other) noexcept { +template +constexpr void bit_array::swap(bit_array& other) noexcept { std::swap(this->storage, other.storage); std::swap(this->m_size, other.m_size); } -template -constexpr void bit_array::fill(value_type bit_val) noexcept { +template +constexpr void bit_array::fill(value_type bit_val) noexcept { + std::fill(this->begin(), this->end(), bit_val); /* if (bit_val) { - std::fill(&storage[0], &storage[Words(m_size)], bit_array::word_type(-1)); + std::fill(&storage[0], &storage[Words(m_size)], bit_array::word_type(-1)); } else { - std::fill(&storage[0], &storage[Words(m_size)], bit_array::word_type(0)); - } + std::fill(&storage[0], &storage[Words(m_size)], bit_array::word_type(0)); + }*/ } // -------------------------------------------------------------------------- // /* * Element Access */ -template -constexpr typename bit_array::reference bit_array::operator[](size_type pos) { +template +constexpr typename bit_array::reference bit_array::operator[](size_type pos) { return begin()[pos]; } -template -constexpr typename bit_array::const_reference bit_array::operator[](size_type pos) const { +template +constexpr typename bit_array::const_reference bit_array::operator[](size_type pos) const { return begin()[pos]; } -template -constexpr typename bit_array::reference bit_array::at(size_type pos) { +template +constexpr typename bit_array::reference bit_array::at(size_type pos) { if (pos < size()) { return begin()[pos]; } else { @@ -251,8 +279,8 @@ constexpr typename bit_array::reference bit_array -constexpr typename bit_array::const_reference bit_array::at(size_type pos) const { +template +constexpr typename bit_array::const_reference bit_array::at(size_type pos) const { if (pos < size()) { return begin()[pos]; } else { @@ -260,77 +288,77 @@ constexpr typename bit_array::const_reference bit_arr } } -template -constexpr typename bit_array::reference bit_array::front() { +template +constexpr typename bit_array::reference bit_array::front() { return begin()[0]; } -template -constexpr typename bit_array::const_reference bit_array::front() const { +template +constexpr typename bit_array::const_reference bit_array::front() const { return begin()[0]; } -template -constexpr typename bit_array::reference bit_array::back() { +template +constexpr typename bit_array::reference bit_array::back() { return begin()[size() - 1]; } -template -constexpr typename bit_array::const_reference bit_array::back() const { +template +constexpr typename bit_array::const_reference bit_array::back() const { return begin()[size() - 1]; } -template -constexpr typename bit_array::word_type* bit_array::data() noexcept { +template +constexpr typename bit_array::word_type* bit_array::data() noexcept { return size() ? storage.get() : nullptr; } -template -constexpr const typename bit_array::word_type* bit_array::data() const noexcept { +template +constexpr const typename bit_array::word_type* bit_array::data() const noexcept { return size() ? storage.get() : nullptr; } // -------------------------------------------------------------------------- // -template -constexpr typename bit_array::size_type bit_array::size() const noexcept { return m_size; } +template +constexpr typename bit_array::size_type bit_array::size() const noexcept { return m_size; } -template -constexpr typename bit_array::size_type bit_array::max_size() const noexcept { return size(); } +template +constexpr typename bit_array::size_type bit_array::max_size() const noexcept { return size(); } -template -constexpr bool bit_array::empty() const noexcept { return 0 == size(); } +template +constexpr bool bit_array::empty() const noexcept { return 0 == size(); } // Iterators // -------------------------------------------------------------------------- // -template -constexpr typename bit_array::iterator bit_array::begin() noexcept { +template +constexpr typename bit_array::iterator bit_array::begin() noexcept { return iterator(storage.get()); } -template -constexpr typename bit_array::iterator bit_array::end() noexcept { +template +constexpr typename bit_array::iterator bit_array::end() noexcept { return begin() + size(); } -template -constexpr typename bit_array::const_iterator bit_array::begin() const noexcept { +template +constexpr typename bit_array::const_iterator bit_array::begin() const noexcept { return const_iterator(storage.get()); } -template -constexpr typename bit_array::const_iterator bit_array::end() const noexcept { +template +constexpr typename bit_array::const_iterator bit_array::end() const noexcept { return const_iterator(storage.get()) + size(); } -template -constexpr typename bit_array::const_iterator bit_array::cbegin() const noexcept { +template +constexpr typename bit_array::const_iterator bit_array::cbegin() const noexcept { return const_iterator(storage.get()); } -template -constexpr typename bit_array::const_iterator bit_array::cend() const noexcept { +template +constexpr typename bit_array::const_iterator bit_array::cend() const noexcept { return const_iterator(storage.get()) + size(); } diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index fd7ddbd7..873dd77d 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -66,7 +66,20 @@ template constexpr std::size_t binary_digits_v = binary_digits::value; /* ************************************************************************** */ - +template +using smallest_integral = std::conditional_t< + (sizeof(T) <= sizeof(std::uint8_t)), + std::uint8_t, + std::conditional_t< + (sizeof(T) <= sizeof(std::uint16_t)), + std::uint16_t, + std::conditional_t< + (sizeof(T) <= sizeof(std::uint32_t)), + std::uint32_t, + std::conditional_t< + (sizeof(T) <= sizeof(std::uint64_t)), + std::uint64_t, + T>>>>; /* *************** IMPLEMENTATION DETAILS: CV ITERATOR TRAITS *************** */ // Cv iterator traits structure definition diff --git a/test/src/test-array.cpp b/test/src/test-array.cpp index 53f45425..fcd5e797 100644 --- a/test/src/test-array.cpp +++ b/test/src/test-array.cpp @@ -13,7 +13,7 @@ */ // Tests the default c'tor. TEST(ArrayTest, DefaultConstructor) { - bit::bit_array<11> barr(bit::bit0); + bit::bit_array barr(bit::bit0); EXPECT_EQ(2, sizeof(barr)); EXPECT_EQ(11u, barr.size()); EXPECT_EQ(bit::bit0, barr[0]); @@ -30,54 +30,54 @@ TEST(ArrayTest, DefaultConstructor) { } TEST(ArrayTest, BitsOf) { - bit::bit_array<11> barr(bit::bit0); + bit::bit_array barr(bit::bit0); EXPECT_EQ(11u, bitsof(barr)); - EXPECT_EQ(11u, bitsof(bit::bit_array<11>())); + EXPECT_EQ(11u, bitsof(bit::bit_array())); } TEST(ArrayTest, BasicIteration) { // <-- LSB, apparently šŸ™„ - bit::bit_array<11> barr("0110_0101_110"); - int i=0; - for(const auto& bbit : barr) { - switch(10 - i++) { - case 0: EXPECT_EQ(bit::bit0, bbit); break; - case 1: EXPECT_EQ(bit::bit1, bbit); break; - case 2: EXPECT_EQ(bit::bit1, bbit); break; - case 3: EXPECT_EQ(bit::bit1, bbit); break; - case 4: EXPECT_EQ(bit::bit0, bbit); break; - case 5: EXPECT_EQ(bit::bit1, bbit); break; - case 6: EXPECT_EQ(bit::bit0, bbit); break; - case 7: EXPECT_EQ(bit::bit0, bbit); break; - case 8: EXPECT_EQ(bit::bit1, bbit); break; - case 9: EXPECT_EQ(bit::bit1, bbit); break; - case 10: EXPECT_EQ(bit::bit0, bbit); break; - } - } + bit::bit_array barr("0110_0101_110"); + int i = 0; + for (const auto& bbit : barr) { + switch (10 - i++) { + case 0: EXPECT_EQ(bit::bit0, bbit); break; + case 1: EXPECT_EQ(bit::bit1, bbit); break; + case 2: EXPECT_EQ(bit::bit1, bbit); break; + case 3: EXPECT_EQ(bit::bit1, bbit); break; + case 4: EXPECT_EQ(bit::bit0, bbit); break; + case 5: EXPECT_EQ(bit::bit1, bbit); break; + case 6: EXPECT_EQ(bit::bit0, bbit); break; + case 7: EXPECT_EQ(bit::bit0, bbit); break; + case 8: EXPECT_EQ(bit::bit1, bbit); break; + case 9: EXPECT_EQ(bit::bit1, bbit); break; + case 10: EXPECT_EQ(bit::bit0, bbit); break; + } + } } TEST(ArrayTest, ZeroSize) { - bit::bit_array<0> barr{}; - std::array foo{}; + bit::bit_array barr{}; + std::array foo{}; EXPECT_EQ(sizeof(foo), sizeof(barr)); EXPECT_EQ(0, bitsof(barr)); } // Test that the default constructor initializes all bits to false. TEST(ArrayTest, DefaultInitialization) { - bit::bit_array<8> ba; - for (size_t i = 0; i < ba.size(); ++i) { - EXPECT_FALSE(ba[i]) << "Bit " << i << " should be false by default"; - } + bit::bit_array ba; + for (size_t i = 0; i < ba.size(); ++i) { + EXPECT_FALSE(ba[i]) << "Bit " << i << " should be false by default"; + } } // Test the fill() method. TEST(ArrayTest, FillMethod) { - bit::bit_array<10> ba; - ba.fill(bit::bit_value(true)); - for (size_t i = 0; i < ba.size(); ++i) { - EXPECT_TRUE(ba[i]) << "Bit " << i << " should be true after fill(true)"; - } + bit::bit_array ba; + ba.fill(bit::bit_value(true)); + for (size_t i = 0; i < ba.size(); ++i) { + EXPECT_TRUE(ba[i]) << "Bit " << i << " should be true after fill(true)"; + } ba.fill(bit::bit_value(false)); for (size_t i = 0; i < ba.size(); ++i) { EXPECT_FALSE(ba[i]) << "Bit " << i << " should be false after fill(false)"; @@ -86,128 +86,128 @@ TEST(ArrayTest, FillMethod) { // Test element access via operator[] and at(), including out-of-range checking. TEST(ArrayTest, ElementAccess) { - bit::bit_array<5> ba; - ba.fill(bit::bit_value(false)); - ba[2] = bit::bit_value(true); - EXPECT_TRUE(ba.at(2)); - EXPECT_THROW(ba.at(5), std::out_of_range); + bit::bit_array ba; + ba.fill(bit::bit_value(false)); + ba[2] = bit::bit_value(true); + EXPECT_TRUE(ba.at(2)); + EXPECT_THROW(ba.at(5), std::out_of_range); } // Test front() and back() member functions. TEST(ArrayTest, FrontBackAccess) { - bit::bit_array<4> ba; - ba.fill(bit::bit_value(false)); - ba.front() = bit::bit_value(true); - ba.back() = bit::bit_value(true); - EXPECT_TRUE(ba.front()); - EXPECT_TRUE(ba.back()); + bit::bit_array ba; + ba.fill(bit::bit_value(false)); + ba.front() = bit::bit_value(true); + ba.back() = bit::bit_value(true); + EXPECT_TRUE(ba.front()); + EXPECT_TRUE(ba.back()); } // Test iterator functionality (both non-const and range-based). TEST(ArrayTest, IteratorFunctionality) { - bit::bit_array<4> ba; - ba.fill(bit::bit_value(false)); - int index = 0; - for (auto it = ba.begin(); it != ba.end(); ++it) { - // Change the second element using the iterator. - if (index == 1) { - *it = bit::bit_value(true); - } - ++index; + bit::bit_array ba; + ba.fill(bit::bit_value(false)); + int index = 0; + for (auto it = ba.begin(); it != ba.end(); ++it) { + // Change the second element using the iterator. + if (index == 1) { + *it = bit::bit_value(true); } + ++index; + } EXPECT_TRUE(ba[1]); } // Test const_iterator functionality. TEST(ArrayTest, ConstIteratorFunctionality) { - bit::bit_array<4> ba; - ba.fill(bit::bit_value(true)); - const bit::bit_array<4>& const_ba = ba; - for (auto it = const_ba.begin(); it != const_ba.end(); ++it) { - EXPECT_TRUE(*it); - } + bit::bit_array ba; + ba.fill(bit::bit_value(true)); + const bit::bit_array& const_ba = ba; + for (auto it = const_ba.begin(); it != const_ba.end(); ++it) { + EXPECT_TRUE(*it); + } } // Test the swap() member function. TEST(ArrayTest, SwapFunctionality) { - bit::bit_array<4> ba1, ba2; - ba1.fill(bit::bit_value(false)); - ba2.fill(bit::bit_value(true)); - ba1.swap(ba2); - for (size_t i = 0; i < ba1.size(); ++i) { - EXPECT_TRUE(ba1[i]) << "After swap, ba1[" << i << "] should be true"; - EXPECT_FALSE(ba2[i]) << "After swap, ba2[" << i << "] should be false"; - } + bit::bit_array ba1, ba2; + ba1.fill(bit::bit_value(false)); + ba2.fill(bit::bit_value(true)); + ba1.swap(ba2); + for (size_t i = 0; i < ba1.size(); ++i) { + EXPECT_TRUE(ba1[i]) << "After swap, ba1[" << i << "] should be true"; + EXPECT_FALSE(ba2[i]) << "After swap, ba2[" << i << "] should be false"; + } } // Test comparison operators (== and !=). TEST(ArrayTest, ComparisonOperators) { - bit::bit_array<5> ba1 = { bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1 }; - bit::bit_array<5> ba2 = { bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1 }; - EXPECT_EQ(ba1, ba2); - ba2[2] = bit::bit_value(true); // Change one element - EXPECT_NE(ba1, ba2); + bit::bit_array ba1 = {bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; + bit::bit_array ba2 = {bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; + EXPECT_EQ(ba1, ba2); + ba2[2] = bit::bit_value(true); // Change one element + EXPECT_NE(ba1, ba2); } // Test the data() method to access the underlying storage. TEST(ArrayTest, DataAccess) { - bit::bit_array<8> ba; - ba.fill(bit::bit_value(false)); - // Assume data() returns a pointer to a boolean array. - uint8_t* data_ptr = ba.data(); - EXPECT_FALSE(data_ptr[0]); - ba[0] = bit::bit_value(true); - EXPECT_TRUE(data_ptr[0]); + bit::bit_array ba; + ba.fill(bit::bit_value(false)); + // Assume data() returns a pointer to a boolean array. + uint8_t* data_ptr = ba.data(); + EXPECT_FALSE(data_ptr[0]); + ba[0] = bit::bit_value(true); + EXPECT_TRUE(data_ptr[0]); } // Test size() and empty() functions. TEST(ArrayTest, SizeAndEmpty) { - bit::bit_array<0> ba_empty; - EXPECT_EQ(ba_empty.size(), 0); - EXPECT_TRUE(ba_empty.empty()); + bit::bit_array ba_empty; + EXPECT_EQ(ba_empty.size(), 0); + EXPECT_TRUE(ba_empty.empty()); - bit::bit_array<5> ba; - EXPECT_EQ(ba.size(), 5); - EXPECT_FALSE(ba.empty()); + bit::bit_array ba; + EXPECT_EQ(ba.size(), 5); + EXPECT_FALSE(ba.empty()); } // Test initializer list construction. TEST(ArrayTest, InitializerListConstruction) { - bit::bit_array<3> ba = { bit::bit1, bit::bit0, bit::bit1 }; - EXPECT_TRUE(ba[0]); - EXPECT_FALSE(ba[1]); - EXPECT_TRUE(ba[2]); + bit::bit_array ba = {bit::bit1, bit::bit0, bit::bit1}; + EXPECT_TRUE(ba[0]); + EXPECT_FALSE(ba[1]); + EXPECT_TRUE(ba[2]); } // Test copy constructor and copy assignment operator. TEST(ArrayTest, CopyAndAssignment) { - bit::bit_array<5> ba1 = { bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1 }; - bit::bit_array<5> ba_copy(ba1); - EXPECT_EQ(ba1, ba_copy); + bit::bit_array ba1 = {bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; + bit::bit_array ba_copy(ba1); + EXPECT_EQ(ba1, ba_copy); - bit::bit_array<5> ba_assigned; - ba_assigned = ba1; - EXPECT_EQ(ba1, ba_assigned); + bit::bit_array ba_assigned; + ba_assigned = ba1; + EXPECT_EQ(ba1, ba_assigned); } // Test move semantics (move constructor and move assignment), if implemented. TEST(ArrayTest, MoveSemantics) { - bit::bit_array<5> ba1 = { bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1 }; - bit::bit_array<5> ba_moved(std::move(ba1)); - // We test the moved-to container's values. The moved-from object is valid but unspecified. - EXPECT_TRUE(ba_moved[0]); - EXPECT_FALSE(ba_moved[1]); - EXPECT_TRUE(ba_moved[2]); - EXPECT_FALSE(ba_moved[3]); - EXPECT_TRUE(ba_moved[4]); - - bit::bit_array<5> ba2 = { bit::bit0, bit::bit0, bit::bit0, bit::bit0, bit::bit0 }; - ba2 = std::move(ba_moved); - EXPECT_TRUE(ba2[0]); - EXPECT_FALSE(ba2[1]); - EXPECT_TRUE(ba2[2]); - EXPECT_FALSE(ba2[3]); - EXPECT_TRUE(ba2[4]); + bit::bit_array ba1 = {bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; + bit::bit_array ba_moved(std::move(ba1)); + // We test the moved-to container's values. The moved-from object is valid but unspecified. + EXPECT_TRUE(ba_moved[0]); + EXPECT_FALSE(ba_moved[1]); + EXPECT_TRUE(ba_moved[2]); + EXPECT_FALSE(ba_moved[3]); + EXPECT_TRUE(ba_moved[4]); + + bit::bit_array ba2 = {bit::bit0, bit::bit0, bit::bit0, bit::bit0, bit::bit0}; + ba2 = std::move(ba_moved); + EXPECT_TRUE(ba2[0]); + EXPECT_FALSE(ba2[1]); + EXPECT_TRUE(ba2[2]); + EXPECT_FALSE(ba2[3]); + EXPECT_TRUE(ba2[4]); } // @@ -405,4 +405,12 @@ TEST(BitArrayDynamicTest, StructOfBitArray) { EXPECT_EQ(s.arr1.size(), s1); EXPECT_EQ(s.arr2.size(), s2); EXPECT_EQ(s.arr3.size(), s3); -} \ No newline at end of file +} + +TEST(BitArrayDynamicTest, TwoDBitArray) { + bit::bit_array> arr( + 16, + bit::bit_array<>(4, bit::bit1)); + EXPECT_EQ(arr.size(), 16); + EXPECT_EQ(arr[0].size(), 4); +} From b6314a7aa18b9186656e001328f7fcc850401737 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 28 Mar 2025 20:33:50 -0400 Subject: [PATCH 22/85] tighten up the dynamic extent class. Add UDL --- include/bitlib/bit-containers/bit_array.hpp | 14 +++--- .../bit_array_dynamic_extent.hpp | 22 +++++---- test/src/test-array.cpp | 45 ++++++++++++++----- 3 files changed, 54 insertions(+), 27 deletions(-) diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index 07fa433c..b5a5aaf8 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -69,7 +69,8 @@ class bit_array { requires(!std::is_same_v); constexpr bit_array(const std::initializer_list init); constexpr bit_array(const std::initializer_list init); - constexpr bit_array(const std::string_view s); + constexpr bit_array(const std::string_view s) + requires(std::is_same_v); ~bit_array() = default; /* @@ -153,11 +154,7 @@ constexpr bit_array::bit_array(bit_array::value_type bit template constexpr void bit_array::fill(bit_array::value_type bit_val) noexcept { - if (bit_val) { - std::fill(storage.begin(), storage.end(), bit_array::word_type(-1)); - } else { - std::fill(storage.begin(), storage.end(), bit_array::word_type(0)); - } + std::fill(this->begin(), this->end(), bit_val); } template @@ -184,7 +181,9 @@ constexpr bit_array::bit_array(const std::initializer_list init) } template -constexpr bit_array::bit_array(const std::string_view s) { +constexpr bit_array::bit_array(const std::string_view s) + requires(std::is_same_v) +{ if(bitsof(*this) != (std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))) [[unlikely]] { throw std::invalid_argument("String contains an invalid number of bits for bit_array."); }; @@ -326,7 +325,6 @@ constexpr typename bit_array::const_iterator bit_array:: // -------------------------------------------------------------------------- // - // ========================================================================== // } // namespace bit #endif // _BIT_ARRAY_HPP_INCLUDED diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index 84240197..7e859a32 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -50,7 +50,7 @@ class bit_array { /* * Constructors, copies and moves... */ - constexpr bit_array() noexcept; + bit_array() = delete; constexpr bit_array(size_type size); constexpr bit_array(size_type size, value_type bit_val); constexpr bit_array(const bit_array& other); @@ -61,7 +61,8 @@ class bit_array { constexpr bit_array(const std::initializer_list init); #endif constexpr bit_array(const std::initializer_list init); - constexpr bit_array(const std::string_view s); + constexpr bit_array(const std::string_view s) + requires(std::is_same_v); ~bit_array() = default; /* @@ -118,10 +119,12 @@ static_assert(bit_contiguous_range>, "bit_array<> does not satisfy b static_assert(bit_contiguous_sized_range>, "bit_array<> does not satisfy bit_contiguous_sized_range concept!"); #endif +/* Can't imagine any use for a default constructor template constexpr bit_array::bit_array() noexcept : m_size(0), storage(std::unique_ptr(nullptr, &std::free)) { } +*/ template constexpr bit_array::bit_array(size_type size) @@ -204,6 +207,7 @@ constexpr bit_array::bit_array(const std::initiali template constexpr bit_array::bit_array(const std::string_view s) + requires(std::is_same_v) : m_size((std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))), storage(std::unique_ptr( static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { @@ -248,12 +252,7 @@ constexpr void bit_array::swap(bit_array constexpr void bit_array::fill(value_type bit_val) noexcept { - std::fill(this->begin(), this->end(), bit_val); /* - if (bit_val) { - std::fill(&storage[0], &storage[Words(m_size)], bit_array::word_type(-1)); - } else { - std::fill(&storage[0], &storage[Words(m_size)], bit_array::word_type(0)); - }*/ + std::fill(this->begin(), this->end(), bit_val); } // -------------------------------------------------------------------------- // @@ -366,5 +365,12 @@ constexpr typename bit_array::const_iterator bit_a // ========================================================================== // } // namespace bit + +constexpr bit::bit_array operator""_b(const char* binary, std::size_t len) { + auto reversed = std::make_unique(len + 1); + std::reverse_copy(binary, binary + len, reversed.get()); + return bit::bit_array(std::string_view(reversed.get(), len)); +} + #endif // _BIT_ARRAY_DYNAMIC_EXTENT_HPP_INCLUDED // ========================================================================== // diff --git a/test/src/test-array.cpp b/test/src/test-array.cpp index fcd5e797..34a19276 100644 --- a/test/src/test-array.cpp +++ b/test/src/test-array.cpp @@ -222,13 +222,6 @@ TEST(BitArrayDynamicTest, Bitsof) { EXPECT_EQ(arr.size(), 23); } -TEST(BitArrayDynamicTest, DefaultConstructorCreatesEmptyArray) { - bit::bit_array<> arr; - EXPECT_TRUE(arr.empty()); - EXPECT_EQ(arr.size(), 0u); - EXPECT_EQ(arr.begin(), arr.end()); -} - TEST(BitArrayDynamicTest, SizeConstructorCreatesArrayOfGivenSize) { const std::size_t size = 10; bit::bit_array<> arr(size); @@ -332,9 +325,9 @@ TEST(BitArrayDynamicTest, IteratorTraversal) { } TEST(BitArrayDynamicTest, CapacityFunctions) { - bit::bit_array<> arr; - EXPECT_TRUE(arr.empty()); - EXPECT_EQ(arr.size(), 0u); + bit::bit_array<> arr{bit::bit0}; + EXPECT_FALSE(arr.empty()); + EXPECT_EQ(arr.size(), 1u); // max_size() is expected to be >= size. EXPECT_GE(arr.max_size(), arr.size()); } @@ -407,10 +400,40 @@ TEST(BitArrayDynamicTest, StructOfBitArray) { EXPECT_EQ(s.arr3.size(), s3); } -TEST(BitArrayDynamicTest, TwoDBitArray) { +TEST(BitArrayDynamicTest, StringConstructor) { + bit::bit_array<> arr{"01001101"}; + + EXPECT_EQ(arr.size(), 8); + + EXPECT_EQ(arr[7], bit::bit1); + EXPECT_EQ(arr[6], bit::bit0); + EXPECT_EQ(arr[5], bit::bit1); + EXPECT_EQ(arr[4], bit::bit1); + EXPECT_EQ(arr[3], bit::bit0); + EXPECT_EQ(arr[2], bit::bit0); + EXPECT_EQ(arr[1], bit::bit1); + EXPECT_EQ(arr[0], bit::bit0); +} + +TEST(BitArrayDynamicTest, UserDefinedLiteral) { + auto arr = "01001101"_b; + EXPECT_EQ(arr.size(), 8); + EXPECT_EQ(arr[7], bit::bit0); + EXPECT_EQ(arr[0], bit::bit1); +} + +TEST(BitArrayDynamicTest, TwoDBitArraySizeValueConstructor) { bit::bit_array> arr( 16, bit::bit_array<>(4, bit::bit1)); EXPECT_EQ(arr.size(), 16); EXPECT_EQ(arr[0].size(), 4); } + +TEST(BitArrayDynamicTest, TwoDBitArraySizeConstructor) { + bit::bit_array> arr( + 16, + bit::bit_array<>(4)); + EXPECT_EQ(arr.size(), 16); + EXPECT_EQ(arr[0].size(), 4); +} From 27434f36e20aefbb2425b3aa5e9e80cb60c7a388 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sat, 29 Mar 2025 16:31:56 -0400 Subject: [PATCH 23/85] Hex or bin user defined literal --- include/bitlib/bit-containers/bit_array.hpp | 75 +++++++++++++++++-- .../bit_array_dynamic_extent.hpp | 6 -- test/src/test-array.cpp | 12 ++- 3 files changed, 81 insertions(+), 12 deletions(-) diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index b5a5aaf8..39d8702f 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -14,14 +14,12 @@ // ================================ PREAMBLE ================================ // // C++ standard library -#include +#include #include +#include #include -#include -#include -#include #include -#include +#include // Project sources #include "bitlib/bit-iterator/bit.hpp" #include "bitlib/bit-algorithms/bit_algorithm.hpp" @@ -326,6 +324,73 @@ constexpr typename bit_array::const_iterator bit_array:: // -------------------------------------------------------------------------- // // ========================================================================== // + +template +constexpr void _append_char_from_parameter_pack(Iter& iter) { +} + +template +constexpr void _append_char_from_parameter_pack(Iter& iter) { + uint8_t decimal; + uint8_t bits; + switch (Base) { + case 2: + bits = 1u; + decimal = Bit - '0'; + break; + + case 4: + bits = 4u; + decimal = Bit - '0'; + if (Bit >= 'a' && Bit <= 'f') { + decimal = Bit - 'a' + 10u; + } + if (Bit >= 'A' && Bit <= 'F') { + decimal = Bit - 'A' + 10u; + } + break; + } + for (int i = 0; i < bits; i++) { + *(iter++) = (decimal & 0x1u) ? bit1 : bit0; + decimal >>= 1; + } +} + +template +constexpr void _append_char_from_parameter_pack(Iter& iter) + requires(sizeof...(Bits) > 0) +{ + _append_char_from_parameter_pack(iter); + _append_char_from_parameter_pack(iter); +} + +template +constexpr size_t _parameter_pack_number_of_bits() { + if constexpr ((Zero == '0') && ((X == 'X') || (X == 'x'))) { + return sizeof...(Bits) * 4; + } else { + return 2 + sizeof...(Bits); + } +} + +template +constexpr void _router_char_parameter_pack(Iter& iter) { + if constexpr ((Zero == '0') && ((X == 'X') || (X == 'x'))) { + _append_char_from_parameter_pack<4u, Iter, Bits...>(iter); + } else { + _append_char_from_parameter_pack<2u, Iter, Zero, X, Bits...>(iter); + } +} + } // namespace bit + +template +constexpr bit::bit_array()> operator""_b() { + bit::bit_array()> arr{}; + auto iter = arr.begin(); + bit::_router_char_parameter_pack()>::iterator, Str...>(iter); + return arr; +} + #endif // _BIT_ARRAY_HPP_INCLUDED // ========================================================================== // diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index 7e859a32..8b92e326 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -366,11 +366,5 @@ constexpr typename bit_array::const_iterator bit_a // ========================================================================== // } // namespace bit -constexpr bit::bit_array operator""_b(const char* binary, std::size_t len) { - auto reversed = std::make_unique(len + 1); - std::reverse_copy(binary, binary + len, reversed.get()); - return bit::bit_array(std::string_view(reversed.get(), len)); -} - #endif // _BIT_ARRAY_DYNAMIC_EXTENT_HPP_INCLUDED // ========================================================================== // diff --git a/test/src/test-array.cpp b/test/src/test-array.cpp index 34a19276..3e8794b0 100644 --- a/test/src/test-array.cpp +++ b/test/src/test-array.cpp @@ -416,12 +416,22 @@ TEST(BitArrayDynamicTest, StringConstructor) { } TEST(BitArrayDynamicTest, UserDefinedLiteral) { - auto arr = "01001101"_b; + auto arr = 01001101_b; EXPECT_EQ(arr.size(), 8); EXPECT_EQ(arr[7], bit::bit0); EXPECT_EQ(arr[0], bit::bit1); } +TEST(BitArrayDynamicTest, UserDefinedHexLiteral) { + auto arr = 0x010A110A_b; + EXPECT_EQ(arr.size(), 32); + EXPECT_EQ(arr[0], bit::bit0); + EXPECT_EQ(arr[1], bit::bit1); + EXPECT_EQ(arr[2], bit::bit0); + EXPECT_EQ(arr[3], bit::bit1); + EXPECT_EQ(arr[7], bit::bit0); +} + TEST(BitArrayDynamicTest, TwoDBitArraySizeValueConstructor) { bit::bit_array> arr( 16, From 3010eeb1bde920ca5900bd10eeefab99a9018ec3 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sat, 29 Mar 2025 21:24:13 -0400 Subject: [PATCH 24/85] User Defined Literals more restrictive than expected. Using quote as bit width separator --- include/bitlib/bit-containers/bit_array.hpp | 129 ++++++++++++-------- test/src/test-array.cpp | 15 ++- 2 files changed, 92 insertions(+), 52 deletions(-) diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index 39d8702f..d06e1a8d 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -324,71 +324,98 @@ constexpr typename bit_array::const_iterator bit_array:: // -------------------------------------------------------------------------- // // ========================================================================== // - -template -constexpr void _append_char_from_parameter_pack(Iter& iter) { -} - -template -constexpr void _append_char_from_parameter_pack(Iter& iter) { - uint8_t decimal; - uint8_t bits; - switch (Base) { - case 2: - bits = 1u; - decimal = Bit - '0'; - break; - - case 4: - bits = 4u; - decimal = Bit - '0'; - if (Bit >= 'a' && Bit <= 'f') { - decimal = Bit - 'a' + 10u; - } - if (Bit >= 'A' && Bit <= 'F') { - decimal = Bit - 'A' + 10u; - } - break; - } - for (int i = 0; i < bits; i++) { - *(iter++) = (decimal & 0x1u) ? bit1 : bit0; - decimal >>= 1; +template +constexpr void _parameter_pack_base_bits_prefix_len(size_t& base, size_t& bits, size_t& prefix_len, uint64_t& num) { + if ('\'' == Bit) { + if (0 != bits) { + return; //skip. Should we, though? It seems like it will be confusing + } + bits = num; + num = 0; + ++prefix_len; + if (0 == base) { + base = 10; + } + } else if (0 == base) { + if (Bit == 'b' || Bit == 'B') { + base = 2; + num = 0; + } else if (Bit == 'x' || Bit == 'X') { + base = 16; + num = 0; + } else if (Bit == 'd' || Bit == 'D') { + base = 10; + num = 0; + } else { + num = (num * 10) + (Bit - '0'); + } + ++prefix_len; + } else { + uint8_t decimal; + switch (base) { + case 2: + decimal = Bit - '0'; + break; + case 10: + decimal = Bit - '0'; + break; + case 16: + decimal = Bit - '0'; + if (Bit >= 'a' && Bit <= 'f') { + decimal = Bit - 'a' + 10u; + } + if (Bit >= 'A' && Bit <= 'F') { + decimal = Bit - 'A' + 10u; + } + break; + } + num = num * base + decimal; } } -template -constexpr void _append_char_from_parameter_pack(Iter& iter) +template +constexpr void _parameter_pack_base_bits_prefix_len(size_t& base, size_t& bits, size_t& prefix_len, uint64_t& num) requires(sizeof...(Bits) > 0) { - _append_char_from_parameter_pack(iter); - _append_char_from_parameter_pack(iter); + _parameter_pack_base_bits_prefix_len(base, bits, prefix_len, num); + _parameter_pack_base_bits_prefix_len(base, bits, prefix_len, num); } -template -constexpr size_t _parameter_pack_number_of_bits() { - if constexpr ((Zero == '0') && ((X == 'X') || (X == 'x'))) { - return sizeof...(Bits) * 4; - } else { - return 2 + sizeof...(Bits); +template +constexpr std::pair _parameter_pack_decode_prefixed_num() { + size_t base = 0; + size_t bits = 0; + size_t prefix_len = 0; + uint64_t num = 0; + _parameter_pack_base_bits_prefix_len(base, bits, prefix_len, num); + size_t digits = (sizeof...(Bits) - prefix_len); + if (0 == base) { + base = 10; + digits = prefix_len; + prefix_len = 0; } -} - -template -constexpr void _router_char_parameter_pack(Iter& iter) { - if constexpr ((Zero == '0') && ((X == 'X') || (X == 'x'))) { - _append_char_from_parameter_pack<4u, Iter, Bits...>(iter); - } else { - _append_char_from_parameter_pack<2u, Iter, Zero, X, Bits...>(iter); + if (0 == bits) { + if (base == 2) { + bits = digits; + } else if (base == 10) { + bits = static_cast(std::ceil(digits * std::log2(10))); + } else { + bits = 4 * digits; + } } + return {bits, num}; } } // namespace bit template -constexpr bit::bit_array()> operator""_b() { - bit::bit_array()> arr{}; - auto iter = arr.begin(); - bit::_router_char_parameter_pack()>::iterator, Str...>(iter); +constexpr bit::bit_array().first> operator""_b() { + bit::bit_array().first> arr{}; + auto [bits, num] = bit::_parameter_pack_decode_prefixed_num(); + for (int i = 0; i < arr.size(); i++) { + arr[i] = (num & 0x1) ? bit::bit1 : bit::bit0; + num >>= 1; + } return arr; } diff --git a/test/src/test-array.cpp b/test/src/test-array.cpp index 3e8794b0..c0cf4eb2 100644 --- a/test/src/test-array.cpp +++ b/test/src/test-array.cpp @@ -416,10 +416,12 @@ TEST(BitArrayDynamicTest, StringConstructor) { } TEST(BitArrayDynamicTest, UserDefinedLiteral) { - auto arr = 01001101_b; + auto arr = 0b01001101_b; EXPECT_EQ(arr.size(), 8); EXPECT_EQ(arr[7], bit::bit0); EXPECT_EQ(arr[0], bit::bit1); + auto arr2 = 0b1'01001101_b; + EXPECT_EQ(arr2.size(), 1); } TEST(BitArrayDynamicTest, UserDefinedHexLiteral) { @@ -430,6 +432,17 @@ TEST(BitArrayDynamicTest, UserDefinedHexLiteral) { EXPECT_EQ(arr[2], bit::bit0); EXPECT_EQ(arr[3], bit::bit1); EXPECT_EQ(arr[7], bit::bit0); + auto arr2 = 0x19'010A110A_b; + EXPECT_EQ(arr2.size(), 25); +} + +TEST(BitArrayDynamicTest, UserDefinedDecLiteral) { + auto arr = 16'12345_b; + EXPECT_EQ(arr.size(), 16); + auto arr2 = 0x3039_b; + EXPECT_EQ(arr, arr2); + auto arr3 = 16'123'45_b; + EXPECT_EQ(arr, arr3); } TEST(BitArrayDynamicTest, TwoDBitArraySizeValueConstructor) { From c252e53d0899f1cd9e1fe6a75a84a6af15fb0d46 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Wed, 2 Apr 2025 09:27:31 -0400 Subject: [PATCH 25/85] Unintuitive, but bit_reference methods can all be const, as the pointer / mask are only set in constructor. The end-value of the pointer can change --- include/bitlib/bit-iterator/bit_reference.hpp | 164 ++++++++---------- 1 file changed, 68 insertions(+), 96 deletions(-) diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index 86906645..2905828b 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -50,20 +50,20 @@ class bit_reference // Assignment public: - constexpr bit_reference& operator=(const bit_reference& other) noexcept; - template - constexpr bit_reference& operator=(const bit_reference& other) noexcept; - constexpr bit_reference& operator=(const bit_value val) noexcept; - constexpr bit_reference& assign(word_type val) noexcept; - constexpr bit_reference& assign(word_type val, size_type pos); - - // Bitwise assignment operators + constexpr bit_reference& operator=(const bit_reference& other) const noexcept; + template + constexpr bit_reference& operator=(const bit_reference& other) const noexcept; + constexpr bit_reference& operator=(const bit_value val) const noexcept; + constexpr bit_reference& assign(word_type val) const noexcept; + constexpr bit_reference& assign(word_type val, size_type pos) const; + + // Bitwise assignment operators public: - constexpr bit_reference& operator&=(bit_value other) noexcept; - constexpr bit_reference& operator|=(bit_value other) noexcept; - constexpr bit_reference& operator^=(bit_value other) noexcept; + constexpr bit_reference& operator&=(bit_value other) const noexcept; + constexpr bit_reference& operator|=(bit_value other) const noexcept; + constexpr bit_reference& operator^=(bit_value other) const noexcept; - // Conversion + // Conversion public: explicit constexpr operator bool() const noexcept; @@ -73,18 +73,18 @@ class bit_reference // Swap members public: - template - void swap(bit_reference other); - void swap(bit_value& other); + template + void swap(bit_reference other) const; + void swap(bit_value& other) const; - // Bit manipulation + // Bit manipulation public: - constexpr bit_reference& set(bool b) noexcept; - constexpr bit_reference& set() noexcept; - constexpr bit_reference& reset() noexcept; - constexpr bit_reference& flip() noexcept; + constexpr bit_reference& set(bool b) const noexcept; + constexpr bit_reference& set() const noexcept; + constexpr bit_reference& reset() const noexcept; + constexpr bit_reference& flip() const noexcept; - // Underlying details + // Underlying details public: constexpr word_type* address() const noexcept; constexpr size_type position() const noexcept; @@ -185,54 +185,44 @@ constexpr bit_reference::bit_reference( // Copies a bit reference to the bit reference template constexpr bit_reference& bit_reference::operator=( - const bit_reference& other -) noexcept -{ - other ? set() : reset(); - return *this; + const bit_reference& other) const noexcept { + other ? set() : reset(); + return const_cast&>(*this); } // Assigns a bit reference to the bit reference template template constexpr bit_reference& bit_reference::operator=( - const bit_reference& other -) noexcept -{ - other ? set() : reset(); - return *this; + const bit_reference& other) const noexcept { + other ? set() : reset(); + return const_cast&>(*this); } // Assigns a bit value to the bit reference template constexpr bit_reference& bit_reference::operator=( - const bit_value val -) noexcept -{ - val ? set() : reset(); - return *this; + const bit_value val) const noexcept { + val ? set() : reset(); + return const_cast&>(*this); } // Assigns the aligned bit of a value to the bit reference template constexpr bit_reference& bit_reference::assign( - word_type val -) noexcept -{ - val & 1 ? set() : reset(); - return *this; + word_type val) const noexcept { + val & 1 ? set() : reset(); + return const_cast&>(*this); } // Assigns an unaligned bit of a value to the bit reference template constexpr bit_reference& bit_reference::assign( word_type val, - size_type pos -) -{ - assert(pos < binary_digits::value); - val >> pos & 1 ? set() : reset(); - return *this; + size_type pos) const { + assert(pos < binary_digits::value); + val >> pos & 1 ? set() : reset(); + return const_cast&>(*this); } // -------------------------------------------------------------------------- // @@ -242,31 +232,25 @@ constexpr bit_reference& bit_reference::assign( // Assigns the value of the referenced bit through a bitwise and operation template constexpr bit_reference& bit_reference::operator&=( - bit_value other -) noexcept -{ - *_ptr &= ~(_mask * static_cast(!other._value)); - return *this; + bit_value other) const noexcept { + *_ptr &= ~(_mask * static_cast(!other._value)); + return const_cast&>(*this); } // Assigns the value of the referenced bit through a bitwise or operation template constexpr bit_reference& bit_reference::operator|=( - bit_value other -) noexcept -{ - *_ptr |= _mask * static_cast(other._value); - return *this; + bit_value other) const noexcept { + *_ptr |= _mask * static_cast(other._value); + return const_cast&>(*this); } // Assigns the value of the referenced bit through a bitwise xor operation template constexpr bit_reference& bit_reference::operator^=( - bit_value other -) noexcept -{ - *_ptr ^= _mask * static_cast(other._value); - return *this; + bit_value other) const noexcept { + *_ptr ^= _mask * static_cast(other._value); + return const_cast&>(*this); } // -------------------------------------------------------------------------- // @@ -301,25 +285,21 @@ constexpr bit_pointer bit_reference::operator&( template template void bit_reference::swap( - bit_reference other -) -{ - if (other != *this) { - flip(); - other.flip(); - } + bit_reference other) const { + if (other != *this) { + flip(); + other.flip(); + } } // Swaps the value of the referenced bit with a bit value template void bit_reference::swap( - bit_value& other -) -{ - if (other != *this) { - flip(); - other.flip(); - } + bit_value& other) const { + if (other != *this) { + flip(); + other.flip(); + } } // -------------------------------------------------------------------------- // @@ -329,38 +309,30 @@ void bit_reference::swap( // Sets the value of the referenced bit to the provided boolean value template constexpr bit_reference& bit_reference::set( - bool b -) noexcept -{ - b ? set() : reset(); - return *this; + bool b) const noexcept { + b ? set() : reset(); + return const_cast&>(*this); } // Sets the value of the referenced bit to 1 template -constexpr bit_reference& bit_reference::set( -) noexcept -{ - *_ptr |= _mask; - return *this; +constexpr bit_reference& bit_reference::set() const noexcept { + *_ptr |= _mask; + return const_cast&>(*this); } // Resets the value of the referenced bit to 0 template -constexpr bit_reference& bit_reference::reset( -) noexcept -{ - *_ptr &= ~_mask; - return *this; +constexpr bit_reference& bit_reference::reset() const noexcept { + *_ptr &= ~_mask; + return const_cast&>(*this); } // Flips the value of the referenced bit template -constexpr bit_reference& bit_reference::flip( -) noexcept -{ - *_ptr ^= _mask; - return *this; +constexpr bit_reference& bit_reference::flip() const noexcept { + *_ptr ^= _mask; + return const_cast&>(*this); } // -------------------------------------------------------------------------- // From 4cfb3acff109bba7afb67d7a8d277e501cb6484c Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Wed, 2 Apr 2025 09:33:25 -0400 Subject: [PATCH 26/85] Working range based copy --- test/src/test-usecase.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/src/test-usecase.cpp b/test/src/test-usecase.cpp index 8962965b..3ba48ad7 100644 --- a/test/src/test-usecase.cpp +++ b/test/src/test-usecase.cpp @@ -1,6 +1,7 @@ #include "bitlib/bitlib.hpp" // Third-party libraries #include +#include #include "gtest/gtest.h" @@ -29,6 +30,17 @@ TEST(UseCaseTest, VectorAppend) { EXPECT_EQ(vec[17 + 28], bit::bit0); } +TEST(UseCaseTest, RangeCopy) { + uint32_t test32 = 0xABBADABA; + auto bspan = bit::bit_span(test32); + bit::bit_vector v(32); + std::ranges::copy(bspan, v.begin()); + EXPECT_EQ(v.size(), 32); + EXPECT_EQ(v[0], (0xABBADABA & (1 << 0)) ? bit::bit1 : bit::bit0); + EXPECT_EQ(v[1], (0xABBADABA & (1 << 1)) ? bit::bit1 : bit::bit0); + EXPECT_EQ(v[2], (0xABBADABA & (2 << 1)) ? bit::bit1 : bit::bit0); +} + /* c++23 TEST(UseCaseTest, StartLifeAs) { From c50569d3c6406c0644de735d821db1455569c94d Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Thu, 3 Apr 2025 17:59:44 -0400 Subject: [PATCH 27/85] Add half-open subscript operator to containers --- include/bitlib/bit-containers/bit_array.hpp | 13 ++++++++++++- .../bit-containers/bit_array_dynamic_extent.hpp | 11 +++++++++++ include/bitlib/bit-containers/bit_span.hpp | 10 ++++++++++ include/bitlib/bit-containers/bit_vector.hpp | 14 ++++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index d06e1a8d..f19b7e59 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -21,9 +21,10 @@ #include #include // Project sources -#include "bitlib/bit-iterator/bit.hpp" #include "bitlib/bit-algorithms/bit_algorithm.hpp" #include "bitlib/bit-containers/bit_bitsof.hpp" +#include "bitlib/bit-containers/bit_span.hpp" +#include "bitlib/bit-iterator/bit.hpp" namespace bit { // ========================================================================== // @@ -110,6 +111,11 @@ class bit_array { constexpr size_type size() const noexcept; constexpr size_type max_size() const noexcept; + /* + * Slice + */ + constexpr bit_span operator[](size_type offset, size_type right) const noexcept; + /* * Operations */ @@ -323,6 +329,11 @@ constexpr typename bit_array::const_iterator bit_array:: // -------------------------------------------------------------------------- // +template +constexpr bit_span bit_array::operator[](size_type begin, size_type end) const noexcept { + return bit_span(&this->at(begin), end - begin); +} + // ========================================================================== // template constexpr void _parameter_pack_base_bits_prefix_len(size_t& base, size_t& bits, size_t& prefix_len, uint64_t& num) { diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index 8b92e326..165171e4 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -19,6 +19,7 @@ #include "bitlib/bit-algorithms/bit_algorithm.hpp" #include "bitlib/bit-containers/bit_array.hpp" #include "bitlib/bit-containers/bit_bitsof.hpp" +#include "bitlib/bit-containers/bit_span.hpp" #include "bitlib/bit-iterator/bit.hpp" namespace bit { @@ -104,6 +105,11 @@ class bit_array { constexpr size_type size() const noexcept; constexpr size_type max_size() const noexcept; + /* + * Slice + */ + constexpr bit_span operator[](size_type offset, size_type right) const noexcept; + /* * Operations */ @@ -363,6 +369,11 @@ constexpr typename bit_array::const_iterator bit_a // -------------------------------------------------------------------------- // +template +constexpr bit_span bit_array::operator[](size_type begin, size_type end) const noexcept { + return bit_span(&this->at(begin), end - begin); +} + // ========================================================================== // } // namespace bit diff --git a/include/bitlib/bit-containers/bit_span.hpp b/include/bitlib/bit-containers/bit_span.hpp index 360bbeff..6b6f088c 100644 --- a/include/bitlib/bit-containers/bit_span.hpp +++ b/include/bitlib/bit-containers/bit_span.hpp @@ -132,6 +132,9 @@ class bit_span : private bit_span_storage { constexpr auto subspan() const noexcept -> bit_span()>; constexpr bit_span subspan(size_type offset, size_type count = std::dynamic_extent) const noexcept requires(Extent == std::dynamic_extent); + constexpr bit_span operator[](size_type begin, size_type end) const noexcept + requires(Extent == std::dynamic_extent); + template constexpr bit_span first() const noexcept requires(NewExtent != std::dynamic_extent); constexpr bit_span first(size_type offset) const noexcept requires(Extent == std::dynamic_extent); @@ -298,6 +301,13 @@ constexpr bit_span bit_span::sub return bit_span(begin() + offset, new_count); } +template +constexpr bit_span bit_span::operator[](size_type begin, size_type end) const noexcept + requires(Extent == std::dynamic_extent) +{ + return subspan(begin, end - begin); +} + template template constexpr bit_span bit_span::first() const noexcept requires(NewExtent != std::dynamic_extent) { diff --git a/include/bitlib/bit-containers/bit_vector.hpp b/include/bitlib/bit-containers/bit_vector.hpp index 831926e6..be2aff9d 100644 --- a/include/bitlib/bit-containers/bit_vector.hpp +++ b/include/bitlib/bit-containers/bit_vector.hpp @@ -22,6 +22,7 @@ #include // Project sources #include "bitlib/bit-algorithms/bit_algorithm.hpp" +#include "bitlib/bit-containers/bit_span.hpp" #include "bitlib/bit-iterator/bit.hpp" #include "bitlib/bit_concepts.hpp" @@ -191,6 +192,11 @@ class bit_vector { template constexpr void append_range(R&& range); + /* + * Slice + */ + constexpr bit_span operator[](size_type offset, size_type right) const noexcept; + /* * Helper functions */ @@ -657,6 +663,14 @@ constexpr void bit_vector::append_range(R&& range) { this->insert(this->end(), range.begin(), range.end()); } +/* + * Slice +*/ +template +constexpr bit_span bit_vector::operator[](size_type offset, size_type right) const noexcept { + return bit_span(&this->at(begin), end - begin); +} + // ------------------------ BIT VECTOR: DEBUGGING -------------------------- // template constexpr std::string bit_vector::debug_string(const_iterator first, const_iterator last) { From ad59cb2643fa34fdca1d8f486846cf7b5c7a99a9 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Thu, 3 Apr 2025 20:10:11 -0400 Subject: [PATCH 28/85] Basic working slices --- include/bitlib/bit-containers/bit_array.hpp | 10 +- include/bitlib/bit-containers/bit_span.hpp | 164 ++++++++++---------- test/src/test-array.cpp | 22 +++ test/src/test-span.cpp | 11 ++ test/src/vector_test.cpp | 11 ++ 5 files changed, 135 insertions(+), 83 deletions(-) diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index f19b7e59..705a0b0b 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -114,7 +114,8 @@ class bit_array { /* * Slice */ - constexpr bit_span operator[](size_type offset, size_type right) const noexcept; + constexpr bit_span operator[](size_type offset, size_type right) const noexcept; + constexpr bit_span operator[](size_type offset, size_type right) noexcept; /* * Operations @@ -330,7 +331,12 @@ constexpr typename bit_array::const_iterator bit_array:: // -------------------------------------------------------------------------- // template -constexpr bit_span bit_array::operator[](size_type begin, size_type end) const noexcept { +constexpr bit_span bit_array::operator[](size_type begin, size_type end) const noexcept { + return bit_span(&this->at(begin), end - begin); +} + +template +constexpr bit_span bit_array::operator[](size_type begin, size_type end) noexcept { return bit_span(&this->at(begin), end - begin); } diff --git a/include/bitlib/bit-containers/bit_span.hpp b/include/bitlib/bit-containers/bit_span.hpp index 6b6f088c..e0c8f5dd 100644 --- a/include/bitlib/bit-containers/bit_span.hpp +++ b/include/bitlib/bit-containers/bit_span.hpp @@ -65,83 +65,87 @@ class bit_span : private bit_span_storage { public: // --- Constructors --- - // Default constructor: - // For dynamic extent, we start with a null pointer and size zero. - // For fixed extent, the pointer is null but the size is always Extent. - constexpr bit_span() noexcept; - - // Construct from a bit_pointer and a bit count. - constexpr bit_span(pointer data, size_type bit_count) noexcept requires(Extent == std::dynamic_extent) ; - constexpr bit_span(pointer data) noexcept; - - // Construct from a WordType pointer and a bit count. - constexpr bit_span(WordType* word_ptr, size_type bit_count) noexcept requires(Extent == std::dynamic_extent); - constexpr bit_span(WordType* word_ptr) noexcept; - constexpr bit_span(WordType& word_ref, size_type bit_count) noexcept - requires(Extent == std::dynamic_extent); - constexpr bit_span(WordType& word_ref) noexcept; - - // Construct from a iterator and a bit count. - constexpr bit_span(iterator iter, size_type bit_count) noexcept requires(Extent == std::dynamic_extent); - constexpr bit_span(iterator iter) noexcept; - - constexpr bit_span(WordType& s) - requires(std::is_scalar_v); - constexpr bit_span(WordType* s) - requires(std::is_scalar_v); - - // --- Observers --- - - // Returns the number of bits in the span. - constexpr size_type size() const noexcept; - // Checks if the span is empty. - constexpr bool empty() const noexcept; - - // --- Element Access --- - - // Non-const element access. - constexpr reference operator[](size_type pos); - // Const element access returns a const_reference. - constexpr const_reference operator[](size_type pos) const; - // Bounds-checked access - constexpr reference at(size_type idx); - // Bounds-checked access - constexpr const_reference at(size_type idx) const; - - // Non-const front/back access. - constexpr reference front(); - constexpr reference back(); - - // Const front/back access returns a const_reference. - constexpr const_reference front() const; - constexpr const_reference back() const; - - // --- Iterators --- - - // Returns an iterator to the first bit. - constexpr iterator begin() const noexcept; - - // Returns an iterator just past the last bit. - constexpr iterator end() const noexcept; - - // --- Subspan Functionality --- - - - // Creates a subspan starting at offset with count bits. - template - constexpr auto subspan() const noexcept -> bit_span()>; - constexpr bit_span subspan(size_type offset, size_type count = std::dynamic_extent) const noexcept requires(Extent == std::dynamic_extent); - - constexpr bit_span operator[](size_type begin, size_type end) const noexcept - requires(Extent == std::dynamic_extent); - - template - constexpr bit_span first() const noexcept requires(NewExtent != std::dynamic_extent); - constexpr bit_span first(size_type offset) const noexcept requires(Extent == std::dynamic_extent); - - template - constexpr bit_span last() const noexcept requires(RevOffset != std::dynamic_extent); - constexpr bit_span last(size_type offset) const noexcept requires(Extent == std::dynamic_extent); + // Default constructor: + constexpr bit_span() noexcept; + + // Construct from a bit_pointer and a bit count. + constexpr bit_span(pointer data, size_type bit_count) noexcept + requires(Extent == std::dynamic_extent); + constexpr bit_span(pointer data) noexcept; + + // Construct from a WordType pointer and a bit count. + constexpr bit_span(WordType* word_ptr, size_type bit_count) noexcept + requires(Extent == std::dynamic_extent); + constexpr bit_span(WordType* word_ptr) noexcept; + constexpr bit_span(WordType& word_ref, size_type bit_count) noexcept + requires(Extent == std::dynamic_extent); + constexpr bit_span(WordType& word_ref) noexcept; + + // Construct from a iterator and a bit count. + constexpr bit_span(iterator iter, size_type bit_count) noexcept + requires(Extent == std::dynamic_extent); + constexpr bit_span(iterator iter) noexcept; + + constexpr bit_span(WordType& s) + requires(std::is_scalar_v); + constexpr bit_span(WordType* s) + requires(std::is_scalar_v); + + // --- Observers --- + + // Returns the number of bits in the span. + constexpr size_type size() const noexcept; + // Checks if the span is empty. + constexpr bool empty() const noexcept; + + // --- Element Access --- + + // Non-const element access. + constexpr reference operator[](size_type pos); + // Const element access returns a const_reference. + constexpr const_reference operator[](size_type pos) const; + // Bounds-checked access + constexpr reference at(size_type idx); + // Bounds-checked access + constexpr const_reference at(size_type idx) const; + + // Non-const front/back access. + constexpr reference front(); + constexpr reference back(); + + // Const front/back access returns a const_reference. + constexpr const_reference front() const; + constexpr const_reference back() const; + + // --- Iterators --- + + // Returns an iterator to the first bit. + constexpr iterator begin() const noexcept; + + // Returns an iterator just past the last bit. + constexpr iterator end() const noexcept; + + // --- Subspan Functionality --- + + // Creates a subspan starting at offset with count bits. + template + constexpr auto subspan() const noexcept -> bit_span()>; + constexpr bit_span subspan(size_type offset, size_type count = std::dynamic_extent) const noexcept + requires(Extent == std::dynamic_extent); + + constexpr bit_span operator[](size_type begin, size_type end) const noexcept; + + template + constexpr bit_span first() const noexcept + requires(NewExtent != std::dynamic_extent); + constexpr bit_span first(size_type offset) const noexcept + requires(Extent == std::dynamic_extent); + + template + constexpr bit_span last() const noexcept + requires(RevOffset != std::dynamic_extent); + constexpr bit_span last(size_type offset) const noexcept + requires(Extent == std::dynamic_extent); }; static_assert(bit_range>, "bit_span does not satisfy bit_range concept!"); @@ -302,10 +306,8 @@ constexpr bit_span bit_span::sub } template -constexpr bit_span bit_span::operator[](size_type begin, size_type end) const noexcept - requires(Extent == std::dynamic_extent) -{ - return subspan(begin, end - begin); +constexpr bit_span bit_span::operator[](size_type begin, size_type end) const noexcept { + return bit_span(&(this->begin()[begin]), end - begin); } template diff --git a/test/src/test-array.cpp b/test/src/test-array.cpp index c0cf4eb2..32ddf9a8 100644 --- a/test/src/test-array.cpp +++ b/test/src/test-array.cpp @@ -460,3 +460,25 @@ TEST(BitArrayDynamicTest, TwoDBitArraySizeConstructor) { EXPECT_EQ(arr.size(), 16); EXPECT_EQ(arr[0].size(), 4); } + +TEST(BitArrayTest, Slice) { + auto arr = 0x20'DEADBEEF_b; + auto span2 = arr[4, 8]; + EXPECT_EQ(span2.size(), 4); + EXPECT_EQ(span2[0], (0xE & (1 << 0)) ? bit::bit1 : bit::bit0); + EXPECT_EQ(span2[1], (0xE & (1 << 1)) ? bit::bit1 : bit::bit0); + EXPECT_EQ(span2[2], (0xE & (1 << 2)) ? bit::bit1 : bit::bit0); + EXPECT_EQ(span2[3], (0xE & (1 << 3)) ? bit::bit1 : bit::bit0); +} + +TEST(BitArrayTest, SliceModify) { + auto arr = 0x24'DEADBEEF_b; + auto span2 = arr[4, 8]; + EXPECT_EQ(span2.size(), 4); + EXPECT_EQ(span2[0], (0xE & (1 << 0)) ? bit::bit1 : bit::bit0); + EXPECT_EQ(span2[1], (0xE & (1 << 1)) ? bit::bit1 : bit::bit0); + EXPECT_EQ(span2[2], (0xE & (1 << 2)) ? bit::bit1 : bit::bit0); + EXPECT_EQ(span2[3], (0xE & (1 << 3)) ? bit::bit1 : bit::bit0); + span2[3] = bit::bit0; + EXPECT_EQ(span2[3], bit::bit0); +} diff --git a/test/src/test-span.cpp b/test/src/test-span.cpp index 6cc16781..53a9789a 100644 --- a/test/src/test-span.cpp +++ b/test/src/test-span.cpp @@ -204,6 +204,17 @@ TEST(BitSpanTest, CopyConstructionFixed) { EXPECT_EQ(span2[2], bit::bit1); } +TEST(BitSpanTest, Slice) { + uint32_t word = 0xDEADBEEF; + bit::bit_span span1(word); + auto span2 = span1[4, 8]; + EXPECT_EQ(span2.size(), 4); + EXPECT_EQ(span2[0], (0xE & (1 << 0)) ? bit::bit1 : bit::bit0); + EXPECT_EQ(span2[1], (0xE & (1 << 1)) ? bit::bit1 : bit::bit0); + EXPECT_EQ(span2[2], (0xE & (1 << 2)) ? bit::bit1 : bit::bit0); + EXPECT_EQ(span2[3], (0xE & (1 << 3)) ? bit::bit1 : bit::bit0); +} + // ---------- Const Reference Alias Tests ---------- TEST(BitSpanTest, ConstReferenceAlias) { diff --git a/test/src/vector_test.cpp b/test/src/vector_test.cpp index 0d714f54..f7f332ff 100644 --- a/test/src/vector_test.cpp +++ b/test/src/vector_test.cpp @@ -748,6 +748,17 @@ TYPED_TEST(VectorTest, PopBack) { } } +TEST(BitVectorTest, Slice) { + uint32_t word = 0xDEADBEEF; + bit::bit_span span1(word); + auto vec = bit::bit_vector(std::from_range, span1); + auto span2 = span1[4, 8]; + EXPECT_EQ(span2.size(), 4); + EXPECT_EQ(span2[0], (0xE & (1 << 0)) ? bit::bit1 : bit::bit0); + EXPECT_EQ(span2[1], (0xE & (1 << 1)) ? bit::bit1 : bit::bit0); + EXPECT_EQ(span2[2], (0xE & (1 << 2)) ? bit::bit1 : bit::bit0); + EXPECT_EQ(span2[3], (0xE & (1 << 3)) ? bit::bit1 : bit::bit0); +} //TYPED_TEST(VectorTest, Print) { //std::cout << this->v3_ << std::endl; From 42a6d3c4f83e6a22fb40472d48f339c6e52cd896 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Wed, 9 Apr 2025 20:42:54 -0400 Subject: [PATCH 29/85] Add mdspan test. Change slice operator to () --- CMakeLists.txt | 10 ++++- .../bitlib/bit-containers/bit-containers.hpp | 1 + include/bitlib/bit-containers/bit_array.hpp | 8 ++-- .../bit-containers/bit_mdspan_accessor.hpp | 32 +++++++++++++++ test/CMakeLists.txt | 26 ++++++++++++- test/src/test-array.cpp | 4 +- test/src/test-mdspan.cpp | 39 +++++++++++++++++++ test/src/test-usecase.cpp | 36 +++++++++++++++++ 8 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 include/bitlib/bit-containers/bit_mdspan_accessor.hpp create mode 100644 test/src/test-mdspan.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d0abef6d..a5b0e255 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,6 @@ project(Bit-Vector VERSION 0.3.0) #clang-tidy; #-header-filter=include/;) - add_library(bitlib INTERFACE) add_library(bitlib::bitlib ALIAS bitlib) target_include_directories(bitlib INTERFACE @@ -35,6 +34,15 @@ option(BITLIB_EXAMPLE "Build bitlib examples" OFF) option(BITLIB_TEST "Build bitlib tests" OFF) option(BITLIB_PROFILE "Buid simple example for profiling" OFF) option(BITLIB_COVERAGE "Compute test coverage" OFF) +option(BITLIB_MDSPAN "Compute test coverage" ON) + +if (BITLIB_MDSPAN) + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + find_package(mdspan CONFIG REQUIRED) + target_compile_definitions(bitlib INTERFACE MDSPAN_IMPL_STANDARD_NAMESPACE=std MDSPAN_IMPL_PROPOSED_NAMESPACE=experimental) + target_link_libraries(bitlib INTERFACE std::mdspan) + endif() +endif() if (BITLIB_HWY) add_definitions(-DBITLIB_HWY) diff --git a/include/bitlib/bit-containers/bit-containers.hpp b/include/bitlib/bit-containers/bit-containers.hpp index fd885d7f..921fcd5b 100644 --- a/include/bitlib/bit-containers/bit-containers.hpp +++ b/include/bitlib/bit-containers/bit-containers.hpp @@ -9,6 +9,7 @@ // ============================== PREAMBLE ================================== // #include "bit_array.hpp" #include "bit_array_dynamic_extent.hpp" +#include "bit_mdspan_accessor.hpp" #include "bit_span.hpp" #include "bit_vector.hpp" // ========================================================================== // diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index 705a0b0b..6ce9b1ad 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -114,8 +114,8 @@ class bit_array { /* * Slice */ - constexpr bit_span operator[](size_type offset, size_type right) const noexcept; - constexpr bit_span operator[](size_type offset, size_type right) noexcept; + constexpr bit_span operator()(size_type offset, size_type right) const noexcept; + constexpr bit_span operator()(size_type offset, size_type right) noexcept; /* * Operations @@ -331,12 +331,12 @@ constexpr typename bit_array::const_iterator bit_array:: // -------------------------------------------------------------------------- // template -constexpr bit_span bit_array::operator[](size_type begin, size_type end) const noexcept { +constexpr bit_span bit_array::operator()(size_type begin, size_type end) const noexcept { return bit_span(&this->at(begin), end - begin); } template -constexpr bit_span bit_array::operator[](size_type begin, size_type end) noexcept { +constexpr bit_span bit_array::operator()(size_type begin, size_type end) noexcept { return bit_span(&this->at(begin), end - begin); } diff --git a/include/bitlib/bit-containers/bit_mdspan_accessor.hpp b/include/bitlib/bit-containers/bit_mdspan_accessor.hpp new file mode 100644 index 00000000..c5b22091 --- /dev/null +++ b/include/bitlib/bit-containers/bit_mdspan_accessor.hpp @@ -0,0 +1,32 @@ +// =============================== BIT VALUE ================================ // +// Project: The C++ Bit Library +// Name: bit_value.hpp +// Description: A class representing an independent, non-referenced bit +// Creator: Vincent Reverdy +// Contributor(s): Peter McLean [2025] +// License: BSD 3-Clause License +// ========================================================================== // +#ifndef _BIT_MDSPAN_ACCESSOR_HPP_INCLUDED +#define _BIT_MDSPAN_ACCESSOR_HPP_INCLUDED + +#include "bitlib/bit-iterator/bit_pointer.hpp" +#include "bitlib/bit-iterator/bit_reference.hpp" +#include "bitlib/bit-iterator/bit_value.hpp" + +namespace bit { +template +struct bit_default_accessor { + using element_type = bit_value; + using data_handle_type = bit_pointer; + using reference = bit_reference; + using offset_policy = bit_default_accessor; + constexpr reference access(data_handle_type p, std::size_t i) const noexcept { + return p[i]; + } + constexpr data_handle_type offset(data_handle_type p, std::size_t i) const noexcept { + return p + i; + } +}; +} // namespace bit + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8d24c721..d19c59cc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -7,7 +7,31 @@ find_package(GTest REQUIRED) include(GoogleTest) file(GLOB TEST_SOURCES "src/*.cpp") -add_executable(bitlib-tests ${TEST_SOURCES}) +add_executable(bitlib-tests) + +target_sources(bitlib-tests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src/fixtures.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-array.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-copy_backward.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-copy.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-count.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-equal.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-fill.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-find.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-move.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-reverse.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-rotate.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-shift.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-span.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-swap_ranges.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-transform.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-usecase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/vector_test.cpp +) + +if (BITLIB_MDSPAN) + target_sources(bitlib-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/test-mdspan.cpp) +endif() target_compile_options(bitlib-tests PUBLIC -O0 -g -fno-omit-frame-pointer -Wpedantic -fno-inline) diff --git a/test/src/test-array.cpp b/test/src/test-array.cpp index 32ddf9a8..212eeff7 100644 --- a/test/src/test-array.cpp +++ b/test/src/test-array.cpp @@ -463,7 +463,7 @@ TEST(BitArrayDynamicTest, TwoDBitArraySizeConstructor) { TEST(BitArrayTest, Slice) { auto arr = 0x20'DEADBEEF_b; - auto span2 = arr[4, 8]; + auto span2 = arr(4, 8); EXPECT_EQ(span2.size(), 4); EXPECT_EQ(span2[0], (0xE & (1 << 0)) ? bit::bit1 : bit::bit0); EXPECT_EQ(span2[1], (0xE & (1 << 1)) ? bit::bit1 : bit::bit0); @@ -473,7 +473,7 @@ TEST(BitArrayTest, Slice) { TEST(BitArrayTest, SliceModify) { auto arr = 0x24'DEADBEEF_b; - auto span2 = arr[4, 8]; + auto span2 = arr(4, 8); EXPECT_EQ(span2.size(), 4); EXPECT_EQ(span2[0], (0xE & (1 << 0)) ? bit::bit1 : bit::bit0); EXPECT_EQ(span2[1], (0xE & (1 << 1)) ? bit::bit1 : bit::bit0); diff --git a/test/src/test-mdspan.cpp b/test/src/test-mdspan.cpp new file mode 100644 index 00000000..2f1d1c7b --- /dev/null +++ b/test/src/test-mdspan.cpp @@ -0,0 +1,39 @@ +#include "bitlib/bitlib.hpp" +// Third-party libraries +#include +#include + +#include "gtest/gtest.h" +#include "mdspan/mdspan.hpp" + +TEST(MdSpanTest, BitDefaultLayout) { + uint32_t rot = 0xDEADBEEF; + bit::bit_array<> dynarr(5 * 6 * 7); + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 6; j++) { + for (int k = 0; k < 7; k++) { + dynarr[i * 6 * 7 + j * 7 + k] = (rot & 1) ? bit::bit1 : bit::bit0; + rot = (rot >> 1) | ((rot & 1) << 31); + } + } + } + rot = 0xDEADBEEF; + std::mdspan< + bit::bit_value, + std::dextents, + std::layout_right, + bit::bit_default_accessor > + myspan(&dynarr[0], 5, 6, 7); + + for (int i = 0; i < myspan.extent(0); i++) { + for (int j = 0; j < myspan.extent(1); j++) { + for (int k = 0; k < myspan.extent(2); k++) { + bit::bit_value expected = ((rot & 1) ? bit::bit1 : bit::bit0); + EXPECT_EQ(dynarr[i * 6 * 7 + j * 7 + k], expected); + bit::bit_value actual = myspan[i, j, k]; + EXPECT_EQ(actual, expected); + rot = (rot >> 1) | ((rot & 1) << 31); + } + } + } +} diff --git a/test/src/test-usecase.cpp b/test/src/test-usecase.cpp index 3ba48ad7..f5181982 100644 --- a/test/src/test-usecase.cpp +++ b/test/src/test-usecase.cpp @@ -33,6 +33,42 @@ TEST(UseCaseTest, VectorAppend) { TEST(UseCaseTest, RangeCopy) { uint32_t test32 = 0xABBADABA; auto bspan = bit::bit_span(test32); + static_assert(std::indirectly_writable>); + static_assert(std::ranges::input_range>); + static_assert(std::weakly_incrementable::iterator>); + static_assert(std::indirectly_readable>>); + + using BitVecIterator = bit::bit_vector::iterator; + using BitSpanIterator = std::ranges::iterator_t>; + using OtherRef = std::iter_reference_t; // the type used on the RHS + + // 1. Check that *it = t is valid. + static_assert( + requires(BitVecIterator out, OtherRef in) { + *out = in; + }, + "Requirement failed: *it = t must be a valid assignment."); + + // 2. Check that *it++ = t is valid. + static_assert( + requires(BitVecIterator out, OtherRef in) { + *out++ = in; + }, + "Requirement failed: *it++ = t must be a valid assignment."); + + // 3. Check that const_cast(*it) = t is valid. + + static_assert( + requires(BitVecIterator&& out, OtherRef&& in) { + const_cast&&>(*out) = std::forward(in); + }, + "Requirement failed: const_cast(*it) = t must be a valid assignment."); + + static_assert(std::indirectly_writable::iterator, std::iter_reference_t>>>); + /* + static_assert(std::indirectly_copyable>, bit::bit_vector::iterator>); + */ + bit::bit_vector v(32); std::ranges::copy(bspan, v.begin()); EXPECT_EQ(v.size(), 32); From b48a260f439e3721559f3d16e77dbab2010483d5 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Thu, 10 Apr 2025 09:09:29 -0400 Subject: [PATCH 30/85] Use a true reference for bit_reference --- include/bitlib/bit-iterator/bit_pointer.hpp | 2 +- include/bitlib/bit-iterator/bit_reference.hpp | 82 ++++++++----------- 2 files changed, 33 insertions(+), 51 deletions(-) diff --git a/include/bitlib/bit-iterator/bit_pointer.hpp b/include/bitlib/bit-iterator/bit_pointer.hpp index 41ba5f51..8da3777c 100644 --- a/include/bitlib/bit-iterator/bit_pointer.hpp +++ b/include/bitlib/bit-iterator/bit_pointer.hpp @@ -263,7 +263,7 @@ constexpr bit_reference bit_pointer::operator[]( if (sum < 0 && diff * digits != sum) { --diff; } - return bit_reference(_ref._ptr + diff, sum - diff * digits); + return bit_reference(&_ref._ref + diff, sum - diff * digits); } // -------------------------------------------------------------------------- // diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index 2905828b..d867d286 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -93,14 +93,16 @@ class bit_reference // Implementation details: function members private: bit_reference() noexcept = default; +#if 0 explicit constexpr bit_reference(std::nullptr_t) noexcept; +#endif explicit constexpr bit_reference(word_type* ptr) noexcept; constexpr bit_reference(word_type* ptr, size_type pos); // Implementation details: data members private: - word_type* _ptr; - typename std::remove_cv::type _mask; + word_type& _ref; + typename std::remove_cv::type _mask; }; static_assert(bit_reference_c>, "bit_reference does not satisfy bit_reference_c concept!"); @@ -141,41 +143,30 @@ std::basic_ostream& operator<<( template template constexpr bit_reference::bit_reference( - const bit_reference& other -) noexcept -: _ptr(other._ptr) -, _mask(other._mask) -{ + const bit_reference& other) noexcept + : _ref(other._ref), _mask(other._mask) { } template constexpr bit_reference::bit_reference( - const bit_reference& other -) noexcept -: _ptr(other._ptr) -, _mask(other._mask) -{ + const bit_reference& other) noexcept + : _ref(other._ref), _mask(other._mask) { } // Explicitly constructs an aligned bit reference template constexpr bit_reference::bit_reference( - word_type& ref -) noexcept -: _ptr(&ref) -, _mask(1) -{ + word_type& ref) noexcept + : _ref(ref), _mask(1) { } // Explicitly constructs an unaligned bit reference template constexpr bit_reference::bit_reference( word_type& ref, - size_type pos -) -: _ptr((assert(pos < binary_digits::value), &ref)) -, _mask(static_cast(1) << pos) -{ + size_type pos) + : _ref(ref), _mask(static_cast(1) << pos) { + assert(pos < binary_digits::value); } // -------------------------------------------------------------------------- // @@ -233,7 +224,7 @@ constexpr bit_reference& bit_reference::assign( template constexpr bit_reference& bit_reference::operator&=( bit_value other) const noexcept { - *_ptr &= ~(_mask * static_cast(!other._value)); + _ref &= ~(_mask * static_cast(!other._value)); return const_cast&>(*this); } @@ -241,7 +232,7 @@ constexpr bit_reference& bit_reference::operator&=( template constexpr bit_reference& bit_reference::operator|=( bit_value other) const noexcept { - *_ptr |= _mask * static_cast(other._value); + _ref |= _mask * static_cast(other._value); return const_cast&>(*this); } @@ -249,7 +240,7 @@ constexpr bit_reference& bit_reference::operator|=( template constexpr bit_reference& bit_reference::operator^=( bit_value other) const noexcept { - *_ptr ^= _mask * static_cast(other._value); + _ref ^= _mask * static_cast(other._value); return const_cast&>(*this); } // -------------------------------------------------------------------------- // @@ -262,7 +253,7 @@ template constexpr bit_reference::operator bool( ) const noexcept { - return *_ptr & _mask; + return _ref & _mask; } // -------------------------------------------------------------------------- // @@ -274,7 +265,7 @@ template constexpr bit_pointer bit_reference::operator&( ) const noexcept { - return bit_pointer(_ptr, position()); + return bit_pointer(&_ref, position()); } // -------------------------------------------------------------------------- // @@ -317,21 +308,21 @@ constexpr bit_reference& bit_reference::set( // Sets the value of the referenced bit to 1 template constexpr bit_reference& bit_reference::set() const noexcept { - *_ptr |= _mask; + _ref |= _mask; return const_cast&>(*this); } // Resets the value of the referenced bit to 0 template constexpr bit_reference& bit_reference::reset() const noexcept { - *_ptr &= ~_mask; + _ref &= ~_mask; return const_cast&>(*this); } // Flips the value of the referenced bit template constexpr bit_reference& bit_reference::flip() const noexcept { - *_ptr ^= _mask; + _ref ^= _mask; return const_cast&>(*this); } // -------------------------------------------------------------------------- // @@ -342,10 +333,8 @@ constexpr bit_reference& bit_reference::flip() const noexcep // Returns a pointer to the underlying word template constexpr typename bit_reference::word_type* -bit_reference::address( -) const noexcept -{ - return _ptr; +bit_reference::address() const noexcept { + return &_ref; } // Returns the position of the referenced bit within the underlying word @@ -483,34 +472,27 @@ std::basic_ostream& operator<<( // -------- BIT REFERENCE: IMPLEMENTATION DETAILS: FUNCTION MEMBERS --------- // // Privately explicitly constructs a bit reference from a nullptr +#if 0 template constexpr bit_reference::bit_reference( - std::nullptr_t -) noexcept -: _ptr(nullptr) -, _mask() -{ + std::nullptr_t) noexcept + : _ref(nullptr), _mask() { } - +#endif // Privately explicitly constructs an aligned bit reference from a pointer template constexpr bit_reference::bit_reference( - word_type* ptr -) noexcept -: _ptr(ptr) -, _mask(1) -{ + word_type* ptr) noexcept + : _ref(*ptr), _mask(1) { } // Privately explicitly constructs an unaligned bit reference from a pointer template constexpr bit_reference::bit_reference( word_type* ptr, - size_type pos -) -: _ptr((assert(pos < binary_digits::value), ptr)) -, _mask(static_cast(1) << pos) -{ + size_type pos) + : _ref(*ptr), _mask(static_cast(1) << pos) { + assert(pos < binary_digits::value); } // -------------------------------------------------------------------------- // From 916b29c1bdf27c14cf0151bda96973c74ebfad9b Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 11 Apr 2025 08:15:29 -0400 Subject: [PATCH 31/85] const some members that should never change after construction --- .../bit-containers/bit_array_dynamic_extent.hpp | 12 ++++++++---- include/bitlib/bit-iterator/bit_reference.hpp | 13 +++++-------- test/src/test-array.cpp | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index 165171e4..293dca00 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -43,8 +43,8 @@ class bit_array { const word_type*>::type; private: - size_type m_size; - std::unique_ptr storage; + const size_type m_size; + const std::unique_ptr storage; static constexpr std::size_t Words(std::size_t N) { return (N * bitsof() + bitsof() - 1) / bitsof(); }; public: @@ -252,8 +252,12 @@ constexpr bool bit_array::operator==(const bit_arr template constexpr void bit_array::swap(bit_array& other) noexcept { - std::swap(this->storage, other.storage); - std::swap(this->m_size, other.m_size); + assert(this->m_size == other.m_size); + W* it1 = this->storage.get(); + W* it2 = other.storage.get(); + for (size_t i = 0; i < Words(this->m_size); i++, it1++, it2++) { + std::swap(*it1, *it2); + } } template diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index d867d286..7ce8be23 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -39,6 +39,7 @@ class bit_reference public: using word_type = WordType; using size_type = std::size_t; + using mask_type = typename std::remove_cv::type; // Lifecycle public: @@ -88,7 +89,7 @@ class bit_reference public: constexpr word_type* address() const noexcept; constexpr size_type position() const noexcept; - constexpr typename std::remove_cv::type mask() const noexcept; + constexpr mask_type mask() const noexcept; // Implementation details: function members private: @@ -102,7 +103,7 @@ class bit_reference // Implementation details: data members private: word_type& _ref; - typename std::remove_cv::type _mask; + const mask_type _mask; }; static_assert(bit_reference_c>, "bit_reference does not satisfy bit_reference_c concept!"); @@ -348,12 +349,8 @@ bit_reference::position( // Returns a mask corresponding to the referenced bit template -constexpr typename std::remove_cv< - typename bit_reference::word_type ->::type bit_reference::mask( -) const noexcept -{ - return _mask; +constexpr bit_reference::mask_type bit_reference::mask() const noexcept { + return _mask; } // -------------------------------------------------------------------------- // diff --git a/test/src/test-array.cpp b/test/src/test-array.cpp index 212eeff7..f4355141 100644 --- a/test/src/test-array.cpp +++ b/test/src/test-array.cpp @@ -342,7 +342,7 @@ TEST(BitArrayDynamicTest, FillOperationSetsAllBits) { } TEST(BitArrayDynamicTest, SwapOperationSwapsContents) { - const std::size_t size1 = 5; + const std::size_t size1 = 8; const std::size_t size2 = 8; bit::bit_array<> arr1(size1, bit::bit_value(true)); bit::bit_array<> arr2(size2, bit::bit_value(false)); From c80456ad485f201af5aef18d9818dbbe165855d0 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 11 Apr 2025 10:06:23 -0400 Subject: [PATCH 32/85] bit_pointer now holds pointer member --- include/bitlib/bit-algorithms/shift.hpp | 111 +++-- include/bitlib/bit-containers/bit_span.hpp | 43 +- include/bitlib/bit-iterator/bit_iterator.hpp | 4 +- include/bitlib/bit-iterator/bit_pointer.hpp | 428 +++++++------------ include/bitlib/bit_concepts.hpp | 4 - 5 files changed, 237 insertions(+), 353 deletions(-) diff --git a/include/bitlib/bit-algorithms/shift.hpp b/include/bitlib/bit-algorithms/shift.hpp index e598e0ca..75f55a6c 100644 --- a/include/bitlib/bit-algorithms/shift.hpp +++ b/include/bitlib/bit-algorithms/shift.hpp @@ -66,10 +66,9 @@ bit_iterator shift_left( // Out of range cases if (n <= 0) return last; - if (n >= d) - { - //bit::fill(first, last, bit::bit0); - return first; + if (n >= d) { + //bit::fill(first, last, bit::bit0); + return first; } // Single word case @@ -102,37 +101,30 @@ bit_iterator shift_left( word_type last_value = !is_last_aligned ? *last.base() : 0; // Align first - if (!is_first_aligned) - { - if (first.position() >= middle.position()) - { - *first.base() = _bitblend( - *first.base(), - (*middle.base()) << (first.position() - middle.position()), - first.position(), - digits - first.position() - ); - } - else - { - const int n1 = digits - middle.position(); - const int n2 = digits - first.position() - n1; - *first.base() = _bitblend( - *first.base(), - (*middle.base()) >> (middle.position() - first.position()), - first.position(), - n1 - ); - *first.base() = _bitblend( - *first.base(), - (*std::next(middle.base())) << (digits - n2), - first.position() + n1, - n2 - ); - } - const int shifted = std::min(d - n, (digits - first.position())); - first += shifted; - middle += shifted; + if (!is_first_aligned) { + if (first.position() >= middle.position()) { + *first.base() = _bitblend( + *first.base(), + (*middle.base()) << (first.position() - middle.position()), + first.position(), + digits - first.position()); + } else { + const int n1 = digits - middle.position(); + const int n2 = digits - first.position() - n1; + *first.base() = _bitblend( + *first.base(), + (*middle.base()) >> (middle.position() - first.position()), + first.position(), + n1); + *first.base() = _bitblend( + *first.base(), + (*std::next(middle.base())) << (digits - n2), + first.position() + n1, + n2); + } + const int shifted = std::min(d - n, (digits - first.position())); + first += shifted; + middle += shifted; } if (middle.base() == last.base()) { @@ -148,7 +140,7 @@ bit_iterator shift_left( first += bits_left; } // https://en.cppreference.com/w/cpp/algorithm/shift - // "Elements that are in the original range but not the new range + // "Elements that are in the original range but not the new range // are left in a valid but unspecified state." // //bit::fill(first, last, bit::bit0); @@ -175,7 +167,7 @@ bit_iterator shift_left( first += last.position(); } // https://en.cppreference.com/w/cpp/algorithm/shift - // "Elements that are in the original range but not the new range + // "Elements that are in the original range but not the new range // are left in a valid but unspecified state." // //bit::fill(first, last, bit::bit0); @@ -248,7 +240,6 @@ bit_iterator shift_right( using size_type = typename bit_iterator::size_type; // Initialization - const bool is_first_aligned = first.position() == 0; const bool is_last_aligned = last.position() == 0; constexpr auto digits = binary_digits::value; auto d = bit::distance(first, last); @@ -276,22 +267,22 @@ bit_iterator shift_right( // Align last if (last.position() != 0) { - const size_type bits_to_align = std::min( - last.position(), - bit::distance(first, middle)); - const word_type word_to_write = get_word( - middle - bits_to_align, - bits_to_align); - write_word( - word_to_write, - last - bits_to_align, - bits_to_align); - middle -= bits_to_align; - last -= bits_to_align; - - // Nothing left to do - if (middle == first) - return first + n; + const size_type bits_to_align = std::min( + last.position(), + bit::distance(first, middle)); + const word_type word_to_write = get_word( + middle - bits_to_align, + bits_to_align); + write_word( + word_to_write, + last - bits_to_align, + bits_to_align); + middle -= bits_to_align; + last -= bits_to_align; + + // Nothing left to do + if (middle == first) + return first + n; } // More initialization @@ -308,7 +299,7 @@ bit_iterator shift_right( first.position() ); // https://en.cppreference.com/w/cpp/algorithm/shift - // "Elements that are in the original range but not the new range + // "Elements that are in the original range but not the new range // are left in a valid but unspecified state." // //bit::fill(first, new_first, bit::bit0); @@ -365,12 +356,12 @@ bit_iterator shift_right( { const size_type bits_to_align = bit::distance(first, middle); const word_type word_to_write = get_word( - first, - bits_to_align); + first, + bits_to_align); write_word( - word_to_write, - last - bits_to_align, - bits_to_align); + word_to_write, + last - bits_to_align, + bits_to_align); } return first + n; diff --git a/include/bitlib/bit-containers/bit_span.hpp b/include/bitlib/bit-containers/bit_span.hpp index e0c8f5dd..525597fc 100644 --- a/include/bitlib/bit-containers/bit_span.hpp +++ b/include/bitlib/bit-containers/bit_span.hpp @@ -68,6 +68,8 @@ class bit_span : private bit_span_storage { // Default constructor: constexpr bit_span() noexcept; + constexpr bit_span(const bit_span& other) noexcept; + // Construct from a bit_pointer and a bit count. constexpr bit_span(pointer data, size_type bit_count) noexcept requires(Extent == std::dynamic_extent); @@ -174,41 +176,50 @@ bit_span(WordType*, std::size_t) -> bit_span; // Default constructor: // For dynamic extent, we start with a null pointer and size zero. // For fixed extent, the pointer is null but the size is always Extent. -template -constexpr bit_span::bit_span() noexcept : data_(nullptr), bit_span_storage() { } +template +constexpr bit_span::bit_span() noexcept : bit_span_storage(), data_(nullptr) {} + +template +constexpr bit_span::bit_span(const bit_span& other) noexcept : bit_span_storage(other), data_(other.data_) { +} // Construct from a bit_pointer and a bit count. -template -constexpr bit_span::bit_span(pointer data, size_type bit_count) noexcept requires(Extent == std::dynamic_extent) : data_(data), bit_span_storage(bit_count) { } +template +constexpr bit_span::bit_span(pointer data, size_type bit_count) noexcept + requires(Extent == std::dynamic_extent) + : bit_span_storage(bit_count), data_(data) {} // Construct from a WordType pointer and a word count. // This converts the word count to a bit count using bitsof. -template -constexpr bit_span::bit_span(WordType* word_ptr, size_type bit_count) noexcept requires(Extent == std::dynamic_extent) : data_(pointer(word_ptr, 0)), bit_span_storage(bit_count) { } +template +constexpr bit_span::bit_span(WordType* word_ptr, size_type bit_count) noexcept + requires(Extent == std::dynamic_extent) + : bit_span_storage(bit_count), data_(pointer(word_ptr, 0)) {} template constexpr bit_span::bit_span(WordType& word_ref, size_type bit_count) noexcept requires(Extent == std::dynamic_extent) : bit_span(&word_ref, bit_count) {} -template -constexpr bit_span::bit_span(iterator iter, size_type bit_count) noexcept requires(Extent == std::dynamic_extent) : data_(&(*iter)), bit_span_storage(bit_count) { } - +template +constexpr bit_span::bit_span(iterator iter, size_type bit_count) noexcept + requires(Extent == std::dynamic_extent) + : bit_span_storage(bit_count), data_(&(*iter)) {} // Construct from a bit_pointer and a bit count. -template -constexpr bit_span::bit_span(pointer data) noexcept : data_(data), bit_span_storage() { } +template +constexpr bit_span::bit_span(pointer data) noexcept : bit_span_storage(), data_(data) {} -template -constexpr bit_span::bit_span(WordType* word_ptr) noexcept : data_(pointer(word_ptr, 0)), bit_span_storage() { } +template +constexpr bit_span::bit_span(WordType* word_ptr) noexcept : bit_span_storage(), data_(pointer(word_ptr, 0)) {} -template -constexpr bit_span::bit_span(iterator iter) noexcept : data_(&(*iter)), bit_span_storage() { } +template +constexpr bit_span::bit_span(iterator iter) noexcept : bit_span_storage(), data_(&(*iter)) {} template constexpr bit_span::bit_span(WordType* word_ptr) requires(std::is_scalar_v) - : data_(word_ptr), bit_span_storage() { + : bit_span_storage(), data_(word_ptr) { } template diff --git a/include/bitlib/bit-iterator/bit_iterator.hpp b/include/bitlib/bit-iterator/bit_iterator.hpp index d3995aa5..803e8631 100644 --- a/include/bitlib/bit-iterator/bit_iterator.hpp +++ b/include/bitlib/bit-iterator/bit_iterator.hpp @@ -155,9 +155,7 @@ constexpr bit_iterator::bit_iterator( // Explicitly constructs an unaligned bit iterator from a pointer template constexpr bit_iterator::bit_iterator(const pointer& ptr) -: _current(ptr->address()) -, _position(ptr->position()) -{ + : _current(ptr.address()), _position(ptr.position()) { } // -------------------------------------------------------------------------- // diff --git a/include/bitlib/bit-iterator/bit_pointer.hpp b/include/bitlib/bit-iterator/bit_pointer.hpp index 8da3777c..25c7ff16 100644 --- a/include/bitlib/bit-iterator/bit_pointer.hpp +++ b/include/bitlib/bit-iterator/bit_pointer.hpp @@ -15,6 +15,7 @@ // ================================ PREAMBLE ================================ // // C++ standard library // Project sources +#include "../bit-containers/bit_bitsof.hpp" #include "bit_details.hpp" // Third-party libraries // Miscellaneous @@ -37,12 +38,14 @@ class bit_pointer // Types public: using word_type = WordType; + using mask_type = typename std::remove_cv::type; using size_type = std::size_t; using difference_type = std::ptrdiff_t; // Lifecycle public: constexpr bit_pointer() noexcept; + constexpr bit_pointer(const bit_pointer& other) noexcept; template constexpr bit_pointer(const bit_pointer& other) noexcept; constexpr bit_pointer(std::nullptr_t) noexcept; @@ -62,11 +65,10 @@ class bit_pointer // Access public: - constexpr bit_reference operator*() const noexcept; - constexpr bit_reference* operator->() const noexcept; - constexpr bit_reference operator[](difference_type n) const; + constexpr bit_reference operator*() const noexcept; + constexpr bit_reference operator[](difference_type n) const; - // Increment and decrement operators + // Increment and decrement operators public: constexpr bit_pointer& operator++(); constexpr bit_pointer& operator--(); @@ -76,57 +78,52 @@ class bit_pointer constexpr bit_pointer operator-(difference_type n) const; constexpr bit_pointer& operator+=(difference_type n); constexpr bit_pointer& operator-=(difference_type n); + constexpr size_t position() const { return _tzcnt(_mask); } + constexpr word_type* address() const { return _ptr; } // Implementation details: data members private: - bit_reference _ref; - - // Non-member arithmetic operators - template - friend constexpr bit_pointer operator+( - typename bit_pointer::difference_type n, - bit_pointer x - ); - template - friend constexpr typename std::common_type< - typename bit_pointer::difference_type, - typename bit_pointer::difference_type - >::type operator-( - bit_pointer lhs, - bit_pointer rhs - ); - - // Comparison operators - template - friend constexpr bool operator==( - bit_pointer lhs, - bit_pointer rhs - ) noexcept; - template - friend constexpr bool operator!=( - bit_pointer lhs, - bit_pointer rhs - ) noexcept; - template - friend constexpr bool operator<( - bit_pointer lhs, - bit_pointer rhs - ) noexcept; - template - friend constexpr bool operator<=( - bit_pointer lhs, - bit_pointer rhs - ) noexcept; - template - friend constexpr bool operator>( - bit_pointer lhs, - bit_pointer rhs - ) noexcept; - template - friend constexpr bool operator>=( - bit_pointer lhs, - bit_pointer rhs - ) noexcept; + word_type* _ptr; + const mask_type _mask; + + // Non-member arithmetic operators + template + friend constexpr bit_pointer operator+( + typename bit_pointer::difference_type n, + bit_pointer x); + template + friend constexpr typename std::common_type< + typename bit_pointer::difference_type, + typename bit_pointer::difference_type>::type + operator-( + bit_pointer lhs, + bit_pointer rhs); + + // Comparison operators + template + friend constexpr bool operator==( + bit_pointer lhs, + bit_pointer rhs) noexcept; + template + friend constexpr bool operator!=( + bit_pointer lhs, + bit_pointer rhs) noexcept; + template + friend constexpr bool operator<( + bit_pointer lhs, + bit_pointer rhs) noexcept; + template + friend constexpr bool operator<=( + bit_pointer lhs, + bit_pointer rhs) noexcept; + template + friend constexpr bool operator>( + bit_pointer lhs, + bit_pointer rhs) noexcept; + template + friend constexpr bool operator>=( + bit_pointer lhs, + bit_pointer rhs) noexcept; }; static_assert(bit_pointer_c, bit_reference>, "bit_pointer does not satisfy bit_pointer_c concept!"); @@ -137,86 +134,68 @@ static_assert(bit_pointer_c, bit_reference>, "bit_ // ------------------------- BIT POINTER: LIFECYCLE ------------------------- // // Implicitly default constructs a null bit pointer template -constexpr bit_pointer::bit_pointer( -) noexcept -: _ref(nullptr) -{ +constexpr bit_pointer::bit_pointer() noexcept + : _ptr(nullptr), _mask(0) { +} + +template +constexpr bit_pointer::bit_pointer(const bit_pointer& other) noexcept + : _ptr(other._ptr), _mask(other._mask) { } // Implicitly constructs a bit pointer from another bit pointer template template -constexpr bit_pointer::bit_pointer( - const bit_pointer& other -) noexcept -: _ref(other._ref) -{ +constexpr bit_pointer::bit_pointer(const bit_pointer& other) noexcept + : _ptr(other._ptr), _mask(other._mask) { } // Explicitly constructs a bit pointer from a null pointer template -constexpr bit_pointer::bit_pointer( - std::nullptr_t -) noexcept -: _ref(nullptr) -{ +constexpr bit_pointer::bit_pointer(std::nullptr_t) noexcept + : _ptr(nullptr), _mask(0) { } // Explicitly constructs an aligned bit pointer from a pointer template -constexpr bit_pointer::bit_pointer( - word_type* ptr -) noexcept -: _ref(ptr) -{ +constexpr bit_pointer::bit_pointer(word_type* ptr) noexcept + : _ptr(ptr), _mask(1) { } // Explicitly constructs an unaligned bit pointer from a pointer template constexpr bit_pointer::bit_pointer( word_type* ptr, - size_type pos -) -: _ref(ptr, pos) -{ + size_type pos) + : _ptr(ptr), _mask(1 << pos) { } // -------------------------------------------------------------------------- // - - // ------------------------ BIT POINTER: ASSIGNMENT ------------------------- // // Assigns a null pointer to the bit pointer template constexpr bit_pointer& bit_pointer::operator=( - std::nullptr_t -) noexcept -{ - _ref._ptr = nullptr; - _ref._mask = 0; - return *this; + std::nullptr_t) noexcept { + _ptr = nullptr; + _mask = 0; + return *this; } // Copies a bit pointer to the bit pointer template -constexpr bit_pointer& bit_pointer::operator=( - const bit_pointer& other -) noexcept -{ - _ref._ptr = other._ref._ptr; - _ref._mask = other._ref._mask; - return *this; +constexpr bit_pointer& bit_pointer::operator=(const bit_pointer& other) noexcept { + _ptr = other._ptr; + _mask = other._mask; + return *this; } // Assigns a bit pointer to the bit pointer template template -constexpr bit_pointer& bit_pointer::operator=( - const bit_pointer& other -) noexcept -{ - _ref._ptr = other._ref._ptr; - _ref._mask = other._ref._mask; - return *this; +constexpr bit_pointer& bit_pointer::operator=(const bit_pointer& other) noexcept { + _ptr = other._ptr; + _mask = other._mask; + return *this; } // -------------------------------------------------------------------------- // @@ -225,10 +204,8 @@ constexpr bit_pointer& bit_pointer::operator=( // ------------------------ BIT POINTER: CONVERSION ------------------------- // // Returns true if the bit pointer is null, false otherwise template -constexpr bit_pointer::operator bool( -) const noexcept -{ - return _ref._ptr; +constexpr bit_pointer::operator bool() const noexcept { + return (_ptr == nullptr); } // -------------------------------------------------------------------------- // @@ -237,33 +214,20 @@ constexpr bit_pointer::operator bool( // -------------------------- BIT POINTER: ACCESS --------------------------- // // Gets a bit reference from the bit pointer template -constexpr bit_reference bit_pointer::operator*( -) const noexcept -{ - return _ref; -} - -// Gets a pointer to a bit reference -template -constexpr bit_reference* bit_pointer::operator->( -) const noexcept -{ - return std::addressof(const_cast&>(_ref)); +constexpr bit_reference bit_pointer::operator*() const noexcept { + return bit_reference(*_ptr, _mask); } // Gets a bit reference, decrementing or incrementing the pointer template -constexpr bit_reference bit_pointer::operator[]( - difference_type n -) const -{ - constexpr difference_type digits = binary_digits::value; - const difference_type sum = _ref.position() + n; - difference_type diff = sum / digits; - if (sum < 0 && diff * digits != sum) { - --diff; - } - return bit_reference(&_ref._ref + diff, sum - diff * digits); +constexpr bit_reference bit_pointer::operator[](difference_type n) const { + constexpr difference_type digits = binary_digits::value; + const difference_type sum = position() + n; + difference_type diff = sum / digits; + if (sum < 0 && diff * digits != sum) { + --diff; + } + return bit_reference(_ptr + diff, sum - diff * digits); } // -------------------------------------------------------------------------- // @@ -272,112 +236,78 @@ constexpr bit_reference bit_pointer::operator[]( // ------------- BIT POINTER: INCREMENT AND DECREMENT OPERATORS ------------- // // Increments the bit pointer and returns it template -constexpr bit_pointer& bit_pointer::operator++( -) -{ - using type = typename std::remove_cv::type; - constexpr size_type digits = binary_digits::value; - constexpr type one = 1; - constexpr type mask = one; - const size_type pos = _ref.position(); - if (pos + 1 < digits) { - _ref._mask <<= 1; - } else { - ++_ref._ptr; - _ref._mask = mask; - } - return *this; +constexpr bit_pointer& bit_pointer::operator++() { + _mask <<= 1; + if (_mask == 0) { + _mask = 1; + _ptr++; + } + return *this; } // Decrements the bit pointer and returns it template -constexpr bit_pointer& bit_pointer::operator--( -) -{ - using type = typename std::remove_cv::type; - constexpr size_type digits = binary_digits::value; - constexpr type one = 1; - constexpr type mask = static_cast(one << (digits - 1)); - const size_type pos = _ref.position(); - if (pos) { - _ref._mask >>= 1; - } else { - --_ref._ptr; - _ref._mask = mask; - } - return *this; +constexpr bit_pointer& bit_pointer::operator--() { + _mask >>= 1; + if (_mask == 0) { + _mask = mask_type(1) << (bitsof() - 1); + _ptr--; + } + return *this; } // Increments the bit pointer and returns the old one template -constexpr bit_pointer bit_pointer::operator++( - int -) -{ - bit_pointer old = *this; - ++(*this); - return old; +constexpr bit_pointer bit_pointer::operator++(int) { + bit_pointer old = *this; + ++(*this); + return old; } // Decrements the bit pointer and returns the old one template -constexpr bit_pointer bit_pointer::operator--( - int -) -{ - bit_pointer old = *this; - --(*this); - return old; +constexpr bit_pointer bit_pointer::operator--(int) { + bit_pointer old = *this; + --(*this); + return old; } // Looks forward several bits and gets a pointer at this position template -constexpr bit_pointer bit_pointer::operator+( - difference_type n -) const -{ - constexpr difference_type digits = binary_digits::value; - const difference_type sum = _ref.position() + n; - difference_type diff = sum / digits; - if (sum < 0 && diff * digits != sum) { - --diff; - } - return bit_pointer(_ref._ptr + diff, sum - diff * digits); +constexpr bit_pointer bit_pointer::operator+(difference_type n) const { + constexpr difference_type digits = binary_digits::value; + const difference_type sum = position() + n; + difference_type diff = sum / digits; + if (sum < 0 && diff * digits != sum) { + --diff; + } + return bit_pointer(_ptr + diff, sum - diff * digits); } // Looks backward several bits and gets a pointer at this position template -constexpr bit_pointer bit_pointer::operator-( - difference_type n -) const -{ - constexpr difference_type digits = binary_digits::value; - const difference_type sum = _ref.position() - n; - difference_type diff = sum / digits; - if (sum < 0 && diff * digits != sum) { - --diff; - } - return bit_pointer(_ref._ptr + diff, sum - diff * digits); +constexpr bit_pointer bit_pointer::operator-(difference_type n) const { + constexpr difference_type digits = binary_digits::value; + const difference_type sum = position() - n; + difference_type diff = sum / digits; + if (sum < 0 && diff * digits != sum) { + --diff; + } + return bit_pointer(_ptr + diff, sum - diff * digits); } // Increments the pointer by several bits and returns it template -constexpr bit_pointer& bit_pointer::operator+=( - difference_type n -) -{ - *this = *this + n; - return *this; +constexpr bit_pointer& bit_pointer::operator+=(difference_type n) { + *this = *this + n; + return *this; } // Decrements the pointer by several bits and returns it template -constexpr bit_pointer& bit_pointer::operator-=( - difference_type n -) -{ - *this = *this - n; - return *this; +constexpr bit_pointer& bit_pointer::operator-=(difference_type n) { + *this = *this - n; + return *this; } // -------------------------------------------------------------------------- // @@ -386,33 +316,25 @@ constexpr bit_pointer& bit_pointer::operator-=( // -------------- BIT POINTER: NON-MEMBER ARITHMETIC OPERATORS -------------- // // Advances the bit pointer several times template -constexpr bit_pointer operator+( - typename bit_pointer::difference_type n, - bit_pointer x -) -{ - return x + n; +constexpr bit_pointer operator+(typename bit_pointer::difference_type n, bit_pointer x) { + return x + n; } // Computes the distance in bits separating two bit pointers template constexpr typename std::common_type< typename bit_pointer::difference_type, - typename bit_pointer::difference_type ->::type operator-( - bit_pointer lhs, - bit_pointer rhs -) -{ - using lhs_type = typename bit_pointer::difference_type; - using rhs_type = typename bit_pointer::difference_type; - using difference_type = typename std::common_type::type; - constexpr difference_type lhs_digits = binary_digits::value; - constexpr difference_type rhs_digits = binary_digits::value; - constexpr difference_type digits = rhs_digits; - static_assert(lhs_digits == rhs_digits, ""); - const difference_type main = lhs._ref.address() - rhs._ref.address(); - return main * digits + (lhs._ref.position() - rhs._ref.position()); + typename bit_pointer::difference_type>::type +operator-(bit_pointer lhs, bit_pointer rhs) { + using lhs_type = typename bit_pointer::difference_type; + using rhs_type = typename bit_pointer::difference_type; + using difference_type = typename std::common_type::type; + constexpr difference_type lhs_digits = binary_digits::value; + constexpr difference_type rhs_digits = binary_digits::value; + constexpr difference_type digits = rhs_digits; + static_assert(lhs_digits == rhs_digits, ""); + const difference_type main = lhs._ptr - rhs._ptr; + return main * digits + (lhs.position() - rhs.position()); } // -------------------------------------------------------------------------- // @@ -421,78 +343,44 @@ constexpr typename std::common_type< // ------------------- BIT POINTER: COMPARISON OPERATORS -------------------- // // Checks if the left hand side is equal to the right hand side template -constexpr bool operator==( - bit_pointer lhs, - bit_pointer rhs -) noexcept -{ - return lhs._ref.address() == rhs._ref.address() - && lhs._ref.position() == rhs._ref.position(); +constexpr bool operator==(bit_pointer lhs, bit_pointer rhs) noexcept { + return lhs._ptr == rhs._ptr && lhs.position() == rhs.position(); } // Checks if the left hand side is non equal to the right hand side template -constexpr bool operator!=( - bit_pointer lhs, - bit_pointer rhs -) noexcept -{ - return lhs._ref.address() != rhs._ref.address() - || lhs._ref.position() != rhs._ref.position(); +constexpr bool operator!=(bit_pointer lhs, bit_pointer rhs) noexcept { + return lhs._ptr != rhs._ptr || lhs.position() != rhs.position(); } // Checks if the left hand side is less than the right hand side template -constexpr bool operator<( - bit_pointer lhs, - bit_pointer rhs -) noexcept -{ - return lhs._ref.address() < rhs._ref.address() - || (lhs._ref.address() == rhs._ref.address() - && lhs._ref.position() < rhs._ref.position()); +constexpr bool operator<(bit_pointer lhs, bit_pointer rhs) noexcept { + return lhs._ptr < rhs._ptr || (lhs._ptr == rhs._ptr && lhs.position() < rhs.position()); } // Checks if the left hand side is less than or equal to the right hand side template -constexpr bool operator<=( - bit_pointer lhs, - bit_pointer rhs -) noexcept -{ - return lhs._ref.address() < rhs._ref.address() - || (lhs._ref.address() == rhs._ref.address() - && lhs._ref.position() <= rhs._ref.position()); +constexpr bool operator<=(bit_pointer lhs, bit_pointer rhs) noexcept { + return lhs._ptr < rhs._ptr || (lhs._ptr == rhs._ptr && lhs.position() <= rhs.position()); } // Checks if the left hand side is greater than the right hand side template -constexpr bool operator>( - bit_pointer lhs, - bit_pointer rhs -) noexcept -{ - return lhs._ref.address() > rhs._ref.address() - || (lhs._ref.address() == rhs._ref.address() - && lhs._ref.position() > rhs._ref.position()); +constexpr bool operator>(bit_pointer lhs, bit_pointer rhs) noexcept { + return lhs._ptr > rhs._ptr || (lhs._ptr == rhs._ptr && lhs.position() > rhs.position()); } // Checks if the left hand side is greater than or equal to the right hand side template -constexpr bool operator>=( - bit_pointer lhs, - bit_pointer rhs -) noexcept -{ - return lhs._ref.address() > rhs._ref.address() - || (lhs._ref.address() == rhs._ref.address() - && lhs._ref.position() >= rhs._ref.position()); +constexpr bool operator>=(bit_pointer lhs, bit_pointer rhs) noexcept { + return lhs._ptr > rhs._ptr || (lhs._ptr == rhs._ptr && lhs.position() >= rhs.position()); } // -------------------------------------------------------------------------- // // ========================================================================== // -} // namespace bit +} // namespace bit #endif // _BIT_POINTER_HPP_INCLUDED // ========================================================================== // diff --git a/include/bitlib/bit_concepts.hpp b/include/bitlib/bit_concepts.hpp index f5aad7c9..e717de5c 100644 --- a/include/bitlib/bit_concepts.hpp +++ b/include/bitlib/bit_concepts.hpp @@ -30,10 +30,6 @@ concept pointer_like = reference_like && requires(po // Must be dereferenceable { *proxy } -> std::same_as; - // Must support arrow operator functionality (not necessarily as a member function) - requires(std::is_pointer_v || - requires(pointer_type p) { { p.operator->() } -> std::same_as; }); - // Must be assignable from reference_type* { proxy = &ref } -> std::same_as; From 1c6a87929c4c98aebfc858208e8dbcf9b7d310c5 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 11 Apr 2025 10:09:47 -0400 Subject: [PATCH 33/85] Remove unneeded member functions from reference --- include/bitlib/bit-iterator/bit_reference.hpp | 38 +------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index 7ce8be23..baab9dd9 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -85,13 +85,7 @@ class bit_reference constexpr bit_reference& reset() const noexcept; constexpr bit_reference& flip() const noexcept; - // Underlying details - public: - constexpr word_type* address() const noexcept; - constexpr size_type position() const noexcept; - constexpr mask_type mask() const noexcept; - - // Implementation details: function members + // Implementation details: function members private: bit_reference() noexcept = default; #if 0 @@ -266,7 +260,7 @@ template constexpr bit_pointer bit_reference::operator&( ) const noexcept { - return bit_pointer(&_ref, position()); + return bit_pointer(&_ref, _tzcnt(_mask)); } // -------------------------------------------------------------------------- // @@ -328,34 +322,6 @@ constexpr bit_reference& bit_reference::flip() const noexcep } // -------------------------------------------------------------------------- // - - -// ------------------- BIT REFERENCE: UNDERLYING DETAILS -------------------- // -// Returns a pointer to the underlying word -template -constexpr typename bit_reference::word_type* -bit_reference::address() const noexcept { - return &_ref; -} - -// Returns the position of the referenced bit within the underlying word -template -constexpr typename bit_reference::size_type -bit_reference::position( -) const noexcept -{ - return _tzcnt(_mask); -} - -// Returns a mask corresponding to the referenced bit -template -constexpr bit_reference::mask_type bit_reference::mask() const noexcept { - return _mask; -} -// -------------------------------------------------------------------------- // - - - // -------------------------- BIT REFERENCE: SWAP --------------------------- // // Swaps two bit references template From ba46b84735221dd58e147a574c6518e783792b3e Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 11 Apr 2025 10:25:01 -0400 Subject: [PATCH 34/85] mask is not const for pointer --- include/bitlib/bit-iterator/bit_pointer.hpp | 38 +-- include/bitlib/bit-iterator/bit_reference.hpp | 253 +++++++----------- 2 files changed, 104 insertions(+), 187 deletions(-) diff --git a/include/bitlib/bit-iterator/bit_pointer.hpp b/include/bitlib/bit-iterator/bit_pointer.hpp index 25c7ff16..3a69feee 100644 --- a/include/bitlib/bit-iterator/bit_pointer.hpp +++ b/include/bitlib/bit-iterator/bit_pointer.hpp @@ -15,6 +15,8 @@ // ================================ PREAMBLE ================================ // // C++ standard library // Project sources +#include + #include "../bit-containers/bit_bitsof.hpp" #include "bit_details.hpp" // Third-party libraries @@ -38,7 +40,7 @@ class bit_pointer // Types public: using word_type = WordType; - using mask_type = typename std::remove_cv::type; + using mask_type = std::make_unsigned_t>; using size_type = std::size_t; using difference_type = std::ptrdiff_t; @@ -84,46 +86,30 @@ class bit_pointer // Implementation details: data members private: word_type* _ptr; - const mask_type _mask; + mask_type _mask; // Non-member arithmetic operators template - friend constexpr bit_pointer operator+( - typename bit_pointer::difference_type n, - bit_pointer x); + friend constexpr bit_pointer operator+(typename bit_pointer::difference_type n, bit_pointer x); template friend constexpr typename std::common_type< typename bit_pointer::difference_type, typename bit_pointer::difference_type>::type - operator-( - bit_pointer lhs, - bit_pointer rhs); + operator-(bit_pointer lhs, bit_pointer rhs); // Comparison operators template - friend constexpr bool operator==( - bit_pointer lhs, - bit_pointer rhs) noexcept; + friend constexpr bool operator==(bit_pointer lhs, bit_pointer rhs) noexcept; template - friend constexpr bool operator!=( - bit_pointer lhs, - bit_pointer rhs) noexcept; + friend constexpr bool operator!=(bit_pointer lhs, bit_pointer rhs) noexcept; template - friend constexpr bool operator<( - bit_pointer lhs, - bit_pointer rhs) noexcept; + friend constexpr bool operator<(bit_pointer lhs, bit_pointer rhs) noexcept; template - friend constexpr bool operator<=( - bit_pointer lhs, - bit_pointer rhs) noexcept; + friend constexpr bool operator<=(bit_pointer lhs, bit_pointer rhs) noexcept; template - friend constexpr bool operator>( - bit_pointer lhs, - bit_pointer rhs) noexcept; + friend constexpr bool operator>(bit_pointer lhs, bit_pointer rhs) noexcept; template - friend constexpr bool operator>=( - bit_pointer lhs, - bit_pointer rhs) noexcept; + friend constexpr bool operator>=(bit_pointer lhs, bit_pointer rhs) noexcept; }; static_assert(bit_pointer_c, bit_reference>, "bit_pointer does not satisfy bit_pointer_c concept!"); diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index baab9dd9..a4062f62 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -15,6 +15,8 @@ // ================================ PREAMBLE ================================ // // C++ standard library // Project sources +#include + #include "bit_details.hpp" // Third-party libraries // Miscellaneous @@ -39,7 +41,7 @@ class bit_reference public: using word_type = WordType; using size_type = std::size_t; - using mask_type = typename std::remove_cv::type; + using mask_type = std::make_unsigned_t>; // Lifecycle public: @@ -87,14 +89,11 @@ class bit_reference // Implementation details: function members private: - bit_reference() noexcept = default; -#if 0 - explicit constexpr bit_reference(std::nullptr_t) noexcept; -#endif - explicit constexpr bit_reference(word_type* ptr) noexcept; - constexpr bit_reference(word_type* ptr, size_type pos); - - // Implementation details: data members + bit_reference() noexcept = default; + explicit constexpr bit_reference(word_type* ptr) noexcept; + constexpr bit_reference(word_type* ptr, size_type pos); + + // Implementation details: data members private: word_type& _ref; const mask_type _mask; @@ -103,32 +102,17 @@ static_assert(bit_reference_c>, "bit_reference does not s // Swap template -void swap( - bit_reference lhs, - bit_reference rhs -) noexcept; +void swap(bit_reference lhs, bit_reference rhs) noexcept; template -void swap( - bit_reference lhs, - bit_value& rhs -) noexcept; +void swap(bit_reference lhs, bit_value& rhs) noexcept; template -void swap( - bit_value& lhs, - bit_reference rhs -) noexcept; +void swap(bit_value& lhs, bit_reference rhs) noexcept; // Stream functions template -std::basic_istream& operator>>( - std::basic_istream& is, - bit_reference& x -); +std::basic_istream& operator>>(std::basic_istream& is, bit_reference& x); template -std::basic_ostream& operator<<( - std::basic_ostream& os, - bit_reference x -); +std::basic_ostream& operator<<(std::basic_ostream& os, bit_reference x); /* ************************************************************************** */ @@ -137,29 +121,24 @@ std::basic_ostream& operator<<( // Implicitly constructs a bit reference from another bit reference template template -constexpr bit_reference::bit_reference( - const bit_reference& other) noexcept +constexpr bit_reference::bit_reference(const bit_reference& other) noexcept : _ref(other._ref), _mask(other._mask) { } template -constexpr bit_reference::bit_reference( - const bit_reference& other) noexcept +constexpr bit_reference::bit_reference(const bit_reference& other) noexcept : _ref(other._ref), _mask(other._mask) { } // Explicitly constructs an aligned bit reference template -constexpr bit_reference::bit_reference( - word_type& ref) noexcept +constexpr bit_reference::bit_reference(word_type& ref) noexcept : _ref(ref), _mask(1) { } // Explicitly constructs an unaligned bit reference template -constexpr bit_reference::bit_reference( - word_type& ref, - size_type pos) +constexpr bit_reference::bit_reference(word_type& ref, size_type pos) : _ref(ref), _mask(static_cast(1) << pos) { assert(pos < binary_digits::value); } @@ -170,8 +149,7 @@ constexpr bit_reference::bit_reference( // ----------------------- BIT REFERENCE: ASSIGNMENT ------------------------ // // Copies a bit reference to the bit reference template -constexpr bit_reference& bit_reference::operator=( - const bit_reference& other) const noexcept { +constexpr bit_reference& bit_reference::operator=(const bit_reference& other) const noexcept { other ? set() : reset(); return const_cast&>(*this); } @@ -179,33 +157,28 @@ constexpr bit_reference& bit_reference::operator=( // Assigns a bit reference to the bit reference template template -constexpr bit_reference& bit_reference::operator=( - const bit_reference& other) const noexcept { +constexpr bit_reference& bit_reference::operator=(const bit_reference& other) const noexcept { other ? set() : reset(); return const_cast&>(*this); } // Assigns a bit value to the bit reference template -constexpr bit_reference& bit_reference::operator=( - const bit_value val) const noexcept { +constexpr bit_reference& bit_reference::operator=(const bit_value val) const noexcept { val ? set() : reset(); return const_cast&>(*this); } // Assigns the aligned bit of a value to the bit reference template -constexpr bit_reference& bit_reference::assign( - word_type val) const noexcept { +constexpr bit_reference& bit_reference::assign(word_type val) const noexcept { val & 1 ? set() : reset(); return const_cast&>(*this); } // Assigns an unaligned bit of a value to the bit reference template -constexpr bit_reference& bit_reference::assign( - word_type val, - size_type pos) const { +constexpr bit_reference& bit_reference::assign(word_type val, size_type pos) const { assert(pos < binary_digits::value); val >> pos & 1 ? set() : reset(); return const_cast&>(*this); @@ -217,24 +190,21 @@ constexpr bit_reference& bit_reference::assign( // -------------- BIT REFERENCE: BITWISE ASSIGNMENT OPERATORS --------------- // // Assigns the value of the referenced bit through a bitwise and operation template -constexpr bit_reference& bit_reference::operator&=( - bit_value other) const noexcept { +constexpr bit_reference& bit_reference::operator&=(bit_value other) const noexcept { _ref &= ~(_mask * static_cast(!other._value)); return const_cast&>(*this); } // Assigns the value of the referenced bit through a bitwise or operation template -constexpr bit_reference& bit_reference::operator|=( - bit_value other) const noexcept { +constexpr bit_reference& bit_reference::operator|=(bit_value other) const noexcept { _ref |= _mask * static_cast(other._value); return const_cast&>(*this); } // Assigns the value of the referenced bit through a bitwise xor operation template -constexpr bit_reference& bit_reference::operator^=( - bit_value other) const noexcept { +constexpr bit_reference& bit_reference::operator^=(bit_value other) const noexcept { _ref ^= _mask * static_cast(other._value); return const_cast&>(*this); } @@ -245,9 +215,7 @@ constexpr bit_reference& bit_reference::operator^=( // ----------------------- BIT REFERENCE: CONVERSION ------------------------ // // Explicitly converts the bit reference to a boolean value template -constexpr bit_reference::operator bool( -) const noexcept -{ +constexpr bit_reference::operator bool() const noexcept { return _ref & _mask; } // -------------------------------------------------------------------------- // @@ -257,9 +225,7 @@ constexpr bit_reference::operator bool( // ------------------------- BIT REFERENCE: ACCESS -------------------------- // // Gets a bit pointer from the bit reference template -constexpr bit_pointer bit_reference::operator&( -) const noexcept -{ +constexpr bit_pointer bit_reference::operator&() const noexcept { return bit_pointer(&_ref, _tzcnt(_mask)); } // -------------------------------------------------------------------------- // @@ -270,8 +236,7 @@ constexpr bit_pointer bit_reference::operator&( // Swaps the value of the referenced bit with another bit reference template template -void bit_reference::swap( - bit_reference other) const { +void bit_reference::swap(bit_reference other) const { if (other != *this) { flip(); other.flip(); @@ -280,8 +245,7 @@ void bit_reference::swap( // Swaps the value of the referenced bit with a bit value template -void bit_reference::swap( - bit_value& other) const { +void bit_reference::swap(bit_value& other) const { if (other != *this) { flip(); other.flip(); @@ -325,41 +289,29 @@ constexpr bit_reference& bit_reference::flip() const noexcep // -------------------------- BIT REFERENCE: SWAP --------------------------- // // Swaps two bit references template -void swap( - bit_reference lhs, - bit_reference rhs -) noexcept -{ - if (lhs != rhs) { - lhs.flip(); - rhs.flip(); - } +void swap(bit_reference lhs, bit_reference rhs) noexcept { + if (lhs != rhs) { + lhs.flip(); + rhs.flip(); + } } // Swaps a bit reference and a bit value template -void swap( - bit_reference lhs, - bit_value& rhs -) noexcept -{ - if (lhs != rhs) { - lhs.flip(); - rhs.flip(); - } +void swap(bit_reference lhs, bit_value& rhs) noexcept { + if (lhs != rhs) { + lhs.flip(); + rhs.flip(); + } } // Swaps a bit value and a bit reference template -void swap( - bit_value& lhs, - bit_reference rhs -) noexcept -{ - if (lhs != rhs) { - lhs.flip(); - rhs.flip(); - } +void swap(bit_value& lhs, bit_reference rhs) noexcept { + if (lhs != rhs) { + lhs.flip(); + rhs.flip(); + } } // -------------------------------------------------------------------------- // @@ -368,92 +320,71 @@ void swap( // -------------------- BIT REFERENCE: STREAM FUNCTIONS --------------------- // // Extracts a bit reference from an input stream template -std::basic_istream& operator>>( - std::basic_istream& is, - bit_reference& x -) -{ - using stream_type = std::basic_istream; - using traits_type = typename stream_type::traits_type; - using ios_base = typename stream_type::ios_base; - constexpr char zero = '0'; - constexpr char one = '1'; - constexpr typename stream_type::int_type eof = traits_type::eof(); - typename ios_base::iostate state = ios_base::goodbit; - typename stream_type::char_type char_value = 0; - typename stream_type::int_type int_value = 0; - typename stream_type::sentry sentry(is); - bool ok = false; - bit_value tmp = x; - if (sentry) { - try { - int_value = is.rdbuf()->sbumpc(); - if (traits_type::eq_int_type(int_value, eof)) { - state |= ios_base::eofbit; - } else { - char_value = traits_type::to_char_type(int_value); - if (traits_type::eq(char_value, is.widen(zero))) { - tmp.reset(); - ok = true; - } else if (traits_type::eq(char_value, is.widen(one))) { - tmp.set(); - ok = true; - } else { - int_value = is.rdbuf()->sputbackc(char_value); - if (traits_type::eq_int_type(int_value, eof)) { - state |= ios_base::failbit; - } - } - } - } catch(...) { - is.setstate(ios_base::badbit); +std::basic_istream& operator>>(std::basic_istream& is, bit_reference& x) { + using stream_type = std::basic_istream; + using traits_type = typename stream_type::traits_type; + using ios_base = typename stream_type::ios_base; + constexpr char zero = '0'; + constexpr char one = '1'; + constexpr typename stream_type::int_type eof = traits_type::eof(); + typename ios_base::iostate state = ios_base::goodbit; + typename stream_type::char_type char_value = 0; + typename stream_type::int_type int_value = 0; + typename stream_type::sentry sentry(is); + bool ok = false; + bit_value tmp = x; + if (sentry) { + try { + int_value = is.rdbuf()->sbumpc(); + if (traits_type::eq_int_type(int_value, eof)) { + state |= ios_base::eofbit; + } else { + char_value = traits_type::to_char_type(int_value); + if (traits_type::eq(char_value, is.widen(zero))) { + tmp.reset(); + ok = true; + } else if (traits_type::eq(char_value, is.widen(one))) { + tmp.set(); + ok = true; + } else { + int_value = is.rdbuf()->sputbackc(char_value); + if (traits_type::eq_int_type(int_value, eof)) { + state |= ios_base::failbit; + } } + } + } catch (...) { + is.setstate(ios_base::badbit); } - if (ok) { - x = tmp; - } else { - state |= ios_base::failbit; - } - state ? is.setstate(state) : void(); - return is; + } + if (ok) { + x = tmp; + } else { + state |= ios_base::failbit; + } + state ? is.setstate(state) : void(); + return is; } // Inserts a bit reference in an output stream template -std::basic_ostream& operator<<( - std::basic_ostream& os, - bit_reference x -) -{ - constexpr char zero = '0'; - constexpr char one = '1'; - return os << os.widen(x ? one : zero); +std::basic_ostream& operator<<(std::basic_ostream& os, bit_reference x) { + constexpr char zero = '0'; + constexpr char one = '1'; + return os << os.widen(x ? one : zero); } // -------------------------------------------------------------------------- // - - // -------- BIT REFERENCE: IMPLEMENTATION DETAILS: FUNCTION MEMBERS --------- // -// Privately explicitly constructs a bit reference from a nullptr -#if 0 -template -constexpr bit_reference::bit_reference( - std::nullptr_t) noexcept - : _ref(nullptr), _mask() { -} -#endif // Privately explicitly constructs an aligned bit reference from a pointer template -constexpr bit_reference::bit_reference( - word_type* ptr) noexcept +constexpr bit_reference::bit_reference(word_type* ptr) noexcept : _ref(*ptr), _mask(1) { } // Privately explicitly constructs an unaligned bit reference from a pointer template -constexpr bit_reference::bit_reference( - word_type* ptr, - size_type pos) +constexpr bit_reference::bit_reference(word_type* ptr, size_type pos) : _ref(*ptr), _mask(static_cast(1) << pos) { assert(pos < binary_digits::value); } From e1bf56e2663609b54620f79f227c997a8afd5d90 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 11 Apr 2025 19:56:12 -0400 Subject: [PATCH 35/85] Set default WordType, fix mask/pos for dereference --- include/bitlib/bit-containers/bit_span.hpp | 182 +++++++++--------- include/bitlib/bit-containers/bit_vector.hpp | 153 +++++++-------- include/bitlib/bit-iterator/bit_iterator.hpp | 11 +- include/bitlib/bit-iterator/bit_pointer.hpp | 173 +++++++++-------- include/bitlib/bit-iterator/bit_reference.hpp | 140 +++++++------- include/bitlib/bit_concepts.hpp | 4 +- 6 files changed, 330 insertions(+), 333 deletions(-) diff --git a/include/bitlib/bit-containers/bit_span.hpp b/include/bitlib/bit-containers/bit_span.hpp index 525597fc..b81b94ef 100644 --- a/include/bitlib/bit-containers/bit_span.hpp +++ b/include/bitlib/bit-containers/bit_span.hpp @@ -35,119 +35,119 @@ struct bit_span_storage { bit_span_storage(std::size_t size) : size_(size) {} }; -template +template class bit_span : private bit_span_storage { -public: - using word_type = WordType; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - using reference = bit_reference; - using const_reference = const bit_reference; - using pointer = bit_pointer; - using const_pointer = const pointer; - using iterator = bit_iterator::iterator>; - using const_iterator = bit_iterator; - - private: - // The start of the span, represented as a bit_pointer. - pointer data_; - - template - static constexpr size_t subspan_extent() { - if constexpr (NewCount != std::dynamic_extent) - return NewCount; - else if constexpr (Extent != std::dynamic_extent) - return Extent - NewOffset; - else - return std::dynamic_extent; - } + public: + using word_type = WordType; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = bit_reference; + using const_reference = const bit_reference; + using pointer = bit_pointer; + using const_pointer = const pointer; + using iterator = bit_iterator::iterator>; + using const_iterator = bit_iterator; + + private: + // The start of the span, represented as a bit_pointer. + pointer data_; + + template + static constexpr size_t subspan_extent() { + if constexpr (NewCount != std::dynamic_extent) + return NewCount; + else if constexpr (Extent != std::dynamic_extent) + return Extent - NewOffset; + else + return std::dynamic_extent; + } -public: - // --- Constructors --- + public: + // --- Constructors --- - // Default constructor: - constexpr bit_span() noexcept; + // Default constructor: + constexpr bit_span() noexcept; - constexpr bit_span(const bit_span& other) noexcept; + constexpr bit_span(const bit_span& other) noexcept; - // Construct from a bit_pointer and a bit count. - constexpr bit_span(pointer data, size_type bit_count) noexcept - requires(Extent == std::dynamic_extent); - constexpr bit_span(pointer data) noexcept; + // Construct from a bit_pointer and a bit count. + constexpr bit_span(pointer data, size_type bit_count) noexcept + requires(Extent == std::dynamic_extent); + constexpr bit_span(pointer data) noexcept; - // Construct from a WordType pointer and a bit count. - constexpr bit_span(WordType* word_ptr, size_type bit_count) noexcept - requires(Extent == std::dynamic_extent); - constexpr bit_span(WordType* word_ptr) noexcept; - constexpr bit_span(WordType& word_ref, size_type bit_count) noexcept - requires(Extent == std::dynamic_extent); - constexpr bit_span(WordType& word_ref) noexcept; + // Construct from a WordType pointer and a bit count. + constexpr bit_span(WordType* word_ptr, size_type bit_count) noexcept + requires(Extent == std::dynamic_extent); + constexpr bit_span(WordType* word_ptr) noexcept; + constexpr bit_span(WordType& word_ref, size_type bit_count) noexcept + requires(Extent == std::dynamic_extent); + constexpr bit_span(WordType& word_ref) noexcept; - // Construct from a iterator and a bit count. - constexpr bit_span(iterator iter, size_type bit_count) noexcept - requires(Extent == std::dynamic_extent); - constexpr bit_span(iterator iter) noexcept; + // Construct from a iterator and a bit count. + constexpr bit_span(iterator iter, size_type bit_count) noexcept + requires(Extent == std::dynamic_extent); + constexpr bit_span(iterator iter) noexcept; - constexpr bit_span(WordType& s) - requires(std::is_scalar_v); - constexpr bit_span(WordType* s) - requires(std::is_scalar_v); + constexpr bit_span(WordType& s) + requires(std::is_scalar_v); + constexpr bit_span(WordType* s) + requires(std::is_scalar_v); - // --- Observers --- + // --- Observers --- - // Returns the number of bits in the span. - constexpr size_type size() const noexcept; - // Checks if the span is empty. - constexpr bool empty() const noexcept; + // Returns the number of bits in the span. + constexpr size_type size() const noexcept; + // Checks if the span is empty. + constexpr bool empty() const noexcept; - // --- Element Access --- + // --- Element Access --- - // Non-const element access. - constexpr reference operator[](size_type pos); - // Const element access returns a const_reference. - constexpr const_reference operator[](size_type pos) const; - // Bounds-checked access - constexpr reference at(size_type idx); - // Bounds-checked access - constexpr const_reference at(size_type idx) const; + // Non-const element access. + constexpr reference operator[](size_type pos); + // Const element access returns a const_reference. + constexpr const_reference operator[](size_type pos) const; + // Bounds-checked access + constexpr reference at(size_type idx); + // Bounds-checked access + constexpr const_reference at(size_type idx) const; - // Non-const front/back access. - constexpr reference front(); - constexpr reference back(); + // Non-const front/back access. + constexpr reference front(); + constexpr reference back(); - // Const front/back access returns a const_reference. - constexpr const_reference front() const; - constexpr const_reference back() const; + // Const front/back access returns a const_reference. + constexpr const_reference front() const; + constexpr const_reference back() const; - // --- Iterators --- + // --- Iterators --- - // Returns an iterator to the first bit. - constexpr iterator begin() const noexcept; + // Returns an iterator to the first bit. + constexpr iterator begin() const noexcept; - // Returns an iterator just past the last bit. - constexpr iterator end() const noexcept; + // Returns an iterator just past the last bit. + constexpr iterator end() const noexcept; - // --- Subspan Functionality --- + // --- Subspan Functionality --- - // Creates a subspan starting at offset with count bits. - template - constexpr auto subspan() const noexcept -> bit_span()>; - constexpr bit_span subspan(size_type offset, size_type count = std::dynamic_extent) const noexcept - requires(Extent == std::dynamic_extent); + // Creates a subspan starting at offset with count bits. + template + constexpr auto subspan() const noexcept -> bit_span()>; + constexpr bit_span subspan(size_type offset, size_type count = std::dynamic_extent) const noexcept + requires(Extent == std::dynamic_extent); - constexpr bit_span operator[](size_type begin, size_type end) const noexcept; + constexpr bit_span operator[](size_type begin, size_type end) const noexcept; - template - constexpr bit_span first() const noexcept - requires(NewExtent != std::dynamic_extent); - constexpr bit_span first(size_type offset) const noexcept - requires(Extent == std::dynamic_extent); + template + constexpr bit_span first() const noexcept + requires(NewExtent != std::dynamic_extent); + constexpr bit_span first(size_type offset) const noexcept + requires(Extent == std::dynamic_extent); - template - constexpr bit_span last() const noexcept - requires(RevOffset != std::dynamic_extent); - constexpr bit_span last(size_type offset) const noexcept - requires(Extent == std::dynamic_extent); + template + constexpr bit_span last() const noexcept + requires(RevOffset != std::dynamic_extent); + constexpr bit_span last(size_type offset) const noexcept + requires(Extent == std::dynamic_extent); }; static_assert(bit_range>, "bit_span does not satisfy bit_range concept!"); diff --git a/include/bitlib/bit-containers/bit_vector.hpp b/include/bitlib/bit-containers/bit_vector.hpp index be2aff9d..57aa6290 100644 --- a/include/bitlib/bit-containers/bit_vector.hpp +++ b/include/bitlib/bit-containers/bit_vector.hpp @@ -35,93 +35,84 @@ namespace bit { /* ****************************** BIT VECTOR ****************************** */ //! A bit-vector with a similar interface to std::vector -template> +template > class bit_vector { - private: - static constexpr size_t digits = binary_digits::value; - std::vector word_vector; - size_t length_ = 0; - - // TODO are privates always inlined? - // @brief Get the number of words needed to represet num_bits bits - constexpr uint64_t word_count(unsigned int num_bits) { - return ((num_bits + digits - 1) / digits); - } + private: + static constexpr size_t digits = binary_digits::value; + std::vector word_vector; + size_t length_ = 0; + + // TODO are privates always inlined? + // @brief Get the number of words needed to represet num_bits bits + constexpr uint64_t word_count(unsigned int num_bits) { + return ((num_bits + digits - 1) / digits); + } - // Iterator pair constructor specializations - // Passing in iterator over bool - template - typename std::enable_if< - std::is_same< - typename std::iterator_traits::value_type, - bool - >::value - >::type - constexpr range_constructor( - RandomAccessIt first, - RandomAccessIt last, - const Allocator& alloc); - - // Passing in iterator over WordType constructs via whole words - template - typename std::enable_if< - std::is_same< - typename std::iterator_traits::value_type, - WordType - >::value - >::type - constexpr range_constructor( - RandomAccessIt first, - RandomAccessIt last, - const Allocator& alloc); - - public: - /* + // Iterator pair constructor specializations + // Passing in iterator over bool + template + typename std::enable_if< + std::is_same< + typename std::iterator_traits::value_type, + bool>::value>::type constexpr range_constructor(RandomAccessIt first, + RandomAccessIt last, + const Allocator& alloc); + + // Passing in iterator over WordType constructs via whole words + template + typename std::enable_if< + std::is_same< + typename std::iterator_traits::value_type, + WordType>::value>::type constexpr range_constructor(RandomAccessIt first, + RandomAccessIt last, + const Allocator& alloc); + + public: + /* * Types and typedefs */ - using value_type = bit_value; - using base_type = WordType; - using allocator_type = Allocator; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - using reference = bit_reference; - using const_reference = const reference; - using pointer = bit_pointer; - using iterator = bit_iterator::iterator>; - using const_iterator = bit_iterator::const_iterator>; - - - /* + using value_type = bit_value; + using base_type = WordType; + using allocator_type = Allocator; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = bit_reference; + using const_reference = const reference; + using pointer = bit_pointer; + using iterator = bit_iterator::iterator>; + using const_iterator = bit_iterator::const_iterator>; + + /* * Constructors, copies and moves... */ - constexpr bit_vector() noexcept(noexcept(Allocator())); - constexpr explicit bit_vector(const Allocator& alloc) noexcept; - constexpr bit_vector( - size_type count, - value_type bit_val, - const Allocator& alloc=Allocator()); - constexpr explicit bit_vector( - size_type count, - const Allocator& alloc=Allocator()); - template - constexpr bit_vector(Iterator first, Iterator last, const Allocator& alloc = Allocator()); - - template - constexpr bit_vector(std::from_range_t, _Range&& rg, const Allocator& alloc = Allocator()); - - template - constexpr bit_vector( - RandomAccessIt first, - RandomAccessIt last, - const Allocator& alloc=Allocator()); - constexpr bit_vector(const bit_vector& other) = default; - constexpr bit_vector(const bit_vector& other, const Allocator& alloc); - constexpr bit_vector(const bit_vector&& other) noexcept; - constexpr bit_vector(const bit_vector&& other, const Allocator& alloc); - constexpr bit_vector(std::initializer_list init, const Allocator& alloc=Allocator()); - constexpr bit_vector(std::initializer_list init, const Allocator& alloc=Allocator()); - constexpr bit_vector(std::initializer_list init, const Allocator& alloc=Allocator()); - constexpr bit_vector(std::string_view s); + constexpr bit_vector() noexcept(noexcept(Allocator())); + constexpr explicit bit_vector(const Allocator& alloc) noexcept; + constexpr bit_vector( + size_type count, + value_type bit_val, + const Allocator& alloc = Allocator()); + constexpr explicit bit_vector( + size_type count, + const Allocator& alloc = Allocator()); + template + constexpr bit_vector(Iterator first, Iterator last, const Allocator& alloc = Allocator()); + + template + constexpr bit_vector(std::from_range_t, _Range&& rg, const Allocator& alloc = Allocator()); + + template + constexpr bit_vector( + RandomAccessIt first, + RandomAccessIt last, + const Allocator& alloc = Allocator()); + constexpr bit_vector(const bit_vector& other) = default; + constexpr bit_vector(const bit_vector& other, const Allocator& alloc); + constexpr bit_vector(const bit_vector&& other) noexcept; + constexpr bit_vector(const bit_vector&& other, const Allocator& alloc); + constexpr bit_vector(std::initializer_list init, const Allocator& alloc = Allocator()); + constexpr bit_vector(std::initializer_list init, const Allocator& alloc = Allocator()); + constexpr bit_vector(std::initializer_list init, const Allocator& alloc = Allocator()); + constexpr bit_vector(std::string_view s); #if __cplusplus == 201703L ~bit_vector(); diff --git a/include/bitlib/bit-iterator/bit_iterator.hpp b/include/bitlib/bit-iterator/bit_iterator.hpp index 803e8631..39793b33 100644 --- a/include/bitlib/bit-iterator/bit_iterator.hpp +++ b/include/bitlib/bit-iterator/bit_iterator.hpp @@ -47,6 +47,7 @@ class bit_iterator using pointer = bit_pointer; using reference = bit_reference; using size_type = std::size_t; + using mask_type = std::make_unsigned_t>; // Lifecycle public: @@ -84,7 +85,7 @@ class bit_iterator public: constexpr iterator_type base() const; constexpr size_type position() const noexcept; - constexpr typename std::remove_cv::type mask() const noexcept; + constexpr mask_type mask() const noexcept; auto operator<=>(const bit_iterator&) const = default; // Implementation details: data members @@ -347,12 +348,8 @@ bit_iterator::position( // Returns a mask corresponding to the bit associated with the iterator template -constexpr typename std::remove_cv< - typename bit_iterator::word_type ->::type bit_iterator::mask( -) const noexcept -{ - return static_cast(1) << _position; +constexpr bit_iterator::mask_type bit_iterator::mask() const noexcept { + return static_cast(1) << _position; } // -------------------------------------------------------------------------- // diff --git a/include/bitlib/bit-iterator/bit_pointer.hpp b/include/bitlib/bit-iterator/bit_pointer.hpp index 3a69feee..4a66b9d1 100644 --- a/include/bitlib/bit-iterator/bit_pointer.hpp +++ b/include/bitlib/bit-iterator/bit_pointer.hpp @@ -28,91 +28,98 @@ namespace bit { /* ****************************** BIT POINTER ******************************* */ // Bit pointer class definition -template -class bit_pointer -{ - // Assertions - static_assert(binary_digits::value, ""); - - // Friendship - template friend class bit_pointer; - - // Types - public: - using word_type = WordType; - using mask_type = std::make_unsigned_t>; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - - // Lifecycle - public: - constexpr bit_pointer() noexcept; - constexpr bit_pointer(const bit_pointer& other) noexcept; - template - constexpr bit_pointer(const bit_pointer& other) noexcept; - constexpr bit_pointer(std::nullptr_t) noexcept; - explicit constexpr bit_pointer(word_type* ptr) noexcept; - constexpr bit_pointer(word_type* ptr, size_type pos); - - // Assignment - public: - constexpr bit_pointer& operator=(std::nullptr_t) noexcept; - constexpr bit_pointer& operator=(const bit_pointer& other) noexcept; - template - constexpr bit_pointer& operator=(const bit_pointer& other) noexcept; - - // Conversion - public: - explicit constexpr operator bool() const noexcept; - - // Access - public: - constexpr bit_reference operator*() const noexcept; - constexpr bit_reference operator[](difference_type n) const; - - // Increment and decrement operators - public: - constexpr bit_pointer& operator++(); - constexpr bit_pointer& operator--(); - constexpr bit_pointer operator++(int); - constexpr bit_pointer operator--(int); - constexpr bit_pointer operator+(difference_type n) const; - constexpr bit_pointer operator-(difference_type n) const; - constexpr bit_pointer& operator+=(difference_type n); - constexpr bit_pointer& operator-=(difference_type n); - constexpr size_t position() const { return _tzcnt(_mask); } - constexpr word_type* address() const { return _ptr; } - - // Implementation details: data members - private: - word_type* _ptr; - mask_type _mask; - - // Non-member arithmetic operators - template - friend constexpr bit_pointer operator+(typename bit_pointer::difference_type n, bit_pointer x); - template - friend constexpr typename std::common_type< - typename bit_pointer::difference_type, - typename bit_pointer::difference_type>::type - operator-(bit_pointer lhs, bit_pointer rhs); - - // Comparison operators - template - friend constexpr bool operator==(bit_pointer lhs, bit_pointer rhs) noexcept; - template - friend constexpr bool operator!=(bit_pointer lhs, bit_pointer rhs) noexcept; - template - friend constexpr bool operator<(bit_pointer lhs, bit_pointer rhs) noexcept; - template - friend constexpr bool operator<=(bit_pointer lhs, bit_pointer rhs) noexcept; - template - friend constexpr bool operator>(bit_pointer lhs, bit_pointer rhs) noexcept; - template - friend constexpr bool operator>=(bit_pointer lhs, bit_pointer rhs) noexcept; +template +class bit_pointer { + // Assertions + static_assert(binary_digits::value, ""); + + // Friendship + template + friend class bit_pointer; + + // Types + public: + using iterator_type = WordType*; + using value_type = bit_value; + using word_type = WordType; + using mask_type = std::make_unsigned_t>; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = bit_pointer; + using reference = bit_reference; + + // Lifecycle + public: + constexpr bit_pointer() noexcept; + constexpr bit_pointer(const bit_pointer& other) noexcept; + template + constexpr bit_pointer(const bit_pointer& other) noexcept; + constexpr bit_pointer(std::nullptr_t) noexcept; + explicit constexpr bit_pointer(word_type* ptr) noexcept; + constexpr bit_pointer(word_type* ptr, size_type pos); + + // Assignment + public: + constexpr bit_pointer& operator=(std::nullptr_t) noexcept; + constexpr bit_pointer& operator=(const bit_pointer& other) noexcept; + template + constexpr bit_pointer& operator=(const bit_pointer& other) noexcept; + + // Conversion + public: + explicit constexpr operator bool() const noexcept; + + // Access + public: + constexpr bit_reference operator*() const noexcept; + constexpr bit_reference operator[](difference_type n) const; + + // Increment and decrement operators + public: + constexpr bit_pointer& operator++(); + constexpr bit_pointer& operator--(); + constexpr bit_pointer operator++(int); + constexpr bit_pointer operator--(int); + constexpr bit_pointer operator+(difference_type n) const; + constexpr bit_pointer operator-(difference_type n) const; + constexpr bit_pointer& operator+=(difference_type n); + constexpr bit_pointer& operator-=(difference_type n); + constexpr size_type position() const { return _tzcnt(_mask); } + constexpr word_type* address() const { return _ptr; } + constexpr mask_type mask() const { return _mask; } + constexpr iterator_type base() const { return this->ptr; } + + // Implementation details: data members + private: + word_type* _ptr; + mask_type _mask; + + // Non-member arithmetic operators + template + friend constexpr bit_pointer operator+(typename bit_pointer::difference_type n, bit_pointer x); + template + friend constexpr typename std::common_type< + typename bit_pointer::difference_type, + typename bit_pointer::difference_type>::type + operator-(bit_pointer lhs, bit_pointer rhs); + + // Comparison operators + template + friend constexpr bool operator==(bit_pointer lhs, bit_pointer rhs) noexcept; + template + friend constexpr bool operator!=(bit_pointer lhs, bit_pointer rhs) noexcept; + template + friend constexpr bool operator<(bit_pointer lhs, bit_pointer rhs) noexcept; + template + friend constexpr bool operator<=(bit_pointer lhs, bit_pointer rhs) noexcept; + template + friend constexpr bool operator>(bit_pointer lhs, bit_pointer rhs) noexcept; + template + friend constexpr bool operator>=(bit_pointer lhs, bit_pointer rhs) noexcept; }; static_assert(bit_pointer_c, bit_reference>, "bit_pointer does not satisfy bit_pointer_c concept!"); +static_assert(bit_iterator_c>, "bit_pointer does not satisfy bit_iterator_c concept!"); /* ************************************************************************** */ @@ -201,7 +208,7 @@ constexpr bit_pointer::operator bool() const noexcept { // Gets a bit reference from the bit pointer template constexpr bit_reference bit_pointer::operator*() const noexcept { - return bit_reference(*_ptr, _mask); + return bit_reference(*_ptr, position()); } // Gets a bit reference, decrementing or incrementing the pointer diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index a4062f62..d64ae021 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -27,76 +27,76 @@ namespace bit { /* ***************************** BIT REFERENCE ****************************** */ // Bit reference class definition -template -class bit_reference -{ - // Assertions - static_assert(binary_digits::value, ""); - - // Friendship - template friend class bit_reference; - friend class bit_pointer; - - // Types - public: - using word_type = WordType; - using size_type = std::size_t; - using mask_type = std::make_unsigned_t>; - - // Lifecycle - public: - template - constexpr bit_reference(const bit_reference& other) noexcept; - constexpr bit_reference(const bit_reference& other) noexcept; - explicit constexpr bit_reference(word_type& ref) noexcept; - constexpr bit_reference(word_type& ref, size_type pos); - - // Assignment - public: - constexpr bit_reference& operator=(const bit_reference& other) const noexcept; - template - constexpr bit_reference& operator=(const bit_reference& other) const noexcept; - constexpr bit_reference& operator=(const bit_value val) const noexcept; - constexpr bit_reference& assign(word_type val) const noexcept; - constexpr bit_reference& assign(word_type val, size_type pos) const; - - // Bitwise assignment operators - public: - constexpr bit_reference& operator&=(bit_value other) const noexcept; - constexpr bit_reference& operator|=(bit_value other) const noexcept; - constexpr bit_reference& operator^=(bit_value other) const noexcept; - - // Conversion - public: - explicit constexpr operator bool() const noexcept; - - // Access - public: - constexpr bit_pointer operator&() const noexcept; - - // Swap members - public: - template - void swap(bit_reference other) const; - void swap(bit_value& other) const; - - // Bit manipulation - public: - constexpr bit_reference& set(bool b) const noexcept; - constexpr bit_reference& set() const noexcept; - constexpr bit_reference& reset() const noexcept; - constexpr bit_reference& flip() const noexcept; - - // Implementation details: function members - private: - bit_reference() noexcept = default; - explicit constexpr bit_reference(word_type* ptr) noexcept; - constexpr bit_reference(word_type* ptr, size_type pos); - - // Implementation details: data members - private: - word_type& _ref; - const mask_type _mask; +template +class bit_reference { + // Assertions + static_assert(binary_digits::value, ""); + + // Friendship + template + friend class bit_reference; + friend class bit_pointer; + + // Types + public: + using word_type = WordType; + using size_type = std::size_t; + using mask_type = std::make_unsigned_t>; + + // Lifecycle + public: + template + constexpr bit_reference(const bit_reference& other) noexcept; + constexpr bit_reference(const bit_reference& other) noexcept; + explicit constexpr bit_reference(word_type& ref) noexcept; + constexpr bit_reference(word_type& ref, size_type pos); + + // Assignment + public: + constexpr bit_reference& operator=(const bit_reference& other) const noexcept; + template + constexpr bit_reference& operator=(const bit_reference& other) const noexcept; + constexpr bit_reference& operator=(const bit_value val) const noexcept; + constexpr bit_reference& assign(word_type val) const noexcept; + constexpr bit_reference& assign(word_type val, size_type pos) const; + + // Bitwise assignment operators + public: + constexpr bit_reference& operator&=(bit_value other) const noexcept; + constexpr bit_reference& operator|=(bit_value other) const noexcept; + constexpr bit_reference& operator^=(bit_value other) const noexcept; + + // Conversion + public: + explicit constexpr operator bool() const noexcept; + + // Access + public: + constexpr bit_pointer operator&() const noexcept; + + // Swap members + public: + template + void swap(bit_reference other) const; + void swap(bit_value& other) const; + + // Bit manipulation + public: + constexpr bit_reference& set(bool b) const noexcept; + constexpr bit_reference& set() const noexcept; + constexpr bit_reference& reset() const noexcept; + constexpr bit_reference& flip() const noexcept; + + // Implementation details: function members + private: + bit_reference() noexcept = default; + explicit constexpr bit_reference(word_type* ptr) noexcept; + constexpr bit_reference(word_type* ptr, size_type pos); + + // Implementation details: data members + private: + word_type& _ref; + const mask_type _mask; }; static_assert(bit_reference_c>, "bit_reference does not satisfy bit_reference_c concept!"); diff --git a/include/bitlib/bit_concepts.hpp b/include/bitlib/bit_concepts.hpp index e717de5c..51d40259 100644 --- a/include/bitlib/bit_concepts.hpp +++ b/include/bitlib/bit_concepts.hpp @@ -59,13 +59,15 @@ concept bit_iterator_c = // Must have a nested size_type, iterator_type. typename It::size_type; typename It::iterator_type; + typename It::word_type; + typename It::difference_type; requires std::integral; requires std::random_access_iterator; // It must provide base(), position(), and mask() with proper return types. { it.base() } -> std::same_as; { it.position() } -> std::same_as; - { it.mask() } -> std::same_as::value_type>>; + { it.mask() } -> std::same_as>>; }; template From 7266b730bb76a2de119eacd4c7ee9b4d8548b13d Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 11 Apr 2025 20:06:57 -0400 Subject: [PATCH 36/85] Use iterator_type nested type --- include/bitlib/bit-algorithms/count.hpp | 13 +++--- include/bitlib/bit-algorithms/shift.hpp | 40 +++++++++---------- include/bitlib/bit-algorithms/swap_ranges.hpp | 6 ++- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/include/bitlib/bit-algorithms/count.hpp b/include/bitlib/bit-algorithms/count.hpp index 1a804b17..3f02be01 100644 --- a/include/bitlib/bit-algorithms/count.hpp +++ b/include/bitlib/bit-algorithms/count.hpp @@ -44,6 +44,7 @@ count( // Types and constants using word_type = typename bit_iterator::word_type; using difference_type = typename bit_iterator::difference_type; + using iterator_type = typename bit_iterator::iterator_type; constexpr difference_type digits = binary_digits::value; // Initialization @@ -51,13 +52,13 @@ count( // Computation when bits belong to several underlying words if (first.base() != last.base()) { - RandomAccessIt it = first.base(); + iterator_type it = first.base(); - if (first.position() != 0) { - word_type first_value = *first.base() >> first.position(); - result = _popcnt(first_value); - ++it; - } + if (first.position() != 0) { + word_type first_value = *first.base() >> first.position(); + result = _popcnt(first_value); + ++it; + } // The SIMD implementation here is actually slower than the standard //#ifdef BITLIB_HWY //// ReduceSum not implemented for unsigned char diff --git a/include/bitlib/bit-algorithms/shift.hpp b/include/bitlib/bit-algorithms/shift.hpp index 75f55a6c..fbb27df1 100644 --- a/include/bitlib/bit-algorithms/shift.hpp +++ b/include/bitlib/bit-algorithms/shift.hpp @@ -56,6 +56,7 @@ bit_iterator shift_left( using word_type = typename bit_iterator::word_type; using size_type = typename bit_iterator::size_type; using difference_type = typename bit_iterator::difference_type; + using iterator_type = typename bit_iterator::iterator_type; constexpr size_type digits = binary_digits::value; // Initialization @@ -92,9 +93,9 @@ bit_iterator shift_left( // Triggered if all remaining bits can fit in a word if (d - n <= digits) { - word_type new_word = get_word(middle, d - n); - write_word(new_word, first, d - n); - return first + d - n; + word_type new_word = get_word(middle, d - n); + write_word(new_word, first, d - n); + return first + d - n; } // Multiple word case word_type first_value = *first.base(); @@ -155,17 +156,15 @@ bit_iterator shift_left( // At this point, first is aligned if (offset == 0) { - first = bit::bit_iterator( - STD_SHIFT_LEFT(first.base(), - last.base(), - word_shifts), - 0 - ); - if (!is_last_aligned) - { - write_word(*last.base(), first, last.position()); - first += last.position(); - } + first = bit::bit_iterator( + STD_SHIFT_LEFT(first.base(), + last.base(), + word_shifts), + 0); + if (!is_last_aligned) { + write_word(*last.base(), first, last.position()); + first += last.position(); + } // https://en.cppreference.com/w/cpp/algorithm/shift // "Elements that are in the original range but not the new range // are left in a valid but unspecified state." @@ -220,8 +219,8 @@ bit_iterator shift_left( if (!is_last_aligned) { const difference_type bits_left = last.position() - middle.position(); - const word_type new_word = get_word(middle, bits_left); - write_word(new_word, first, bits_left); + const word_type new_word = get_word(middle, bits_left); + write_word(new_word, first, bits_left); first += bits_left; } @@ -238,6 +237,7 @@ bit_iterator shift_right( // Types and constants using word_type = typename bit_iterator::word_type; using size_type = typename bit_iterator::size_type; + using iterator_type = typename bit_iterator::iterator_type; // Initialization const bool is_last_aligned = last.position() == 0; @@ -270,10 +270,10 @@ bit_iterator shift_right( const size_type bits_to_align = std::min( last.position(), bit::distance(first, middle)); - const word_type word_to_write = get_word( + const word_type word_to_write = get_word( middle - bits_to_align, bits_to_align); - write_word( + write_word( word_to_write, last - bits_to_align, bits_to_align); @@ -355,10 +355,10 @@ bit_iterator shift_right( if (first.position() != middle.position()) { const size_type bits_to_align = bit::distance(first, middle); - const word_type word_to_write = get_word( + const word_type word_to_write = get_word( first, bits_to_align); - write_word( + write_word( word_to_write, last - bits_to_align, bits_to_align); diff --git a/include/bitlib/bit-algorithms/swap_ranges.hpp b/include/bitlib/bit-algorithms/swap_ranges.hpp index 67cd5a06..4841b639 100644 --- a/include/bitlib/bit-algorithms/swap_ranges.hpp +++ b/include/bitlib/bit-algorithms/swap_ranges.hpp @@ -34,6 +34,8 @@ constexpr bit_iterator swap_ranges( using word_type2 = typename bit_iterator::word_type; using size_type1 = typename bit_iterator::size_type; using size_type2 = typename bit_iterator::size_type; + using iterator_type1 = typename bit_iterator::iterator_type; + using iterator_type2 = typename bit_iterator::iterator_type; constexpr size_type1 digits1 = binary_digits::value; constexpr size_type2 digits2 = binary_digits::value; @@ -41,8 +43,8 @@ constexpr bit_iterator swap_ranges( //const bool is_first1_aligned = first1.position() == 0; const bool is_last1_aligned = last1.position() == 0; //const bool is_first2_aligned = first2.position() == 0; - RandomAccessIt1 it1 = first1.base(); - RandomAccessIt2 it2 = first2.base(); + iterator_type1 it1 = first1.base(); + iterator_type2 it2 = first2.base(); if (first1 == last1) return first2; From 26b63cf9ec0bb5eb1f4f0ecf57a25cb43ea0a473 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 11 Apr 2025 20:14:29 -0400 Subject: [PATCH 37/85] Use template deduction --- include/bitlib/bit-algorithms/rotate.hpp | 2 +- include/bitlib/bit-algorithms/shift.hpp | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/bitlib/bit-algorithms/rotate.hpp b/include/bitlib/bit-algorithms/rotate.hpp index f9509ba8..cbcb038a 100644 --- a/include/bitlib/bit-algorithms/rotate.hpp +++ b/include/bitlib/bit-algorithms/rotate.hpp @@ -240,7 +240,7 @@ bit_iterator rotate( size_type k = distance(first, n_first); word_type temp = get_word(first, k); bit_iterator new_last = shift_left(first, last, k); - write_word(temp, new_last, static_cast(k)); + write_word(temp, new_last, static_cast(k)); return new_last; } else if (is_within(n_first, last)) { size_type p = distance(n_first, last); diff --git a/include/bitlib/bit-algorithms/shift.hpp b/include/bitlib/bit-algorithms/shift.hpp index fbb27df1..d8075a5c 100644 --- a/include/bitlib/bit-algorithms/shift.hpp +++ b/include/bitlib/bit-algorithms/shift.hpp @@ -93,8 +93,8 @@ bit_iterator shift_left( // Triggered if all remaining bits can fit in a word if (d - n <= digits) { - word_type new_word = get_word(middle, d - n); - write_word(new_word, first, d - n); + word_type new_word = get_word(middle, d - n); + write_word(new_word, first, d - n); return first + d - n; } // Multiple word case @@ -162,7 +162,7 @@ bit_iterator shift_left( word_shifts), 0); if (!is_last_aligned) { - write_word(*last.base(), first, last.position()); + write_word(*last.base(), first, last.position()); first += last.position(); } // https://en.cppreference.com/w/cpp/algorithm/shift @@ -219,8 +219,8 @@ bit_iterator shift_left( if (!is_last_aligned) { const difference_type bits_left = last.position() - middle.position(); - const word_type new_word = get_word(middle, bits_left); - write_word(new_word, first, bits_left); + const word_type new_word = get_word(middle, bits_left); + write_word(new_word, first, bits_left); first += bits_left; } @@ -270,10 +270,10 @@ bit_iterator shift_right( const size_type bits_to_align = std::min( last.position(), bit::distance(first, middle)); - const word_type word_to_write = get_word( + const word_type word_to_write = get_word( middle - bits_to_align, bits_to_align); - write_word( + write_word( word_to_write, last - bits_to_align, bits_to_align); @@ -355,10 +355,10 @@ bit_iterator shift_right( if (first.position() != middle.position()) { const size_type bits_to_align = bit::distance(first, middle); - const word_type word_to_write = get_word( + const word_type word_to_write = get_word( first, bits_to_align); - write_word( + write_word( word_to_write, last - bits_to_align, bits_to_align); From 732f4faf987ded3791f8b90e05c4102a41b6cf18 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 11 Apr 2025 20:16:37 -0400 Subject: [PATCH 38/85] Remove unneedded using --- include/bitlib/bit-algorithms/shift.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/bitlib/bit-algorithms/shift.hpp b/include/bitlib/bit-algorithms/shift.hpp index d8075a5c..57c5308d 100644 --- a/include/bitlib/bit-algorithms/shift.hpp +++ b/include/bitlib/bit-algorithms/shift.hpp @@ -56,7 +56,6 @@ bit_iterator shift_left( using word_type = typename bit_iterator::word_type; using size_type = typename bit_iterator::size_type; using difference_type = typename bit_iterator::difference_type; - using iterator_type = typename bit_iterator::iterator_type; constexpr size_type digits = binary_digits::value; // Initialization @@ -156,7 +155,7 @@ bit_iterator shift_left( // At this point, first is aligned if (offset == 0) { - first = bit::bit_iterator( + first = bit::bit_iterator( STD_SHIFT_LEFT(first.base(), last.base(), word_shifts), @@ -237,7 +236,6 @@ bit_iterator shift_right( // Types and constants using word_type = typename bit_iterator::word_type; using size_type = typename bit_iterator::size_type; - using iterator_type = typename bit_iterator::iterator_type; // Initialization const bool is_last_aligned = last.position() == 0; From f52c8b6ac6917628cfad0192faed24da6bb8ec22 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sat, 12 Apr 2025 16:45:46 -0400 Subject: [PATCH 39/85] Replace bit_pointer with bit_iterator --- include/bitlib/bit-iterator/bit_details.hpp | 6 +- include/bitlib/bit-iterator/bit_iterator.hpp | 273 +++++-------- include/bitlib/bit-iterator/bit_pointer.hpp | 379 ------------------ include/bitlib/bit-iterator/bit_reference.hpp | 1 - include/bitlib/bit_concepts.hpp | 1 - 5 files changed, 109 insertions(+), 551 deletions(-) delete mode 100644 include/bitlib/bit-iterator/bit_pointer.hpp diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index 873dd77d..805f1279 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -35,9 +35,11 @@ // Miscellaneous namespace bit { class bit_value; -template class bit_reference; -template class bit_pointer; +template +class bit_reference; template class bit_iterator; +template +using bit_pointer = bit_iterator; // ========================================================================== // diff --git a/include/bitlib/bit-iterator/bit_iterator.hpp b/include/bitlib/bit-iterator/bit_iterator.hpp index 39793b33..f0cac905 100644 --- a/include/bitlib/bit-iterator/bit_iterator.hpp +++ b/include/bitlib/bit-iterator/bit_iterator.hpp @@ -32,6 +32,9 @@ namespace bit { template class bit_iterator { + template + friend class bit_iterator; + // Assertions private: using _traits_t = _cv_iterator_traits; @@ -84,8 +87,8 @@ class bit_iterator // Underlying details public: constexpr iterator_type base() const; + constexpr iterator_type address() const; constexpr size_type position() const noexcept; - constexpr mask_type mask() const noexcept; auto operator<=>(const bit_iterator&) const = default; // Implementation details: data members @@ -113,50 +116,33 @@ class bit_iterator // ------------------------ BIT ITERATOR: LIFECYCLE ------------------------- // // Implicitly default constructs a bit iterator template -constexpr bit_iterator::bit_iterator( -) -: _current() -, _position() -{ +constexpr bit_iterator::bit_iterator() + : _current(), _position() { } // Implicitly constructs a bit iterator from another bit iterator template template -constexpr bit_iterator::bit_iterator( - const bit_iterator& other -) -: _current(other.base()) -, _position(other.position()) -{ +constexpr bit_iterator::bit_iterator(const bit_iterator& other) + : _current(other.base()), _position(other.position()) { } // Explicitly constructs an aligned bit iterator from an iterator template -constexpr bit_iterator::bit_iterator( - const iterator_type i -) -: _current(i) -, _position(0) -{ +constexpr bit_iterator::bit_iterator(const iterator_type i) + : _current(i), _position(0) { } // Explicitly constructs an unaligned bit iterator from an iterator template -constexpr bit_iterator::bit_iterator( - const iterator_type i, - size_type pos -) -: _current(i) -, _position((assert(pos < binary_digits::value), pos)) -{ +constexpr bit_iterator::bit_iterator(const iterator_type i, size_type pos) + : _current(i), _position((assert(pos < binary_digits::value), pos)) { } - // Explicitly constructs an unaligned bit iterator from a pointer template constexpr bit_iterator::bit_iterator(const pointer& ptr) - : _current(ptr.address()), _position(ptr.position()) { + : _current(ptr._current), _position(ptr.position()) { } // -------------------------------------------------------------------------- // @@ -167,12 +153,10 @@ constexpr bit_iterator::bit_iterator(const pointer& ptr) template template constexpr bit_iterator& bit_iterator::operator=( - const bit_iterator& other -) -{ - _current = other._current; - _position = other._position; - return *this; + const bit_iterator& other) { + _current = other._current; + _position = other._position; + return *this; } // -------------------------------------------------------------------------- // @@ -182,35 +166,26 @@ constexpr bit_iterator& bit_iterator::operator=( // Gets a bit reference from the bit iterator template constexpr typename bit_iterator::reference -bit_iterator::operator*( -) const noexcept -{ - return reference(*_current, _position); +bit_iterator::operator*() const noexcept { + return reference(*_current, _position); } // Gets a pointer to a bit template -constexpr typename bit_iterator::pointer -bit_iterator::operator->( -) const noexcept -{ - return pointer(&*_current, _position); +constexpr typename bit_iterator::pointer bit_iterator::operator->() const noexcept { + return pointer(&*_current, _position); } // Gets a bit reference, decrementing or incrementing the iterator template -constexpr typename bit_iterator::reference -bit_iterator::operator[]( - difference_type n -) const -{ - constexpr difference_type digits = binary_digits::value; - const difference_type sum = _position + n; - difference_type diff = sum / digits; - if (sum < 0 && diff * digits != sum) { - --diff; - } - return reference(*std::next(_current, diff), sum - diff * digits); +constexpr typename bit_iterator::reference bit_iterator::operator[](difference_type n) const { + constexpr difference_type digits = binary_digits::value; + const difference_type sum = _position + n; + difference_type diff = sum / digits; + if (sum < 0 && diff * digits != sum) { + --diff; + } + return reference(*std::next(_current, diff), sum - diff * digits); } // -------------------------------------------------------------------------- // @@ -219,177 +194,139 @@ bit_iterator::operator[]( // ------------- BIT ITERATOR: INCREMENT AND DECREMENT OPERATORS ------------ // // Increments the bit iterator and returns it template -constexpr bit_iterator& bit_iterator::operator++( -) -{ - constexpr size_type digits = binary_digits::value; - if (_position + 1 < digits) { - ++_position; - } else { - ++_current; - _position = 0; - } - return *this; +constexpr bit_iterator& bit_iterator::operator++() { + constexpr size_type digits = binary_digits::value; + if (_position + 1 < digits) { + ++_position; + } else { + ++_current; + _position = 0; + } + return *this; } // Decrements the bit iterator and returns it template -constexpr bit_iterator& bit_iterator::operator--( -) -{ - constexpr size_type digits = binary_digits::value; - if (_position) { - --_position; - } else { - --_current; - _position = digits - 1; - } - return *this; +constexpr bit_iterator& bit_iterator::operator--() { + constexpr size_type digits = binary_digits::value; + if (_position) { + --_position; + } else { + --_current; + _position = digits - 1; + } + return *this; } // Increments the bit iterator and returns the old one template -constexpr bit_iterator bit_iterator::operator++( - int -) -{ - bit_iterator old = *this; - ++(*this); - return old; +constexpr bit_iterator bit_iterator::operator++(int) { + bit_iterator old = *this; + ++(*this); + return old; } // Decrements the bit iterator and returns the old one template -constexpr bit_iterator bit_iterator::operator--( - int -) -{ - bit_iterator old = *this; - --(*this); - return old; +constexpr bit_iterator bit_iterator::operator--(int) { + bit_iterator old = *this; + --(*this); + return old; } // Looks forward several bits and gets an iterator at this position template -constexpr bit_iterator bit_iterator::operator+( - difference_type n -) const -{ - constexpr difference_type digits = binary_digits::value; - const difference_type sum = _position + n; - difference_type diff = sum / digits; - if (sum < 0 && diff * digits != sum) { - --diff; - } - return bit_iterator(std::next(_current, diff), sum - diff * digits); +constexpr bit_iterator bit_iterator::operator+(difference_type n) const { + constexpr difference_type digits = binary_digits::value; + const difference_type sum = _position + n; + difference_type diff = sum / digits; + if (sum < 0 && diff * digits != sum) { + --diff; + } + return bit_iterator(std::next(_current, diff), sum - diff * digits); } // Looks backward several bits and gets an iterator at this position template -constexpr bit_iterator bit_iterator::operator-( - difference_type n -) const -{ - constexpr difference_type digits = binary_digits::value; - const difference_type sum = _position - n; - difference_type diff = sum / digits; - if (sum < 0 && diff * digits != sum) { - --diff; - } - return bit_iterator(std::next(_current, diff), sum - diff * digits); +constexpr bit_iterator bit_iterator::operator-(difference_type n) const { + constexpr difference_type digits = binary_digits::value; + const difference_type sum = _position - n; + difference_type diff = sum / digits; + if (sum < 0 && diff * digits != sum) { + --diff; + } + return bit_iterator(std::next(_current, diff), sum - diff * digits); } // Increments the iterator by several bits and returns it template -constexpr bit_iterator& bit_iterator::operator+=( - difference_type n -) -{ - *this = *this + n; - return *this; +constexpr bit_iterator& bit_iterator::operator+=(difference_type n) { + *this = *this + n; + return *this; } // Decrements the iterator by several bits and returns it template -constexpr bit_iterator& bit_iterator::operator-=( - difference_type n -) -{ - *this = *this - n; - return *this; +constexpr bit_iterator& bit_iterator::operator-=(difference_type n) { + *this = *this - n; + return *this; } // -------------------------------------------------------------------------- // - template -constexpr bit_iterator::difference_type bit_iterator::operator-(const bit_iterator& other) const { - constexpr difference_type digits = binary_digits::value; - return (_current - other._current) * digits + (_position - other._position); +constexpr bit_iterator::difference_type +bit_iterator::operator-(const bit_iterator& other) const { + constexpr difference_type digits = binary_digits::value; + return (_current - other._current) * digits + (_position - other._position); } // -------------------- BIT ITERATOR: UNDERLYING DETAILS -------------------- // // Returns a copy of the underlying iterator template -constexpr typename bit_iterator::iterator_type -bit_iterator::base( -) const -{ - return _current; +constexpr typename bit_iterator::iterator_type bit_iterator::base() const { + return _current; } -// Returns the position of the bit within the underlying word template -constexpr typename bit_iterator::size_type -bit_iterator::position( -) const noexcept -{ - return _position; +constexpr typename bit_iterator::iterator_type bit_iterator::address() const { + return _current; } -// Returns a mask corresponding to the bit associated with the iterator +// Returns the position of the bit within the underlying word template -constexpr bit_iterator::mask_type bit_iterator::mask() const noexcept { - return static_cast(1) << _position; +constexpr typename bit_iterator::size_type bit_iterator::position() const noexcept { + return _position; } -// -------------------------------------------------------------------------- // - - // -------------- BIT ITERATOR: NON-MEMBER ARITHMETIC OPERATORS ------------- // // Advances the bit iterator several times template -constexpr bit_iterator operator+( - typename bit_iterator::difference_type n, - const bit_iterator& i -) -{ - return i + n; +constexpr bit_iterator operator+(typename bit_iterator::difference_type n, const bit_iterator& i) { + return i + n; } // Computes the distance in bits separating two bit iterators template constexpr typename std::common_type< typename bit_iterator::difference_type, - typename bit_iterator::difference_type ->::type operator-( - const bit_iterator& lhs, - const bit_iterator& rhs -) -{ - using lhs_utype = typename bit_iterator::word_type; - using rhs_utype = typename bit_iterator::word_type; - using lhs_type = typename bit_iterator::difference_type; - using rhs_type = typename bit_iterator::difference_type; - using difference_type = typename std::common_type::type; - constexpr difference_type lhs_digits = binary_digits::value; - constexpr difference_type rhs_digits = binary_digits::value; - constexpr difference_type digits = rhs_digits; - static_assert(lhs_digits == rhs_digits, ""); - const difference_type main = lhs._current - rhs._current; - return main * digits + (lhs._position - rhs._position); + typename bit_iterator::difference_type>::type +operator-(const bit_iterator& lhs, const bit_iterator& rhs) { + using lhs_utype = typename bit_iterator::word_type; + using rhs_utype = typename bit_iterator::word_type; + using lhs_type = typename bit_iterator::difference_type; + using rhs_type = typename bit_iterator::difference_type; + using difference_type = typename std::common_type::type; + constexpr difference_type lhs_digits = binary_digits::value; + constexpr difference_type rhs_digits = binary_digits::value; + constexpr difference_type digits = rhs_digits; + static_assert(lhs_digits == rhs_digits, ""); + const difference_type main = lhs._current - rhs._current; + return main * digits + (lhs._position - rhs._position); } static_assert(bit_iterator_c>, "bit_iterator does not satisfy bit_iterator_c concept!"); +static_assert(bit_pointer_c, bit_reference>, "bit_pointer does not satisfy bit_pointer_c concept!"); +static_assert(bit_iterator_c>, "bit_pointer does not satisfy bit_iterator_c concept!"); // ========================================================================== // } // namespace bit diff --git a/include/bitlib/bit-iterator/bit_pointer.hpp b/include/bitlib/bit-iterator/bit_pointer.hpp deleted file mode 100644 index 4a66b9d1..00000000 --- a/include/bitlib/bit-iterator/bit_pointer.hpp +++ /dev/null @@ -1,379 +0,0 @@ -// ============================== BIT POINTER =============================== // -// Project: The C++ Bit Library -// Name: bit_pointer.hpp -// Description: A class representing a pointer to a bit -// Creator: Vincent Reverdy -// Contributor(s): Vincent Reverdy [2015-2017] -// License: BSD 3-Clause License -// ========================================================================== // -#ifndef _BIT_POINTER_HPP_INCLUDED -#define _BIT_POINTER_HPP_INCLUDED -// ========================================================================== // - - - -// ================================ PREAMBLE ================================ // -// C++ standard library -// Project sources -#include - -#include "../bit-containers/bit_bitsof.hpp" -#include "bit_details.hpp" -// Third-party libraries -// Miscellaneous -namespace bit { -// ========================================================================== // - - - -/* ****************************** BIT POINTER ******************************* */ -// Bit pointer class definition -template -class bit_pointer { - // Assertions - static_assert(binary_digits::value, ""); - - // Friendship - template - friend class bit_pointer; - - // Types - public: - using iterator_type = WordType*; - using value_type = bit_value; - using word_type = WordType; - using mask_type = std::make_unsigned_t>; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - using pointer = bit_pointer; - using reference = bit_reference; - - // Lifecycle - public: - constexpr bit_pointer() noexcept; - constexpr bit_pointer(const bit_pointer& other) noexcept; - template - constexpr bit_pointer(const bit_pointer& other) noexcept; - constexpr bit_pointer(std::nullptr_t) noexcept; - explicit constexpr bit_pointer(word_type* ptr) noexcept; - constexpr bit_pointer(word_type* ptr, size_type pos); - - // Assignment - public: - constexpr bit_pointer& operator=(std::nullptr_t) noexcept; - constexpr bit_pointer& operator=(const bit_pointer& other) noexcept; - template - constexpr bit_pointer& operator=(const bit_pointer& other) noexcept; - - // Conversion - public: - explicit constexpr operator bool() const noexcept; - - // Access - public: - constexpr bit_reference operator*() const noexcept; - constexpr bit_reference operator[](difference_type n) const; - - // Increment and decrement operators - public: - constexpr bit_pointer& operator++(); - constexpr bit_pointer& operator--(); - constexpr bit_pointer operator++(int); - constexpr bit_pointer operator--(int); - constexpr bit_pointer operator+(difference_type n) const; - constexpr bit_pointer operator-(difference_type n) const; - constexpr bit_pointer& operator+=(difference_type n); - constexpr bit_pointer& operator-=(difference_type n); - constexpr size_type position() const { return _tzcnt(_mask); } - constexpr word_type* address() const { return _ptr; } - constexpr mask_type mask() const { return _mask; } - constexpr iterator_type base() const { return this->ptr; } - - // Implementation details: data members - private: - word_type* _ptr; - mask_type _mask; - - // Non-member arithmetic operators - template - friend constexpr bit_pointer operator+(typename bit_pointer::difference_type n, bit_pointer x); - template - friend constexpr typename std::common_type< - typename bit_pointer::difference_type, - typename bit_pointer::difference_type>::type - operator-(bit_pointer lhs, bit_pointer rhs); - - // Comparison operators - template - friend constexpr bool operator==(bit_pointer lhs, bit_pointer rhs) noexcept; - template - friend constexpr bool operator!=(bit_pointer lhs, bit_pointer rhs) noexcept; - template - friend constexpr bool operator<(bit_pointer lhs, bit_pointer rhs) noexcept; - template - friend constexpr bool operator<=(bit_pointer lhs, bit_pointer rhs) noexcept; - template - friend constexpr bool operator>(bit_pointer lhs, bit_pointer rhs) noexcept; - template - friend constexpr bool operator>=(bit_pointer lhs, bit_pointer rhs) noexcept; -}; - -static_assert(bit_pointer_c, bit_reference>, "bit_pointer does not satisfy bit_pointer_c concept!"); -static_assert(bit_iterator_c>, "bit_pointer does not satisfy bit_iterator_c concept!"); -/* ************************************************************************** */ - - - -// ------------------------- BIT POINTER: LIFECYCLE ------------------------- // -// Implicitly default constructs a null bit pointer -template -constexpr bit_pointer::bit_pointer() noexcept - : _ptr(nullptr), _mask(0) { -} - -template -constexpr bit_pointer::bit_pointer(const bit_pointer& other) noexcept - : _ptr(other._ptr), _mask(other._mask) { -} - -// Implicitly constructs a bit pointer from another bit pointer -template -template -constexpr bit_pointer::bit_pointer(const bit_pointer& other) noexcept - : _ptr(other._ptr), _mask(other._mask) { -} - -// Explicitly constructs a bit pointer from a null pointer -template -constexpr bit_pointer::bit_pointer(std::nullptr_t) noexcept - : _ptr(nullptr), _mask(0) { -} - -// Explicitly constructs an aligned bit pointer from a pointer -template -constexpr bit_pointer::bit_pointer(word_type* ptr) noexcept - : _ptr(ptr), _mask(1) { -} - -// Explicitly constructs an unaligned bit pointer from a pointer -template -constexpr bit_pointer::bit_pointer( - word_type* ptr, - size_type pos) - : _ptr(ptr), _mask(1 << pos) { -} -// -------------------------------------------------------------------------- // - -// ------------------------ BIT POINTER: ASSIGNMENT ------------------------- // -// Assigns a null pointer to the bit pointer -template -constexpr bit_pointer& bit_pointer::operator=( - std::nullptr_t) noexcept { - _ptr = nullptr; - _mask = 0; - return *this; -} - -// Copies a bit pointer to the bit pointer -template -constexpr bit_pointer& bit_pointer::operator=(const bit_pointer& other) noexcept { - _ptr = other._ptr; - _mask = other._mask; - return *this; -} - -// Assigns a bit pointer to the bit pointer -template -template -constexpr bit_pointer& bit_pointer::operator=(const bit_pointer& other) noexcept { - _ptr = other._ptr; - _mask = other._mask; - return *this; -} -// -------------------------------------------------------------------------- // - - - -// ------------------------ BIT POINTER: CONVERSION ------------------------- // -// Returns true if the bit pointer is null, false otherwise -template -constexpr bit_pointer::operator bool() const noexcept { - return (_ptr == nullptr); -} -// -------------------------------------------------------------------------- // - - - -// -------------------------- BIT POINTER: ACCESS --------------------------- // -// Gets a bit reference from the bit pointer -template -constexpr bit_reference bit_pointer::operator*() const noexcept { - return bit_reference(*_ptr, position()); -} - -// Gets a bit reference, decrementing or incrementing the pointer -template -constexpr bit_reference bit_pointer::operator[](difference_type n) const { - constexpr difference_type digits = binary_digits::value; - const difference_type sum = position() + n; - difference_type diff = sum / digits; - if (sum < 0 && diff * digits != sum) { - --diff; - } - return bit_reference(_ptr + diff, sum - diff * digits); -} -// -------------------------------------------------------------------------- // - - - -// ------------- BIT POINTER: INCREMENT AND DECREMENT OPERATORS ------------- // -// Increments the bit pointer and returns it -template -constexpr bit_pointer& bit_pointer::operator++() { - _mask <<= 1; - if (_mask == 0) { - _mask = 1; - _ptr++; - } - return *this; -} - -// Decrements the bit pointer and returns it -template -constexpr bit_pointer& bit_pointer::operator--() { - _mask >>= 1; - if (_mask == 0) { - _mask = mask_type(1) << (bitsof() - 1); - _ptr--; - } - return *this; -} - -// Increments the bit pointer and returns the old one -template -constexpr bit_pointer bit_pointer::operator++(int) { - bit_pointer old = *this; - ++(*this); - return old; -} - -// Decrements the bit pointer and returns the old one -template -constexpr bit_pointer bit_pointer::operator--(int) { - bit_pointer old = *this; - --(*this); - return old; -} - -// Looks forward several bits and gets a pointer at this position -template -constexpr bit_pointer bit_pointer::operator+(difference_type n) const { - constexpr difference_type digits = binary_digits::value; - const difference_type sum = position() + n; - difference_type diff = sum / digits; - if (sum < 0 && diff * digits != sum) { - --diff; - } - return bit_pointer(_ptr + diff, sum - diff * digits); -} - -// Looks backward several bits and gets a pointer at this position -template -constexpr bit_pointer bit_pointer::operator-(difference_type n) const { - constexpr difference_type digits = binary_digits::value; - const difference_type sum = position() - n; - difference_type diff = sum / digits; - if (sum < 0 && diff * digits != sum) { - --diff; - } - return bit_pointer(_ptr + diff, sum - diff * digits); -} - -// Increments the pointer by several bits and returns it -template -constexpr bit_pointer& bit_pointer::operator+=(difference_type n) { - *this = *this + n; - return *this; -} - -// Decrements the pointer by several bits and returns it -template -constexpr bit_pointer& bit_pointer::operator-=(difference_type n) { - *this = *this - n; - return *this; -} -// -------------------------------------------------------------------------- // - - - -// -------------- BIT POINTER: NON-MEMBER ARITHMETIC OPERATORS -------------- // -// Advances the bit pointer several times -template -constexpr bit_pointer operator+(typename bit_pointer::difference_type n, bit_pointer x) { - return x + n; -} - -// Computes the distance in bits separating two bit pointers -template -constexpr typename std::common_type< - typename bit_pointer::difference_type, - typename bit_pointer::difference_type>::type -operator-(bit_pointer lhs, bit_pointer rhs) { - using lhs_type = typename bit_pointer::difference_type; - using rhs_type = typename bit_pointer::difference_type; - using difference_type = typename std::common_type::type; - constexpr difference_type lhs_digits = binary_digits::value; - constexpr difference_type rhs_digits = binary_digits::value; - constexpr difference_type digits = rhs_digits; - static_assert(lhs_digits == rhs_digits, ""); - const difference_type main = lhs._ptr - rhs._ptr; - return main * digits + (lhs.position() - rhs.position()); -} -// -------------------------------------------------------------------------- // - - - -// ------------------- BIT POINTER: COMPARISON OPERATORS -------------------- // -// Checks if the left hand side is equal to the right hand side -template -constexpr bool operator==(bit_pointer lhs, bit_pointer rhs) noexcept { - return lhs._ptr == rhs._ptr && lhs.position() == rhs.position(); -} - -// Checks if the left hand side is non equal to the right hand side -template -constexpr bool operator!=(bit_pointer lhs, bit_pointer rhs) noexcept { - return lhs._ptr != rhs._ptr || lhs.position() != rhs.position(); -} - -// Checks if the left hand side is less than the right hand side -template -constexpr bool operator<(bit_pointer lhs, bit_pointer rhs) noexcept { - return lhs._ptr < rhs._ptr || (lhs._ptr == rhs._ptr && lhs.position() < rhs.position()); -} - -// Checks if the left hand side is less than or equal to the right hand side -template -constexpr bool operator<=(bit_pointer lhs, bit_pointer rhs) noexcept { - return lhs._ptr < rhs._ptr || (lhs._ptr == rhs._ptr && lhs.position() <= rhs.position()); -} - -// Checks if the left hand side is greater than the right hand side -template -constexpr bool operator>(bit_pointer lhs, bit_pointer rhs) noexcept { - return lhs._ptr > rhs._ptr || (lhs._ptr == rhs._ptr && lhs.position() > rhs.position()); -} - -// Checks if the left hand side is greater than or equal to the right hand side -template -constexpr bool operator>=(bit_pointer lhs, bit_pointer rhs) noexcept { - return lhs._ptr > rhs._ptr || (lhs._ptr == rhs._ptr && lhs.position() >= rhs.position()); -} -// -------------------------------------------------------------------------- // - - - -// ========================================================================== // -} // namespace bit -#endif // _BIT_POINTER_HPP_INCLUDED -// ========================================================================== // diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index d64ae021..2b5a9ab4 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -35,7 +35,6 @@ class bit_reference { // Friendship template friend class bit_reference; - friend class bit_pointer; // Types public: diff --git a/include/bitlib/bit_concepts.hpp b/include/bitlib/bit_concepts.hpp index 51d40259..c7ec6ed2 100644 --- a/include/bitlib/bit_concepts.hpp +++ b/include/bitlib/bit_concepts.hpp @@ -67,7 +67,6 @@ concept bit_iterator_c = // It must provide base(), position(), and mask() with proper return types. { it.base() } -> std::same_as; { it.position() } -> std::same_as; - { it.mask() } -> std::same_as>>; }; template From 810c72451873a0982601426771dcca83d06cc780 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sat, 12 Apr 2025 16:49:59 -0400 Subject: [PATCH 40/85] Add sources to CMake --- CMakeLists.txt | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index a5b0e255..4ad74d8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,43 @@ target_include_directories(bitlib INTERFACE $ $ ) +target_sources(bitlib INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bitlib.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit_concepts.hpp + + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/bit_algorithm_details.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/bit_algorithm.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/copy_backward.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/copy.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/count.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/debug_utils.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/equal.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/fill.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/find.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/libpopcnt.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/move.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/reverse.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/rotate.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/shift.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/swap_ranges.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/transform.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/type_traits.hpp + + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit_array.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit_bitsof.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit-containers.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit_mdspan_accessor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit_span.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit_vector.hpp + + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/bit_details.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/bit.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/bit_iterator.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/bit_reference.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/bit_value.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/linear_overload.hpp +) target_compile_features(bitlib INTERFACE cxx_std_23) From 0bdc9251e2b89e4f8c60151538369fe6967d322a Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sat, 12 Apr 2025 17:01:55 -0400 Subject: [PATCH 41/85] cleanup --- CMakeLists.txt | 133 ++++++++---------- .../bit-containers/bit_mdspan_accessor.hpp | 2 +- include/bitlib/bit-iterator/bit.hpp | 5 +- include/bitlib/bit-iterator/bit_iterator.hpp | 1 + include/bitlib/bit-iterator/bit_reference.hpp | 1 + test/CMakeLists.txt | 39 +++-- 6 files changed, 86 insertions(+), 95 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ad74d8d..60406437 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,60 +2,58 @@ cmake_minimum_required(VERSION 3.14) # set the project name -project(Bit-Vector VERSION 0.3.0) +project(BitLib VERSION 0.3.0) -# set output directory of builds -#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - -# set up linters/checkers -#set(CMAKE_CXX_CPPCHECK cppcheck;--std=c++17;--file-filter=*bitlib*) -#set(CMAKE_CXX_CPPLINT cpplint;--linelength=100;--filter=-whitespace;) -#set(CMAKE_CXX_CLANG_TIDY - #clang-tidy; - #-header-filter=include/;) +option(BITLIB_HWY "Build with google highway SIMD extensions" OFF) +option(BITLIB_BENCHMARK "Build bitlib benchmarks" OFF) +option(BITLIB_EXAMPLE "Build bitlib examples" OFF) +option(BITLIB_TEST "Build bitlib tests" OFF) +option(BITLIB_PROFILE "Buid simple example for profiling" OFF) +option(BITLIB_COVERAGE "Compute test coverage" OFF) +option(BITLIB_MDSPAN "Accessor Support for mdspan" ON) add_library(bitlib INTERFACE) add_library(bitlib::bitlib ALIAS bitlib) target_include_directories(bitlib INTERFACE - $ - $ + $ + $ ) target_sources(bitlib INTERFACE - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bitlib.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit_concepts.hpp - - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/bit_algorithm_details.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/bit_algorithm.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/copy_backward.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/copy.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/count.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/debug_utils.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/equal.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/fill.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/find.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/libpopcnt.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/move.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/reverse.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/rotate.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/shift.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/swap_ranges.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/transform.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/type_traits.hpp - - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit_array.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit_bitsof.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit-containers.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit_mdspan_accessor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit_span.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit_vector.hpp - - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/bit_details.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/bit.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/bit_iterator.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/bit_reference.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/bit_value.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/linear_overload.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bitlib.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit_concepts.hpp + + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/bit_algorithm_details.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/bit_algorithm.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/copy_backward.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/copy.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/count.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/debug_utils.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/equal.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/fill.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/find.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/libpopcnt.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/move.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/reverse.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/rotate.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/shift.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/swap_ranges.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/transform.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/type_traits.hpp + + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit_array.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit_bitsof.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit-containers.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit_mdspan_accessor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit_span.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-containers/bit_vector.hpp + + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/bit_details.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/bit.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/bit_iterator.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/bit_reference.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/bit_value.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/linear_overload.hpp ) target_compile_features(bitlib INTERFACE cxx_std_23) @@ -64,40 +62,33 @@ add_library(bitlib_utils INTERFACE) target_include_directories(bitlib_utils INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/utils) target_sources(bitlib_utils INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/utils/test_utils.hpp) - -option(BITLIB_HWY "Build with google highway SIMD extensions" OFF) -option(BITLIB_BENCHMARK "Build bitlib benchmarks" OFF) -option(BITLIB_EXAMPLE "Build bitlib examples" OFF) -option(BITLIB_TEST "Build bitlib tests" OFF) -option(BITLIB_PROFILE "Buid simple example for profiling" OFF) -option(BITLIB_COVERAGE "Compute test coverage" OFF) -option(BITLIB_MDSPAN "Compute test coverage" ON) - if (BITLIB_MDSPAN) - if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - find_package(mdspan CONFIG REQUIRED) - target_compile_definitions(bitlib INTERFACE MDSPAN_IMPL_STANDARD_NAMESPACE=std MDSPAN_IMPL_PROPOSED_NAMESPACE=experimental) - target_link_libraries(bitlib INTERFACE std::mdspan) - endif() + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + find_package(mdspan CONFIG REQUIRED) + target_compile_definitions(bitlib INTERFACE MDSPAN_IMPL_STANDARD_NAMESPACE=std MDSPAN_IMPL_PROPOSED_NAMESPACE=experimental) + target_link_libraries(bitlib INTERFACE std::mdspan) + endif() endif() if (BITLIB_HWY) - add_definitions(-DBITLIB_HWY) + target_compile_definitions(bitlib INTERFACE -DBITLIB_HWY) endif() -if(BITLIB_BENCHMARK) - set(BENCHMARK_ENABLE_GTEST_TESTS OFF) - add_subdirectory(benchmark) +if (BITLIB_BENCHMARK) + set(BENCHMARK_ENABLE_GTEST_TESTS OFF) + add_subdirectory(benchmark) endif() -if(BITLIB_EXAMPLE) - add_subdirectory(example) + +if (BITLIB_EXAMPLE) + add_subdirectory(example) endif() -if(BITLIB_TEST) - enable_testing() - add_subdirectory(test) + +if (BITLIB_TEST) + enable_testing() + add_subdirectory(test) endif() if(BITLIB_PROFILE) - add_subdirectory(profile) + add_subdirectory(profile) endif() diff --git a/include/bitlib/bit-containers/bit_mdspan_accessor.hpp b/include/bitlib/bit-containers/bit_mdspan_accessor.hpp index c5b22091..68b6febe 100644 --- a/include/bitlib/bit-containers/bit_mdspan_accessor.hpp +++ b/include/bitlib/bit-containers/bit_mdspan_accessor.hpp @@ -9,7 +9,7 @@ #ifndef _BIT_MDSPAN_ACCESSOR_HPP_INCLUDED #define _BIT_MDSPAN_ACCESSOR_HPP_INCLUDED -#include "bitlib/bit-iterator/bit_pointer.hpp" +#include "bitlib/bit-iterator/bit_details.hpp" #include "bitlib/bit-iterator/bit_reference.hpp" #include "bitlib/bit-iterator/bit_value.hpp" diff --git a/include/bitlib/bit-iterator/bit.hpp b/include/bitlib/bit-iterator/bit.hpp index cab3f30a..8446eae2 100644 --- a/include/bitlib/bit-iterator/bit.hpp +++ b/include/bitlib/bit-iterator/bit.hpp @@ -20,10 +20,9 @@ // C++ standard library // Project sources #include "bit_details.hpp" -#include "bit_value.hpp" -#include "bit_reference.hpp" -#include "bit_pointer.hpp" #include "bit_iterator.hpp" +#include "bit_reference.hpp" +#include "bit_value.hpp" // Third-party libraries // Miscellaneous // ========================================================================== // diff --git a/include/bitlib/bit-iterator/bit_iterator.hpp b/include/bitlib/bit-iterator/bit_iterator.hpp index f0cac905..a72ad0b1 100644 --- a/include/bitlib/bit-iterator/bit_iterator.hpp +++ b/include/bitlib/bit-iterator/bit_iterator.hpp @@ -23,6 +23,7 @@ #include "bitlib/bit_concepts.hpp" // Project sources #include "bit_details.hpp" +#include "bit_reference.hpp" // Third-party libraries // Miscellaneous namespace bit { diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index 2b5a9ab4..8160d167 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -18,6 +18,7 @@ #include #include "bit_details.hpp" +#include "bit_value.hpp" // Third-party libraries // Miscellaneous namespace bit { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d19c59cc..82b913f6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,27 +6,26 @@ find_package(GTest REQUIRED) include(GoogleTest) -file(GLOB TEST_SOURCES "src/*.cpp") add_executable(bitlib-tests) target_sources(bitlib-tests PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/src/fixtures.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-array.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-copy_backward.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-copy.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-count.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-equal.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-fill.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-find.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-move.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-reverse.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-rotate.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-shift.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-span.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-swap_ranges.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-transform.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-usecase.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/vector_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/fixtures.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-array.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-copy_backward.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-copy.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-count.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-equal.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-fill.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-find.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-move.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-reverse.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-rotate.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-shift.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-span.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-swap_ranges.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-transform.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-usecase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/vector_test.cpp ) if (BITLIB_MDSPAN) @@ -36,7 +35,7 @@ endif() target_compile_options(bitlib-tests PUBLIC -O0 -g -fno-omit-frame-pointer -Wpedantic -fno-inline) if (BITLIB_COVERAGE) - target_compile_options(bitlib-tests PUBLIC --coverage -fprofile-arcs -ftest-coverage -pg) + target_compile_options(bitlib-tests PUBLIC --coverage -fprofile-arcs -ftest-coverage -pg) endif() # ASAN throws no matter what when using Google HWY, not sure why but need to fix @@ -50,7 +49,7 @@ target_link_libraries(bitlib-tests PRIVATE bitlib GTest::gtest GTest::gtest_main target_link_options(bitlib-tests PRIVATE -pthread -lgcov --coverage) if (NOT BITLIB_GTEST_REPEAT) - set(BITLIB_GTEST_REPEAT 1) + set(BITLIB_GTEST_REPEAT 1) endif() enable_testing() From 79699ea5ddee2ad320f8435bc4f8bfa8dbdba849 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sun, 13 Apr 2025 09:05:35 -0400 Subject: [PATCH 42/85] Warnings and Tidying --- include/bitlib/bit-containers/bit_array.hpp | 2 +- include/bitlib/bit-containers/bit_vector.hpp | 49 +++++++------------- test/src/test-mdspan.cpp | 6 +-- test/src/test-span.cpp | 30 ++++++------ 4 files changed, 36 insertions(+), 51 deletions(-) diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index 6ce9b1ad..8585ad30 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -189,7 +189,7 @@ template constexpr bit_array::bit_array(const std::string_view s) requires(std::is_same_v) { - if(bitsof(*this) != (std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))) [[unlikely]] { + if (bitsof(*this) != static_cast(std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))) [[unlikely]] { throw std::invalid_argument("String contains an invalid number of bits for bit_array."); }; size_type i = 0; diff --git a/include/bitlib/bit-containers/bit_vector.hpp b/include/bitlib/bit-containers/bit_vector.hpp index 57aa6290..8a5ef22d 100644 --- a/include/bitlib/bit-containers/bit_vector.hpp +++ b/include/bitlib/bit-containers/bit_vector.hpp @@ -191,7 +191,7 @@ class bit_vector { /* * Helper functions */ - constexpr std::string debug_string(const_iterator first, const_iterator last); + constexpr std::string debug_string(const_iterator first, const_iterator end); constexpr std::string debug_string(); // TODO Make constexpr @@ -663,42 +663,27 @@ constexpr bit_span bit_vector -constexpr std::string bit_vector::debug_string(const_iterator first, const_iterator last) { - std::string ret = ""; - iterator mem = first; - auto position = 0; - for (iterator it = first; it != last; ++it) { - if (position % digits == 0 && position != 0) { - ret += " "; - } else if (position % 8 == 0 && position != 0) { - ret += '.'; - } - ret += *it == bit1 ? '1' : '0'; - mem = it; - ++position; +template +constexpr std::string bit_vector::debug_string(const_iterator first, const_iterator end) { + std::string ret = ""; + iterator mem = first; + auto position = 0; + for (iterator it = first; it != end; ++it) { + if (position % digits == 0 && position != 0) { + ret += " "; + } else if (position % 8 == 0 && position != 0) { + ret += '.'; } - return ret; + ret += *it == bit1 ? '1' : '0'; + mem = it; + ++position; + } + return ret; } template constexpr std::string bit_vector::debug_string() { - auto first = begin(); - auto last = end(); - std::string ret = ""; - iterator mem = first; - auto position = 0; - for (iterator it = first; it != last; ++it) { - if (position % digits == 0 && position != 0) { - ret += " "; - } else if (position % 8 == 0 && position != 0) { - ret += '.'; - } - ret += *it == bit1 ? '1' : '0'; - mem = it; - ++position; - } - return ret; + return debug_string(begin(), end()); } // -------------------------------------------------------------------------- // diff --git a/test/src/test-mdspan.cpp b/test/src/test-mdspan.cpp index 2f1d1c7b..ca7e5b3e 100644 --- a/test/src/test-mdspan.cpp +++ b/test/src/test-mdspan.cpp @@ -25,9 +25,9 @@ TEST(MdSpanTest, BitDefaultLayout) { bit::bit_default_accessor > myspan(&dynarr[0], 5, 6, 7); - for (int i = 0; i < myspan.extent(0); i++) { - for (int j = 0; j < myspan.extent(1); j++) { - for (int k = 0; k < myspan.extent(2); k++) { + for (size_t i = 0; i < myspan.extent(0); i++) { + for (size_t j = 0; j < myspan.extent(1); j++) { + for (size_t k = 0; k < myspan.extent(2); k++) { bit::bit_value expected = ((rot & 1) ? bit::bit1 : bit::bit0); EXPECT_EQ(dynarr[i * 6 * 7 + j * 7 + k], expected); bit::bit_value actual = myspan[i, j, k]; diff --git a/test/src/test-span.cpp b/test/src/test-span.cpp index 53a9789a..3f1e025a 100644 --- a/test/src/test-span.cpp +++ b/test/src/test-span.cpp @@ -218,23 +218,23 @@ TEST(BitSpanTest, Slice) { // ---------- Const Reference Alias Tests ---------- TEST(BitSpanTest, ConstReferenceAlias) { - WordType word = 0; - bit::bit_span span(&word, 1); - span[0] = bit::bit1; + WordType word = 0; + bit::bit_span span(&word, 1); + span[0] = bit::bit1; - // Create a const view. - const bit::bit_span& cspan = span; + // Create a const view. + const bit::bit_span& cspan = span; - // Use a static_assert to verify that the type of cspan[0] - // is the same as the defined const_reference. - static_assert(std::is_same_v::const_reference>, - "cspan[0] is not of type const_reference"); + // Use a static_assert to verify that the type of cspan[0] + // is the same as the defined const_reference. + static_assert(std::is_same_v::const_reference>, + "cspan[0] is not of type const_reference"); - // At runtime, verify the value is as expected. - EXPECT_EQ(cspan[0], bit::bit1); + // At runtime, verify the value is as expected. + EXPECT_EQ(cspan[0], bit::bit1); - // The following assignment should fail to compile if uncommented, - // which confirms that the const_reference is read-only. - // cspan[0] = bit::bit0; + // The following assignment should fail to compile if uncommented, + // which confirms that the const_reference is read-only. + // cspan[0] = bit::bit0; } From 13cb512af0089fc14c81d7852cdc069cc4146710 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Mon, 14 Apr 2025 18:37:10 -0400 Subject: [PATCH 43/85] casting integrals to/from bit_array --- include/bitlib/bit-containers/bit_array.hpp | 73 +++++++++++++++- .../bit_array_dynamic_extent.hpp | 86 ++++++++++++++++++- include/bitlib/bit-iterator/bit_pointer.hpp | 2 + include/bitlib/bit-iterator/bit_reference.hpp | 2 + include/bitlib/bit_concepts.hpp | 2 +- 5 files changed, 158 insertions(+), 7 deletions(-) diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index d06e1a8d..fcc8523a 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -20,6 +20,8 @@ #include #include #include +#include // memcpy + // Project sources #include "bitlib/bit-iterator/bit.hpp" #include "bitlib/bit-algorithms/bit_algorithm.hpp" @@ -34,7 +36,7 @@ template , uint8_t, T>::type> class bit_array { public: - static constexpr std::size_t bits = N; + static constexpr std::size_t bits = N * bitsof(); using word_type = W; using value_type = T; using size_type = std::size_t; @@ -61,6 +63,8 @@ class bit_array { */ constexpr bit_array() noexcept; constexpr bit_array(value_type bit_val); + template + constexpr bit_array(const U& integral) requires (bitsof() <= bits); constexpr bit_array(const bit_array& other) = default; constexpr bit_array(const bit_array&& other) noexcept; constexpr bit_array(const std::initializer_list init) @@ -115,7 +119,10 @@ class bit_array { */ constexpr void fill(value_type bit_val) noexcept; constexpr void swap(bit_array& other) noexcept; - //constexpr std::synth-three-way-result operator<=>() const noexcept; + template + explicit constexpr operator U() const noexcept requires (bitsof() >= (bitsof() * N)); + constexpr std::string debug_string() const; + constexpr std::string debug_string(const_iterator first, const_iterator end) const; }; static_assert(bit_range>, "bit_array does not satisfy bit_range concept!"); @@ -150,6 +157,26 @@ constexpr bit_array::bit_array(bit_array::value_type bit fill(bit_val); } +template +template +constexpr bit_array::bit_array(const U& integral) requires (bitsof() <= bit_array::bits) { + std::memcpy(&storage[0], &integral, sizeof(integral)); + + bool sign_extend = false; + if constexpr (std::is_signed_v) { + sign_extend = (integral & (1 << (bitsof()-1))) ? true : false; + } + if (sign_extend) { + for (auto it = begin()+bitsof(); it != end(); ++it) { + *it = bit1; + } + } else { + for (auto it = begin()+bitsof(); it != end(); ++it) { + *it = bit0; + } + } +} + template constexpr void bit_array::fill(bit_array::value_type bit_val) noexcept { std::fill(this->begin(), this->end(), bit_val); @@ -323,6 +350,48 @@ constexpr typename bit_array::const_iterator bit_array:: // -------------------------------------------------------------------------- // +template +template +constexpr bit_array::operator U() const noexcept requires (bitsof() >= (bitsof() * N)) { + U result; + std::memcpy(&result, &storage[0], sizeof(U)); + + if constexpr (std::is_signed_v && begin()[size()-1]) { + for (size_type i = size(); i < bitsof(); ++i) { + result |= (static_cast(1) << i); + } + } else { + for (size_type i = size(); i < bitsof(); ++i) { + result &= ~(static_cast(1) << i); + } + } + return result; +} + +template +constexpr std::string bit_array::debug_string() const { + return debug_string(begin(), end()); +} + +template +constexpr std::string bit_array::debug_string( + bit_array::const_iterator first, + bit_array::const_iterator end) const { + auto digits = bitsof(); + std::string ret = ""; + auto position = 0; + for (auto it = first; it != end; ++it) { + if (position % digits == 0 && position != 0) { + ret += " "; + } else if (position % 8 == 0 && position != 0) { + ret += '.'; + } + ret += *it == bit1 ? '1' : '0'; + ++position; + } + return ret; +} + // ========================================================================== // template constexpr void _parameter_pack_base_bits_prefix_len(size_t& base, size_t& bits, size_t& prefix_len, uint64_t& num) { diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index 8b92e326..2e951cba 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -15,6 +15,7 @@ #include #include // std::dynamic_extent #include +#include // memcpy #include "bitlib/bit-algorithms/bit_algorithm.hpp" #include "bitlib/bit-containers/bit_array.hpp" @@ -51,8 +52,10 @@ class bit_array { * Constructors, copies and moves... */ bit_array() = delete; - constexpr bit_array(size_type size); - constexpr bit_array(size_type size, value_type bit_val); + constexpr bit_array(const size_type size); + template + constexpr bit_array(const size_type size, const U& integral); + constexpr bit_array(const size_type size, const value_type bit_val); constexpr bit_array(const bit_array& other); constexpr bit_array(const bit_array&& other); constexpr bit_array(const std::initializer_list init) @@ -109,7 +112,11 @@ class bit_array { */ constexpr void fill(value_type bit_val) noexcept; constexpr void swap(bit_array& other) noexcept; + template + explicit constexpr operator U() const noexcept; //constexpr std::synth-three-way-result operator<=>() const noexcept; + constexpr std::string debug_string() const; + constexpr std::string debug_string(const_iterator first, const_iterator end) const; }; static_assert(bit_range>, "bit_array<> does not satisfy bit_contiguous_range concept!"); @@ -127,7 +134,33 @@ constexpr bit_array::bit_array() noexcept */ template -constexpr bit_array::bit_array(size_type size) +template +constexpr bit_array::bit_array(const size_type size, const U& integral) +: m_size(size), + storage(std::unique_ptr( + static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { + assert (bitsof() <= size); + for (std::size_t i = 0; i < Words(m_size); ++i) { + new (storage.get() + i) word_type(); + } + std::memcpy(&storage[0], &integral, sizeof(integral)); + bool sign_extend = false; + if constexpr (std::is_signed_v) { + sign_extend = (integral & (1 << (bitsof()-1))) ? true : false; + } + if (sign_extend) { + for (auto it = begin()+bitsof(); it != end(); ++it) { + *it = bit1; + } + } else { + for (auto it = begin()+bitsof(); it != end(); ++it) { + *it = bit0; + } + } +} + +template +constexpr bit_array::bit_array(const size_type size) : m_size(size), storage(std::unique_ptr( static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { @@ -138,7 +171,7 @@ constexpr bit_array::bit_array(size_type size) } template -constexpr bit_array::bit_array(size_type size, value_type bit_val) +constexpr bit_array::bit_array(const size_type size, const value_type bit_val) : m_size(size), storage(std::unique_ptr( static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { @@ -363,6 +396,51 @@ constexpr typename bit_array::const_iterator bit_a // -------------------------------------------------------------------------- // + +template +template +constexpr bit_array::operator U() const noexcept { + assert(size() <= bitsof()); + U result; + std::memcpy(&result, &storage[0], sizeof(U)); + + if constexpr (std::is_signed_v && begin()[size()-1]) { + for (size_type i = size(); i < bitsof(); ++i) { + result |= (static_cast(1) << i); + } + } else { + for (size_type i = size(); i < bitsof(); ++i) { + result &= ~(static_cast(1) << i); + } + } + return result; +} + + +template +constexpr std::string bit_array::debug_string() const { + return debug_string(begin(), end()); +} +template +constexpr std::string bit_array::debug_string( + bit_array::const_iterator first, + bit_array::const_iterator end) const { + auto digits = bitsof(); + std::string ret = ""; + auto position = 0; + for (auto it = first; it != end; ++it) { + if (position % digits == 0 && position != 0) { + ret += " "; + } else if (position % 8 == 0 && position != 0) { + ret += '.'; + } + ret += *it == bit1 ? '1' : '0'; + ++position; + } + return ret; +} + + // ========================================================================== // } // namespace bit diff --git a/include/bitlib/bit-iterator/bit_pointer.hpp b/include/bitlib/bit-iterator/bit_pointer.hpp index 41ba5f51..087eebdb 100644 --- a/include/bitlib/bit-iterator/bit_pointer.hpp +++ b/include/bitlib/bit-iterator/bit_pointer.hpp @@ -44,6 +44,7 @@ class bit_pointer public: constexpr bit_pointer() noexcept; template + requires (std::is_const_v == std::is_const_v) constexpr bit_pointer(const bit_pointer& other) noexcept; constexpr bit_pointer(std::nullptr_t) noexcept; explicit constexpr bit_pointer(word_type* ptr) noexcept; @@ -146,6 +147,7 @@ constexpr bit_pointer::bit_pointer( // Implicitly constructs a bit pointer from another bit pointer template template +requires (std::is_const_v == std::is_const_v) constexpr bit_pointer::bit_pointer( const bit_pointer& other ) noexcept diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index 86906645..7ecb1212 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -43,6 +43,7 @@ class bit_reference // Lifecycle public: template + requires (std::is_const_v == std::is_const_v) constexpr bit_reference(const bit_reference& other) noexcept; constexpr bit_reference(const bit_reference& other) noexcept; explicit constexpr bit_reference(word_type& ref) noexcept; @@ -140,6 +141,7 @@ std::basic_ostream& operator<<( // Implicitly constructs a bit reference from another bit reference template template +requires (std::is_const_v == std::is_const_v) constexpr bit_reference::bit_reference( const bit_reference& other ) noexcept diff --git a/include/bitlib/bit_concepts.hpp b/include/bitlib/bit_concepts.hpp index f5aad7c9..5a9e410f 100644 --- a/include/bitlib/bit_concepts.hpp +++ b/include/bitlib/bit_concepts.hpp @@ -5,7 +5,7 @@ #include #include -#if defined(__GNUC__) && (__GNUC__ < 13) +#if defined(__GNUC__) && (__GNUC__ < 14) namespace std { struct from_range_t { explicit from_range_t() = default; From 5ca51480bf8ffd09e2d9922e1770f875becc1db3 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Thu, 24 Apr 2025 23:16:14 -0400 Subject: [PATCH 44/85] Changes to support clang19 --- include/bitlib/bit-containers/bit_array.hpp | 15 ++++++++------- include/bitlib/bit-containers/bit_span.hpp | 15 +-------------- include/bitlib/bit-containers/bit_vector.hpp | 4 ++-- include/bitlib/bit-iterator/bit_iterator.hpp | 12 ++++++++++-- include/bitlib/bit_concepts.hpp | 2 +- test/CMakeLists.txt | 10 +++++++++- test/src/test-mdspan.cpp | 4 ++++ 7 files changed, 35 insertions(+), 27 deletions(-) diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index 8585ad30..915eaf90 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -15,6 +15,7 @@ // ================================ PREAMBLE ================================ // // C++ standard library #include +#include #include #include #include @@ -48,7 +49,7 @@ class bit_array { private: static constexpr std::size_t Words = (N * bitsof() + bitsof() - 1) / bitsof(); - alignas(V) std::array storage; + alignas(static_cast(V)) std::array storage; public: using iterator = typename std::conditional, @@ -371,18 +372,18 @@ constexpr void _parameter_pack_base_bits_prefix_len(size_t& base, size_t& bits, uint8_t decimal; switch (base) { case 2: - decimal = Bit - '0'; + decimal = static_cast(Bit - '0'); break; case 10: - decimal = Bit - '0'; + decimal = static_cast(Bit - '0'); break; case 16: - decimal = Bit - '0'; + decimal = static_cast(Bit - '0'); if (Bit >= 'a' && Bit <= 'f') { - decimal = Bit - 'a' + 10u; + decimal = static_cast(Bit - 'a') + 10u; } if (Bit >= 'A' && Bit <= 'F') { - decimal = Bit - 'A' + 10u; + decimal = static_cast(Bit - 'A') + 10u; } break; } @@ -415,7 +416,7 @@ constexpr std::pair _parameter_pack_decode_prefixed_num() { if (base == 2) { bits = digits; } else if (base == 10) { - bits = static_cast(std::ceil(digits * std::log2(10))); + bits = std::bit_width(num); } else { bits = 4 * digits; } diff --git a/include/bitlib/bit-containers/bit_span.hpp b/include/bitlib/bit-containers/bit_span.hpp index b81b94ef..cb5debda 100644 --- a/include/bitlib/bit-containers/bit_span.hpp +++ b/include/bitlib/bit-containers/bit_span.hpp @@ -45,7 +45,7 @@ class bit_span : private bit_span_storage { using const_reference = const bit_reference; using pointer = bit_pointer; using const_pointer = const pointer; - using iterator = bit_iterator::iterator>; + using iterator = bit_iterator; using const_iterator = bit_iterator; private: @@ -83,11 +83,6 @@ class bit_span : private bit_span_storage { requires(Extent == std::dynamic_extent); constexpr bit_span(WordType& word_ref) noexcept; - // Construct from a iterator and a bit count. - constexpr bit_span(iterator iter, size_type bit_count) noexcept - requires(Extent == std::dynamic_extent); - constexpr bit_span(iterator iter) noexcept; - constexpr bit_span(WordType& s) requires(std::is_scalar_v); constexpr bit_span(WordType* s) @@ -201,11 +196,6 @@ constexpr bit_span::bit_span(WordType& word_ref, size_type bit requires(Extent == std::dynamic_extent) : bit_span(&word_ref, bit_count) {} -template -constexpr bit_span::bit_span(iterator iter, size_type bit_count) noexcept - requires(Extent == std::dynamic_extent) - : bit_span_storage(bit_count), data_(&(*iter)) {} - // Construct from a bit_pointer and a bit count. template constexpr bit_span::bit_span(pointer data) noexcept : bit_span_storage(), data_(data) {} @@ -213,9 +203,6 @@ constexpr bit_span::bit_span(pointer data) noexcept : bit_span template constexpr bit_span::bit_span(WordType* word_ptr) noexcept : bit_span_storage(), data_(pointer(word_ptr, 0)) {} -template -constexpr bit_span::bit_span(iterator iter) noexcept : bit_span_storage(), data_(&(*iter)) {} - template constexpr bit_span::bit_span(WordType* word_ptr) requires(std::is_scalar_v) diff --git a/include/bitlib/bit-containers/bit_vector.hpp b/include/bitlib/bit-containers/bit_vector.hpp index 8a5ef22d..663d341f 100644 --- a/include/bitlib/bit-containers/bit_vector.hpp +++ b/include/bitlib/bit-containers/bit_vector.hpp @@ -186,7 +186,7 @@ class bit_vector { /* * Slice */ - constexpr bit_span operator[](size_type offset, size_type right) const noexcept; + constexpr bit_span operator[](size_type begin, size_type end) const noexcept; /* * Helper functions @@ -658,7 +658,7 @@ constexpr void bit_vector::append_range(R&& range) { * Slice */ template -constexpr bit_span bit_vector::operator[](size_type offset, size_type right) const noexcept { +constexpr bit_span bit_vector::operator[](size_type begin, size_type end) const noexcept { return bit_span(&this->at(begin), end - begin); } diff --git a/include/bitlib/bit-iterator/bit_iterator.hpp b/include/bitlib/bit-iterator/bit_iterator.hpp index a72ad0b1..953b60a9 100644 --- a/include/bitlib/bit-iterator/bit_iterator.hpp +++ b/include/bitlib/bit-iterator/bit_iterator.hpp @@ -20,6 +20,7 @@ #include #include +#include "bitlib/bit-containers/bit_bitsof.hpp" #include "bitlib/bit_concepts.hpp" // Project sources #include "bit_details.hpp" @@ -57,10 +58,12 @@ class bit_iterator public: constexpr bit_iterator(); template - constexpr bit_iterator(const bit_iterator& other); + constexpr bit_iterator(const bit_iterator& other) + requires std::constructible_from; explicit constexpr bit_iterator(iterator_type i); constexpr bit_iterator(iterator_type i, size_type pos); - explicit constexpr bit_iterator(const pointer& ptr); + explicit constexpr bit_iterator(const pointer& ptr) + requires std::constructible_from; // Assignment public: @@ -125,7 +128,9 @@ constexpr bit_iterator::bit_iterator() template template constexpr bit_iterator::bit_iterator(const bit_iterator& other) + requires std::constructible_from : _current(other.base()), _position(other.position()) { + assert(_position < bitsof()); } // Explicitly constructs an aligned bit iterator from an iterator @@ -143,7 +148,9 @@ constexpr bit_iterator::bit_iterator(const iterator_type i, size_type // Explicitly constructs an unaligned bit iterator from a pointer template constexpr bit_iterator::bit_iterator(const pointer& ptr) + requires std::constructible_from : _current(ptr._current), _position(ptr.position()) { + assert(_position < bitsof()); } // -------------------------------------------------------------------------- // @@ -157,6 +164,7 @@ constexpr bit_iterator& bit_iterator::operator=( const bit_iterator& other) { _current = other._current; _position = other._position; + assert(_position < bitsof()); return *this; } // -------------------------------------------------------------------------- // diff --git a/include/bitlib/bit_concepts.hpp b/include/bitlib/bit_concepts.hpp index c7ec6ed2..8155ae80 100644 --- a/include/bitlib/bit_concepts.hpp +++ b/include/bitlib/bit_concepts.hpp @@ -5,7 +5,7 @@ #include #include -#if defined(__GNUC__) && (__GNUC__ < 13) +#if defined(__GNUC__) && (__GNUC__ < 13) && !defined(__clang__) namespace std { struct from_range_t { explicit from_range_t() = default; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 82b913f6..21f38b2c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,7 +3,15 @@ # Testing # ########### -find_package(GTest REQUIRED) +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.16.x +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) include(GoogleTest) add_executable(bitlib-tests) diff --git a/test/src/test-mdspan.cpp b/test/src/test-mdspan.cpp index ca7e5b3e..6e95ab7d 100644 --- a/test/src/test-mdspan.cpp +++ b/test/src/test-mdspan.cpp @@ -4,7 +4,11 @@ #include #include "gtest/gtest.h" +#if defined(MDSPAN_IMPL_STANDARD_NAMESPACE) #include "mdspan/mdspan.hpp" +#else +#include +#endif TEST(MdSpanTest, BitDefaultLayout) { uint32_t rot = 0xDEADBEEF; From 1b4a6f707da88a62701ac9b9731e3f114f5dee84 Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Fri, 25 Apr 2025 19:12:06 -0400 Subject: [PATCH 45/85] cmake-multi-platform.yml --- .github/workflows/cmake-multi-platform.yml | 79 ++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 .github/workflows/cmake-multi-platform.yml diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml new file mode 100644 index 00000000..2bf992d9 --- /dev/null +++ b/.github/workflows/cmake-multi-platform.yml @@ -0,0 +1,79 @@ +# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml +name: CMake on multiple platforms + +on: + push: + branches: [ "master" ] + paths-ignore: + - '**/*.md' + pull_request: + branches: [ "master" ] + paths-ignore: + - '**/*.md' + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: false + + # Set up a matrix to run the following 3 configurations: + # 1. + # 2. + # 3. + # + # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. + matrix: + os: [ubuntu-latest, windows-latest] + build_type: [Release] + c_compiler: [gcc, clang, cl] + include: + - os: windows-latest + c_compiler: cl + cpp_compiler: cl + - os: ubuntu-latest + c_compiler: gcc + cpp_compiler: g++ + - os: ubuntu-latest + c_compiler: clang + cpp_compiler: clang++ + exclude: + - os: windows-latest + c_compiler: gcc + - os: windows-latest + c_compiler: clang + - os: ubuntu-latest + c_compiler: cl + + steps: + - uses: actions/checkout@v4 + + - name: Set reusable strings + # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -S ${{ github.workspace }} + + - name: Build + # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} --parallel + + - name: Test + working-directory: ${{ steps.strings.outputs.build-output-dir }} + # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --build-config ${{ matrix.build_type }} --parallel From 6866b27185093b524be7b7d094c5fb6bf8a8b874 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 25 Apr 2025 19:14:32 -0400 Subject: [PATCH 46/85] Fetch dependencies automatically if not package found --- CMakeLists.txt | 12 +++++++++++- benchmark/CMakeLists.txt | 14 +++++++++++++- test/CMakeLists.txt | 22 +++++++++++++--------- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 60406437..23d5d50c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,7 +64,17 @@ target_sources(bitlib_utils INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/utils/test_uti if (BITLIB_MDSPAN) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - find_package(mdspan CONFIG REQUIRED) + find_package(mdspan CONFIG) + if (NOT mdspan_DIR OR "${mdspan_DIR}" STREQUAL "mdspan-NOTFOUND") + include(FetchContent) + + FetchContent_Declare( + mdspan + GIT_REPOSITORY https://github.com/kokkos/mdspan.git + GIT_TAG 414a5dccf5c775b2eecc13d408b256e94f23d1d2 # stable 2025/04/25 + ) + FetchContent_MakeAvailable(mdspan) + endif() target_compile_definitions(bitlib INTERFACE MDSPAN_IMPL_STANDARD_NAMESPACE=std MDSPAN_IMPL_PROPOSED_NAMESPACE=experimental) target_link_libraries(bitlib INTERFACE std::mdspan) endif() diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 732c24b6..6a1bbd3d 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -1,6 +1,18 @@ set(BENCHMARK_ENABLE_GTEST_TESTS OFF) -find_package(benchmark REQUIRED) +find_package(benchmark CONFIG) +if (NOT benchmark_DIR OR "${benchmark_DIR}" STREQUAL "benchmark-NOTFOUND") + include(FetchContent) + + FetchContent_Declare( + benchmark + GIT_REPOSITORY https://github.com/google/benchmark.git + GIT_TAG afa23b7699c17f1e26c88cbf95257b20d78d6247 #v1.9.2 + ) + FetchContent_MakeAvailable(benchmark) +endif() + + # set output directory of builds set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 21f38b2c..04cb8eb9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,15 +4,19 @@ ########### include(FetchContent) -FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG v1.16.x -) -# For Windows: Prevent overriding the parent project's compiler/linker settings -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(googletest) -include(GoogleTest) + +find_package(GTest) +if (NOT GTest OR "${GTest_DIR}" STREQUAL "GTest-NOTFOUND") + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG 6910c9d9165801d8827d628cb72eb7ea9dd538c5 # v1.16.x + ) + # For Windows: Prevent overriding the parent project's compiler/linker settings + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(googletest) + include(GoogleTest) +endif() add_executable(bitlib-tests) From 8989a9caaa3fe656ed88840aeac03b72fad1f304 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 25 Apr 2025 19:16:20 -0400 Subject: [PATCH 47/85] Remove old actions --- .github/workflows/cmake.yml | 65 ----------------------- .github/workflows/coverage.yml | 96 ---------------------------------- 2 files changed, 161 deletions(-) delete mode 100644 .github/workflows/cmake.yml delete mode 100644 .github/workflows/coverage.yml diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml deleted file mode 100644 index 4bdbf576..00000000 --- a/.github/workflows/cmake.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: Build - -on: - push: - branches: [ master ] - paths-ignore: - - '**/*.md' - pull_request: - branches: [ master ] - paths-ignore: - - '**/*.md' - -env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: RelWithDebInfo - -jobs: - linux: - # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. - # You can convert this to a matrix build if you need cross-platform coverage. - # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-20.04, ubuntu-22.04] - compiler: - - { compiler: GNU, CC: gcc-11, CXX: g++-11 } - cpp: [17, 20] - - steps: - - name: Cancel Workflow Action - uses: styfle/cancel-workflow-action@0.9.1 - - name: Checkout - uses: actions/checkout@v2 - with: - submodules: 'true' - - name: Install requirements - run: | - sudo apt install build-essential manpages-dev software-properties-common - sudo add-apt-repository ppa:ubuntu-toolchain-r/test - sudo apt update && sudo apt install ${{ matrix.compiler.CC }} ${{ matrix.compiler.CXX }} - sudo apt-get install libgtest-dev - cmake --version - - - - name: Configure CMake - env: - CC: ${{ matrix.compiler.CC }} - CXX: ${{ matrix.compiler.CXX }} - STD: ${{ matrix.cpp }} - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -Bbuild -DBITLIB_TEST=1 -DBITLIB_GTEST_REPEAT=10 -DBITLIB_EXAMPLE=1 -DCMAKE_CXX_STANDARD=${STD} - - - - name: Build - # Build your program with the given configuration - run: cmake --build build -- -j - - - name: Test - run: ctest --test-dir build -j $(nproc --all) - #run: ./bin/bitlib-tests --gtest_repeat=10 --gtest_break_on_failure --gtest_brief=1 - - - name: Run Example - run: ./build/example/example1 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml deleted file mode 100644 index 85591956..00000000 --- a/.github/workflows/coverage.yml +++ /dev/null @@ -1,96 +0,0 @@ -name: Coverage - -on: - push: - branches: [ master ] - paths-ignore: - - '**/*.md' - pull_request: - branches: [ master ] - paths-ignore: - - '**/*.md' - - -env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: RelWithDebInfo - -jobs: - linux: - # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. - # You can convert this to a matrix build if you need cross-platform coverage. - # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-20.04] - compiler: - - { compiler: GNU, CC: gcc-11, CXX: g++-11 } - - steps: - - name: Cancel Workflow Action - uses: styfle/cancel-workflow-action@0.9.1 - - name: Checkout - uses: actions/checkout@v2 - with: - submodules: 'true' - - uses: conda-incubator/setup-miniconda@v2 - with: - miniconda-version: "latest" - activate-environment: test - channels: conda-forge,defaults - channel-priority: true - python-version: 3.8 - - name: Install requirements - shell: bash -l {0} - run: | - sudo apt install build-essential manpages-dev software-properties-common - sudo add-apt-repository ppa:ubuntu-toolchain-r/test - sudo apt update && sudo apt install lcov ${{ matrix.compiler.CC }} ${{ matrix.compiler.CXX }} - sudo apt install libgtest-dev - conda activate test - pip install --user lcov - conda install cppcheck cpplint - cmake --version - - - name: Configure CMake - shell: bash -l {0} - env: - CC: ${{ matrix.compiler.CC }} - CXX: ${{ matrix.compiler.CXX }} - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: | - which cpplint - cmake -Bbuild -DBITLIB_TEST=1 -DBITLIB_COVERAGE=1 -DCMAKE_CXX_CPPCHECK="cppcheck;--std=c++17;--file-filter=*BitLib*" -DCMAKE_CXX_CPPLINT="cpplint;--linelength=140;" - - - - name: Build - shell: bash -l {0} - # Build your program with the given configuration - run: | - cmake --build build -- -j - #run: make bitlib-tests - - - name: Test - shell: bash -l {0} - run: ctest --test-dir build -j $(nproc --all) - #run: ./bin/bitlib-tests --gtest_repeat=10 --gtest_break_on_failure --gtest_brief=1 - - - name: Run lcov - shell: bash -l {0} - run: | - lcov --directory build/test/CMakeFiles/bitlib-tests.dir/src --gcov-tool /usr/bin/gcov-11 --output-file lcov.out --include "*bitlib*" -c - lcov -r lcov.out "*/usr/*" -r lcov.out "*_deps/*" -r lcov.out "*unit-tests*" -r lcov.out "*ext/*" -r lcov.out "*libpopcnt*" -o lcov_filtered.out - - #- name: Code Coverage Report - #uses: romeovs/lcov-reporter-action@v0.2.11 - #with: - #github-token: ${{ secrets.GITHUB_TOKEN }} - #lcov-file: lcov_filtered.out - - - name: Coveralls - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: lcov_filtered.out From d6417396697c69dd5b902cf9d855dfec182c1eee Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 25 Apr 2025 19:28:14 -0400 Subject: [PATCH 48/85] Enable test for workflow --- .github/workflows/cmake-multi-platform.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 2bf992d9..3a931872 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -66,6 +66,7 @@ jobs: -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DBITLIB_TEST=ON -S ${{ github.workspace }} - name: Build From fefdc8f0c87e148ff75a7361e541ed60098dd5a9 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 25 Apr 2025 19:46:55 -0400 Subject: [PATCH 49/85] Fix from_range and install clang-19 --- .github/workflows/cmake-multi-platform.yml | 17 +++++++++++++---- include/bitlib/bit_concepts.hpp | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 3a931872..a84dbfdd 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -29,7 +29,7 @@ jobs: matrix: os: [ubuntu-latest, windows-latest] build_type: [Release] - c_compiler: [gcc, clang, cl] + c_compiler: [gcc, clang-19, cl] include: - os: windows-latest c_compiler: cl @@ -38,19 +38,28 @@ jobs: c_compiler: gcc cpp_compiler: g++ - os: ubuntu-latest - c_compiler: clang - cpp_compiler: clang++ + c_compiler: clang-19 + cpp_compiler: clang++-19 exclude: - os: windows-latest c_compiler: gcc - os: windows-latest - c_compiler: clang + c_compiler: clang-19 - os: ubuntu-latest c_compiler: cl steps: - uses: actions/checkout@v4 + - name: "Install Clang 19" + if: matrix.os == 'ubuntu-latest' && matrix.c_compiler == 'clang-19'" + run: | + sudo apt-get update + sudo apt-get install -y wget gnupg lsb-release + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 19 + - name: Set reusable strings # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. id: strings diff --git a/include/bitlib/bit_concepts.hpp b/include/bitlib/bit_concepts.hpp index 8155ae80..959b86f7 100644 --- a/include/bitlib/bit_concepts.hpp +++ b/include/bitlib/bit_concepts.hpp @@ -5,7 +5,7 @@ #include #include -#if defined(__GNUC__) && (__GNUC__ < 13) && !defined(__clang__) +#if defined(__GNUC__) && (__GNUC__ < 14) && !defined(__clang__) namespace std { struct from_range_t { explicit from_range_t() = default; From 8fbb7e10e4526a4c64327324a4a0773935fcde30 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 25 Apr 2025 19:47:43 -0400 Subject: [PATCH 50/85] yaml syntax --- .github/workflows/cmake-multi-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index a84dbfdd..e70703dd 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -52,7 +52,7 @@ jobs: - uses: actions/checkout@v4 - name: "Install Clang 19" - if: matrix.os == 'ubuntu-latest' && matrix.c_compiler == 'clang-19'" + if: matrix.os == 'ubuntu-latest' && matrix.c_compiler == 'clang-19' run: | sudo apt-get update sudo apt-get install -y wget gnupg lsb-release From dc8f841505590c58c8feaa6c8554f7f4533a8521 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 25 Apr 2025 19:52:30 -0400 Subject: [PATCH 51/85] Use libc++ for clang-19 --- .github/workflows/cmake-multi-platform.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index e70703dd..3ecf542b 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -59,6 +59,8 @@ jobs: wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh sudo ./llvm.sh 19 + # Install libc++ and libc++abi for Clang 19 + sudo apt-get install -y libc++-19-dev libc++abi-19-dev - name: Set reusable strings # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. @@ -76,6 +78,7 @@ jobs: -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DBITLIB_TEST=ON + ${{ matrix.c_compiler == 'clang-19' && '-DCMAKE_CXX_FLAGS=-stdlib=libc++ -DCMAKE_EXE_LINKER_FLAGS=-stdlib=libc++' || '' }} -S ${{ github.workspace }} - name: Build From 9c82f466c3c696e1516f8f493cfe0d1983eb8473 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 25 Apr 2025 20:06:04 -0400 Subject: [PATCH 52/85] Removed windows action target --- .github/workflows/cmake-multi-platform.yml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 3ecf542b..0aca9f32 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -27,26 +27,16 @@ jobs: # # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. matrix: - os: [ubuntu-latest, windows-latest] + os: [ubuntu-latest] # removed windows-latest build_type: [Release] - c_compiler: [gcc, clang-19, cl] + c_compiler: [gcc, clang-19] # removed cl include: - - os: windows-latest - c_compiler: cl - cpp_compiler: cl - os: ubuntu-latest c_compiler: gcc cpp_compiler: g++ - os: ubuntu-latest c_compiler: clang-19 cpp_compiler: clang++-19 - exclude: - - os: windows-latest - c_compiler: gcc - - os: windows-latest - c_compiler: clang-19 - - os: ubuntu-latest - c_compiler: cl steps: - uses: actions/checkout@v4 From 6ee39fd6ae202c232f4737fb286cf5d1c604fc57 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 25 Apr 2025 20:56:14 -0400 Subject: [PATCH 53/85] Add back coverage action --- .github/workflows/cmake-multi-platform.yml | 2 +- .github/workflows/coverage.yml | 109 +++++++++++++++++++++ README.md | 22 ++--- 3 files changed, 121 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/coverage.yml diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 0aca9f32..1af4a4a6 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -79,4 +79,4 @@ jobs: working-directory: ${{ steps.strings.outputs.build-output-dir }} # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --build-config ${{ matrix.build_type }} --parallel + run: ctest --build-config ${{ matrix.build_type }} --output-on-failure --parallel diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000..db7e8265 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,109 @@ +name: Coverage + +on: + push: + branches: [ "master" ] + paths-ignore: + - '**/*.md' + pull_request: + branches: [ "master" ] + paths-ignore: + - '**/*.md' + +jobs: + coverage: + runs-on: ${{ matrix.os }} + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: false + + # Set up a matrix to run the following 3 configurations: + # 1. + # 2. + # 3. + # + # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. + matrix: + os: [ubuntu-latest] # removed windows-latest + build_type: [Debug] + c_compiler: [gcc] + include: + - os: ubuntu-latest + c_compiler: gcc + cpp_compiler: g++ + + steps: + - uses: actions/checkout@v4 + + - name: Set reusable strings + # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + if [ "${{ matrix.c_compiler }}" = "clang-19" ]; then + cmake_flags="-DCMAKE_CXX_FLAGS='--coverage -stdlib=libc++'" + cmake_flags="${cmake_flags} -DCMAKE_C_FLAGS='--coverage'" + cmake_flags="${cmake_flags} -DCMAKE_EXE_LINKER_FLAGS='-stdlib=libc++'" + echo "cmake_flags=${cmake_flags}" + echo "cmake_flags=${cmake_flags}" >> $GITHUB_OUTPUT + else + cmake_flags="-DCMAKE_CXX_FLAGS='--coverage'" + cmake_flags="${cmake_flags} -DCMAKE_C_FLAGS='--coverage'" + echo "cmake_flags=${cmake_flags}" + echo "cmake_flags=${cmake_flags}" >> $GITHUB_OUTPUT + fi + + - name: "Install lcov" + run: | + sudo apt-get update + sudo apt-get install -y lcov + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DBITLIB_TEST=ON + ${{ steps.strings.outputs.cmake_flags }} + -S ${{ github.workspace }} + + - name: Build + # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} --parallel + + - name: Test + # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --test-dir ${{ steps.strings.outputs.build-output-dir }} --build-config ${{ matrix.build_type }} --output-on-failure --parallel + + - name: Generate Coverage Report + run: | + lcov --capture --directory ${{ steps.strings.outputs.build-output-dir }} --output-file coverage.info --ignore-errors mismatch + lcov --remove coverage.info '/usr/*' '*/_deps/*' '*/include/bitlib/bit-algorithms/libpopcnt.h' '*/utils/test_utils.hpp' '*/test/src/*' --output-file coverage.info + lcov --list coverage.info + # Generate an HTML report + genhtml coverage.info --output-directory out/coverage + + - name: Upload coverage artifact + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: out/coverage + + - name: Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: coverage.info + + - name: Upload to Codecov + uses: codecov/codecov-action@v5 + with: + files: coverage.info + flags: unittests + name: codecov-coverage-report diff --git a/README.md b/README.md index 7087e59e..a8f81b4c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # BitLib -![Actions](https://github.com/bkille/BitLib/actions/workflows/cmake.yml/badge.svg?branch=master)[![Coverage Status](https://coveralls.io/repos/github/bkille/BitLib/badge.svg?branch=master)](https://coveralls.io/github/bkille/BitLib?branch=master) +![Actions](https://github.com/PeterCDMcLean/BitLib/actions/workflows/cmake-multi-platform.yml/badge.svg?branch=master)[![Coverage Status](https://coveralls.io/repos/github/PeterCDMcLean/BitLib/badge.svg?branch=master)](https://coveralls.io/github/PeterCDMcLean/BitLib?branch=master) **This repository acts as an efficient replacement of `std::vector`. It provides implementations of many of the functions in [``](https://en.cppreference.com/w/cpp/algorithm) optimized for containers of bits, in addition to providing a `bit_vector` class which has roughly the same interface as `std::vector`**. @@ -38,11 +38,11 @@ int main() { ``` # Installation -BitLib is a header-only libarary. Currently, the BitLib library requires at least `-std=c++17`. +BitLib is a header-only libarary. Currently, the BitLib library requires at least `-std=c++17`. ## CMake -You can automatically fetch the library using Cmake's `FetchContent`. +You can automatically fetch the library using Cmake's `FetchContent`. ```cmake include(FetchContent) @@ -74,7 +74,7 @@ option(BITLIB_COVERAGE "Compute test coverage" OFF) ``` # Usage -The goal of BitLib is to be as similar to the C++ STL as possible. The interface of most functions and classes are the same as they are in the STL. Instead of the values being `bool`, we have `bit::bit_value`, which can take on either `bit::bit0` or `bit::bit1`. +The goal of BitLib is to be as similar to the C++ STL as possible. The interface of most functions and classes are the same as they are in the STL. Instead of the values being `bool`, we have `bit::bit_value`, which can take on either `bit::bit0` or `bit::bit1`. ## Containers Right now, the only container I have implemented is the bitvector. `bit::bit_vector` is essentially a wrapper around `std::vector`. The interfaces are nearly identical. In addition to the normal `vector` constructors, you can also provide a string to construct your bitvector: @@ -106,7 +106,7 @@ auto bitret = bit::transform( bitvec1.end(), bitvec2.begin(), bitvec3.begin() - binary_op); + binary_op); ``` ## Iterators @@ -118,9 +118,9 @@ bit::bit_iterator(&(wordArr[0])); // Constructs a bit iterator starti bit::bit_iterator(&(wordArr[0]), 1); // Constructs a bit iterator from the second bit (position 1) of the first word of the vector ``` -In order to grab the underlying word that a bit pointed to by a bit_iterator comes from, you can use the `bit_iterator.base()` function. +In order to grab the underlying word that a bit pointed to by a bit_iterator comes from, you can use the `bit_iterator.base()` function. -It is worth noting that the "position" of a bit always increases from LSB to MSB. For those looking to create their own algorithms from bit_iterators, this can be a common "gotcha". For example, shifting a word to the right by `k` will eliminate the first `k` bits of the container. This is only important to those implementing their own algorithms. `bit::shift_*` works as described in the documentation i.e. `shift_right` shifts the container towards `end()` and `shift_left` shifts the container towards `begin()`. +It is worth noting that the "position" of a bit always increases from LSB to MSB. For those looking to create their own algorithms from bit_iterators, this can be a common "gotcha". For example, shifting a word to the right by `k` will eliminate the first `k` bits of the container. This is only important to those implementing their own algorithms. `bit::shift_*` works as described in the documentation i.e. `shift_right` shifts the container towards `end()` and `shift_left` shifts the container towards `begin()`. ``` MSB|<-----|LSB @@ -135,13 +135,13 @@ Value: 01000100 --> Sequence: 00100010 ``` # Documentation -Given that the majority of the library is focused on having the same interface as the C++ STL iterators, containers, and algorithms, users should use the official [STL documentation website](https://en.cppreference.com/). We do plan on adding our own documentation in the future, however. +Given that the majority of the library is focused on having the same interface as the C++ STL iterators, containers, and algorithms, users should use the official [STL documentation website](https://en.cppreference.com/). We do plan on adding our own documentation in the future, however. # Performance Benchmarks -I used Google's [benchmark](https://github.com/google/benchmark) library for computing benchmarks. Each benchmark is formatted as `{bit, BitArray, std}::function` (size) [(alignment-tags)]. +I used Google's [benchmark](https://github.com/google/benchmark) library for computing benchmarks. Each benchmark is formatted as `{bit, BitArray, std}::function` (size) [(alignment-tags)]. - * `bit` is for this library, `BitArray` is for the popular C-based [BitArray library](https://github.com/noporpoise/BitArray), [dynamic_bitset](https://github.com/pinam45/dynamic_bitset) is a header-only library similar to Boost's dynamic_bitset, and`std` is the standard library operating on the infamous `vector`. + * `bit` is for this library, `BitArray` is for the popular C-based [BitArray library](https://github.com/noporpoise/BitArray), [dynamic_bitset](https://github.com/pinam45/dynamic_bitset) is a header-only library similar to Boost's dynamic_bitset, and`std` is the standard library operating on the infamous `vector`. * (size) denotes the size of the container in bits. `small = 1 << 8`, `medium= 1 << 16`, `large = 1 << 24`, `huge = 1 << 31` * (alignment-tags) refers to the memory alignment of the bit-iterators. `U` means the iterator does not fall on a word boundary, `R` means the iterator is placed at random, and `A` means the iterator is aligned with a word boundary. @@ -254,4 +254,4 @@ dynamic_bitset::find (large) 259896 ns 259908 ns bitarray::find (large) 252434 ns 252445 ns 2774 std::find (large) 28570723 ns 28567762 ns 25 ``` - + From ee3313bd8982e5e9839ef84a36a40d42142739a7 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Fri, 2 May 2025 12:19:58 -0400 Subject: [PATCH 54/85] Do not resolve mdspan dependency if it was previously add_subdirectory --- CMakeLists.txt | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 23d5d50c..ba9cb8a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,16 +64,18 @@ target_sources(bitlib_utils INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/utils/test_uti if (BITLIB_MDSPAN) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - find_package(mdspan CONFIG) - if (NOT mdspan_DIR OR "${mdspan_DIR}" STREQUAL "mdspan-NOTFOUND") - include(FetchContent) - - FetchContent_Declare( - mdspan - GIT_REPOSITORY https://github.com/kokkos/mdspan.git - GIT_TAG 414a5dccf5c775b2eecc13d408b256e94f23d1d2 # stable 2025/04/25 - ) - FetchContent_MakeAvailable(mdspan) + if (NOT TARGET std::mdspan) + find_package(mdspan CONFIG) + if (NOT mdspan_DIR OR "${mdspan_DIR}" STREQUAL "mdspan-NOTFOUND") + include(FetchContent) + + FetchContent_Declare( + mdspan + GIT_REPOSITORY https://github.com/kokkos/mdspan.git + GIT_TAG 414a5dccf5c775b2eecc13d408b256e94f23d1d2 # stable 2025/04/25 + ) + FetchContent_MakeAvailable(mdspan) + endif() endif() target_compile_definitions(bitlib INTERFACE MDSPAN_IMPL_STANDARD_NAMESPACE=std MDSPAN_IMPL_PROPOSED_NAMESPACE=experimental) target_link_libraries(bitlib INTERFACE std::mdspan) From fdea67cc9044e6ab4c0e2e1e4bd2e92eb09b8d19 Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Sat, 3 May 2025 21:33:25 +0000 Subject: [PATCH 55/85] Presets and fix sanitize (#1) * Roll coverage and profile into presets * Remove some undefined and (sometimes) unaligned behavior * Fix memory leak and use operator new instead of aligned_alloc * Move runs-on section * runs-on should be distro now * Fix build_type * Fix distro seleciton * Don't set OFF * Capitalize CMake Config type * Save coverage.info as artifact --- .github/workflows/cmake-multi-platform.yml | 45 ++-- .github/workflows/coverage.yml | 31 ++- CMakeLists.txt | 9 +- CMakePresets.json | 202 ++++++++++++++++++ benchmark/CMakeLists.txt | 87 ++++++-- benchmark/src/rw_bench.hpp | 13 +- benchmark/src/shift_bench.hpp | 37 ++-- example/CMakeLists.txt | 20 +- .../src/main.cpp => example/src/example2.cpp | 0 include/bitlib/bit-algorithms/libpopcnt.h | 29 ++- include/bitlib/bit-containers/bit_array.hpp | 5 +- .../bit_array_dynamic_extent.hpp | 64 +++--- include/bitlib/bit-containers/bit_vector.hpp | 6 +- include/bitlib/bit-iterator/bit_details.hpp | 6 +- profile/CMakeLists.txt | 14 -- test/CMakeLists.txt | 10 - 16 files changed, 428 insertions(+), 150 deletions(-) create mode 100644 CMakePresets.json rename profile/src/main.cpp => example/src/example2.cpp (100%) delete mode 100644 profile/CMakeLists.txt diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 1af4a4a6..61c1607d 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -14,7 +14,7 @@ on: jobs: build: - runs-on: ${{ matrix.os }} + runs-on: ${{ matrix.distro }} strategy: # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. @@ -27,22 +27,44 @@ jobs: # # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. matrix: - os: [ubuntu-latest] # removed windows-latest - build_type: [Release] - c_compiler: [gcc, clang-19] # removed cl + distro: [ubuntu-latest] # removed windows-latest + build_type: [Debug, Release] + compiler: [gcc, clang] # removed cl include: - - os: ubuntu-latest + - distro: ubuntu-latest + os: linux + compiler: gcc c_compiler: gcc cpp_compiler: g++ - - os: ubuntu-latest + stdlib: libstdcxx + build_type: Release + - distro: ubuntu-latest + os: linux + compiler: clang + cpp_compiler: clang++-19 c_compiler: clang-19 + stdlib: libcxx + build_type: Release + - distro: ubuntu-latest + os: linux + compiler: gcc + c_compiler: gcc + cpp_compiler: g++ + stdlib: libstdcxx + build_type: Debug + - distro: ubuntu-latest + os: linux + compiler: clang cpp_compiler: clang++-19 + c_compiler: clang-19 + stdlib: libcxx + build_type: Debug steps: - uses: actions/checkout@v4 - name: "Install Clang 19" - if: matrix.os == 'ubuntu-latest' && matrix.c_compiler == 'clang-19' + if: matrix.distro == 'ubuntu-latest' && matrix.c_compiler == 'clang-19' run: | sudo apt-get update sudo apt-get install -y wget gnupg lsb-release @@ -63,20 +85,17 @@ jobs: # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type run: > - cmake -B ${{ steps.strings.outputs.build-output-dir }} + cmake -B ${{ steps.strings.outputs.build-output-dir }} -S ${{ github.workspace }} + --preset=tests_${{ matrix.os }}_${{ matrix.compiler }}_${{ matrix.stdlib }} -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - -DBITLIB_TEST=ON - ${{ matrix.c_compiler == 'clang-19' && '-DCMAKE_CXX_FLAGS=-stdlib=libc++ -DCMAKE_EXE_LINKER_FLAGS=-stdlib=libc++' || '' }} - -S ${{ github.workspace }} - name: Build # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} --parallel - name: Test - working-directory: ${{ steps.strings.outputs.build-output-dir }} # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --build-config ${{ matrix.build_type }} --output-on-failure --parallel + run: ctest --test-dir ${{ steps.strings.outputs.build-output-dir }} --build-config ${{ matrix.build_type }} --output-on-failure --parallel diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index db7e8265..da89c88d 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -12,7 +12,7 @@ on: jobs: coverage: - runs-on: ${{ matrix.os }} + runs-on: ${{ matrix.distro }} strategy: # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. @@ -25,13 +25,17 @@ jobs: # # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. matrix: - os: [ubuntu-latest] # removed windows-latest + distro: [ubuntu-latest] build_type: [Debug] - c_compiler: [gcc] + compiler: [gcc] include: - - os: ubuntu-latest + - distro: ubuntu-latest + os: linux + compiler: gcc c_compiler: gcc cpp_compiler: g++ + stdlib: libstdcxx + build_type: Debug steps: - uses: actions/checkout@v4 @@ -42,18 +46,6 @@ jobs: shell: bash run: | echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" - if [ "${{ matrix.c_compiler }}" = "clang-19" ]; then - cmake_flags="-DCMAKE_CXX_FLAGS='--coverage -stdlib=libc++'" - cmake_flags="${cmake_flags} -DCMAKE_C_FLAGS='--coverage'" - cmake_flags="${cmake_flags} -DCMAKE_EXE_LINKER_FLAGS='-stdlib=libc++'" - echo "cmake_flags=${cmake_flags}" - echo "cmake_flags=${cmake_flags}" >> $GITHUB_OUTPUT - else - cmake_flags="-DCMAKE_CXX_FLAGS='--coverage'" - cmake_flags="${cmake_flags} -DCMAKE_C_FLAGS='--coverage'" - echo "cmake_flags=${cmake_flags}" - echo "cmake_flags=${cmake_flags}" >> $GITHUB_OUTPUT - fi - name: "Install lcov" run: | @@ -68,8 +60,7 @@ jobs: -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - -DBITLIB_TEST=ON - ${{ steps.strings.outputs.cmake_flags }} + --preset=coverage_${{ matrix.os }}_${{ matrix.compiler }}_${{ matrix.stdlib }} -S ${{ github.workspace }} - name: Build @@ -93,7 +84,9 @@ jobs: uses: actions/upload-artifact@v4 with: name: coverage-report - path: out/coverage + path: | + out/coverage + coverage.info - name: Coveralls uses: coverallsapp/github-action@master diff --git a/CMakeLists.txt b/CMakeLists.txt index ba9cb8a9..55524491 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,7 @@ # specify the C++ standard cmake_minimum_required(VERSION 3.14) +cmake_policy(SET CMP0168 NEW) +cmake_policy(SET CMP0167 NEW) # set the project name project(BitLib VERSION 0.3.0) @@ -8,8 +10,6 @@ option(BITLIB_HWY "Build with google highway SIMD extensions" OFF) option(BITLIB_BENCHMARK "Build bitlib benchmarks" OFF) option(BITLIB_EXAMPLE "Build bitlib examples" OFF) option(BITLIB_TEST "Build bitlib tests" OFF) -option(BITLIB_PROFILE "Buid simple example for profiling" OFF) -option(BITLIB_COVERAGE "Compute test coverage" OFF) option(BITLIB_MDSPAN "Accessor Support for mdspan" ON) add_library(bitlib INTERFACE) @@ -99,8 +99,3 @@ if (BITLIB_TEST) enable_testing() add_subdirectory(test) endif() - -if(BITLIB_PROFILE) - add_subdirectory(profile) -endif() - diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 00000000..68baddd6 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,202 @@ +{ + "version": 6, + "configurePresets": [ + { + "name": "base", + "hidden": true, + "binaryDir": "${sourceDir}/build/${presetName}", + "cacheVariables": { + "CMAKE_EXPORT_COMPILE_COMMANDS": "YES" + } + }, + { + "name": "windows_base", + "hidden": true, + "inherits": "base", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "linux_base", + "hidden": true, + "inherits": "base", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "msvc_base", + "hidden": true, + "cacheVariables": { + "CMAKE_C_COMPILER": "cl", + "CMAKE_CXX_COMPILER": "cl" + } + }, + { + "name": "gnu_base", + "hidden": true, + "cacheVariables": { + "CMAKE_C_FLAGS_DEBUG": "-DPOPCNT_NO_UNALIGNED -g3 -fno-omit-frame-pointer -fstack-protector-all -fsanitize=address -fsanitize=undefined -finstrument-functions -fno-inline", + "CMAKE_CXX_FLAGS_DEBUG": "-DPOPCNT_NO_UNALIGNED -g3 -fno-omit-frame-pointer -DPOPCNT_NO_UNALIGNED -fstack-protector-all -fsanitize=address -fsanitize=undefined -finstrument-functions -fno-inline", + "CMAKE_C_FLAGS_RELEASE": "-O3 -DNDEBUG -march=native", + "CMAKE_CXX_FLAGS_RELEASE": "-O3 -DNDEBUG -march=native" + } + }, + { + "name": "clang_libcxx_base", + "hidden": true, + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++", + "CMAKE_CXX_FLAGS": "-stdlib=libc++", + "CMAKE_EXE_LINKER_FLAGS": "-stdlib=libc++" + } + }, + { + "name": "clang_libstdcxx_base", + "hidden": true, + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++", + "CMAKE_CXX_FLAGS": "-stdlib=libstdc++", + "CMAKE_EXE_LINKER_FLAGS": "-stdlib=libstdc++" + } + }, + { + "name": "gcc_base", + "hidden": true, + "cacheVariables": { + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++" + } + }, + { + "name": "tests_msvc_base", + "hidden": true, + "inherits": "msvc_base", + "cacheVariables": { + "BITLIB_TEST": "ON" + } + }, + { + "name": "benchmark_msvc_base", + "hidden": true, + "inherits": "msvc_base", + "cacheVariables": { + "BITLIB_BENCHMARK": "ON" + } + }, + { + "name": "tests_gnu_base", + "hidden": true, + "inherits": "gnu_base", + "cacheVariables": { + "BITLIB_TEST": "ON", + "CMAKE_C_FLAGS": "-DPOPCNT_NO_UNALIGNED -fstack-protector-strong -fsanitize=undefined -fsanitize=address -fsanitize=bounds -D_FORTIFY_SOURCE=2", + "CMAKE_CXX_FLAGS": "-DPOPCNT_NO_UNALIGNED -fstack-protector-strong -fsanitize=undefined -fsanitize=address -fsanitize=bounds -D_FORTIFY_SOURCE=2" + } + }, + { + "name": "coverage_gnu_base", + "hidden": true, + "inherits": "gnu_base", + "cacheVariables": { + "CMAKE_C_FLAGS": "--coverage", + "CMAKE_CXX_FLAGS": "--coverage", + "CMAKE_C_FLAGS_RELEASE": "-O2 -g", + "CMAKE_CXX_FLAGS_RELEASE": "-O2 -g", + "BITLIB_TEST": "ON" + } + }, + { + "name": "benchmark_gnu_base", + "hidden": true, + "inherits": "gnu_base", + "cacheVariables": { + "CMAKE_C_FLAGS": "-O3 -DNDEBUG -march=native", + "CMAKE_CXX_FLAGS": "-O3 -DNDEBUG -march=native", + "BITLIB_BENCHMARK": "ON" + } + }, + { + "name": "profile_gnu_base", + "hidden": true, + "inherits": "gnu_base", + "cacheVariables": { + "CMAKE_C_FLAGS": "-pg -fno-omit-frame-pointer", + "CMAKE_CXX_FLAGS": "-pg -fno-omit-frame-pointer", + "CMAKE_C_FLAGS_RELEASE": "-O2 -DNDEBUG -march=native -pg -ggdb", + "CMAKE_CXX_FLAGS_RELEASE": "-O2 -DNDEBUG -march=native -pg -ggdb", + "BITLIB_TEST": "ON", + "BITLIB_BENCHMARK": "ON" + } + }, + { + "name": "tests_windows_msvc_msvcstl", + "inherits": [ + "windows_base", + "tests_msvc_base" + ] + }, + { + "name": "benchmark_windows_msvc_msvcstl", + "inherits": [ + "windows_base", + "benchmark_msvc_base" + ] + }, + { + "name": "tests_linux_gcc_libstdcxx", + "inherits": [ + "linux_base", + "gcc_base", + "tests_gnu_base" + ] + }, + { + "name": "coverage_linux_gcc_libstdcxx", + "inherits": [ + "linux_base", + "gcc_base", + "coverage_gnu_base" + ] + }, + { + "name": "benchmark_linux_gcc_libstdcxx", + "inherits": [ + "linux_base", + "gcc_base", + "benchmark_gnu_base" + ] + }, + { + "name": "tests_linux_clang_libcxx", + "inherits": [ + "linux_base", + "clang_libcxx_base", + "tests_gnu_base" + ] + }, + { + "name": "coverage_linux_clang_libcxx", + "inherits": [ + "linux_base", + "clang_libcxx_base", + "coverage_gnu_base" + ] + }, + { + "name": "benchmark_linux_clang_libcxx", + "inherits": [ + "linux_base", + "clang_libcxx_base", + "benchmark_gnu_base" + ] + } + ] +} diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 6a1bbd3d..a6f43cbd 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -1,9 +1,9 @@ set(BENCHMARK_ENABLE_GTEST_TESTS OFF) +include(FetchContent) + find_package(benchmark CONFIG) if (NOT benchmark_DIR OR "${benchmark_DIR}" STREQUAL "benchmark-NOTFOUND") - include(FetchContent) - FetchContent_Declare( benchmark GIT_REPOSITORY https://github.com/google/benchmark.git @@ -12,27 +12,74 @@ if (NOT benchmark_DIR OR "${benchmark_DIR}" STREQUAL "benchmark-NOTFOUND") FetchContent_MakeAvailable(benchmark) endif() +#find_package(Boost) +#if (NOT Boost_DIR OR "${Boost_DIR}" STREQUAL "Boost-NOTFOUND") +# FetchContent_Declare( +# Boost +# GIT_REPOSITORY https://github.com/boostorg/boost.git +# GIT_TAG 199ef13d6034c85232431130142159af3adfce22 #v1.88.0 +# EXCLUDE_FROM_ALL +# FIND_PACKAGE_ARGS COMPONENTS dynamic_bitset +# ) +# FetchContent_MakeAvailable(Boost) +#endif() +find_package(sul-dynamic_bitset) +if (NOT sul-dynamic_bitset_DIR OR "${sul-dynamic_bitset_DIR}" STREQUAL "sul-dynamic_bitset-NOTFOUND") + FetchContent_Declare( + sul-dynamic_bitset + GIT_REPOSITORY https://github.com/pinam45/dynamic_bitset.git + GIT_TAG ef647f07a663a4cbbd3bf8c70848876e63b64ce1 #v1.3.2 + EXCLUDE_FROM_ALL + FIND_PACKAGE_ARGS + ) + FetchContent_MakeAvailable(sul-dynamic_bitset) +endif() -# set output directory of builds -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - -# set build type -set(CMAKE_BUILD_TYPE Release) +FetchContent_Declare( + BitArray + GIT_REPOSITORY https://github.com/noporpoise/BitArray.git + GIT_TAG 560ed785a0687b35301a8c23b8bb9fc06b69d827 # v2.0 + EXCLUDE_FROM_ALL +) +FetchContent_MakeAvailable(BitArray) +add_library(BitArray SHARED EXCLUDE_FROM_ALL) +target_sources(BitArray PRIVATE + ${CMAKE_BINARY_DIR}/_deps/bitarray-src/bit_array.c + PUBLIC + ${CMAKE_BINARY_DIR}/_deps/bitarray-src/bit_array.h + ${CMAKE_BINARY_DIR}/_deps/bitarray-src/bit_macros.h +) +target_include_directories(BitArray PUBLIC + ${CMAKE_BINARY_DIR}/_deps/bitarray-src +) # Add targets -file(GLOB BENCH_SOURCES "src/*.cc") -add_executable(bitlib-bench ${BENCH_SOURCES}) - -add_subdirectory(ext/dynamic_bitset) +add_executable(bitlib-bench EXCLUDE_FROM_ALL) -# specify benchmark-specific libraries -include_directories( - ${googlebench_SOURCE_DIR}/benchmark/include - src/utils - ext/BitArray - ext/itsy_bitsy/include) -target_link_libraries(bitlib-bench PRIVATE bitlib_utils benchmark::benchmark -pthread ${CMAKE_CURRENT_LIST_DIR}/ext/BitArray/libbitarr.a sul::dynamic_bitset) +target_sources(bitlib-bench PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src/benchmark_main.cc + ${CMAKE_CURRENT_SOURCE_DIR}/src/copy_backward_bench.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/copy_bench.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/count_bench.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/equal_bench.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/fill_bench.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/find_bench.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/move_bench.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/reverse_bench.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/rotate_bench.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/rw_bench.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/shift_bench.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/swap_ranges-bench.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/transform_bench.hpp +) +target_include_directories(bitlib-bench PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src +) -target_compile_options(bitlib-bench PUBLIC -O3 -DNDEBUG -march=native -Wpedantic) -install(TARGETS bitlib-bench DESTINATION .) +target_link_libraries(bitlib-bench PRIVATE + bitlib + bitlib_utils + benchmark::benchmark + sul::dynamic_bitset + BitArray) diff --git a/benchmark/src/rw_bench.hpp b/benchmark/src/rw_bench.hpp index 2aac15d1..4b61aae5 100644 --- a/benchmark/src/rw_bench.hpp +++ b/benchmark/src/rw_bench.hpp @@ -13,8 +13,9 @@ auto BM_BitSet = [](benchmark::State& state, auto input) { auto first1 = bit::bit_iterator(std::begin(bitvec1)); for (auto _ : state) { - benchmark::DoNotOptimize(first1[total_bits/2] = bit::bit1); - benchmark::ClobberMemory(); + first1[total_bits / 2] = bit::bit1; + benchmark::DoNotOptimize(first1); + benchmark::ClobberMemory(); } }; @@ -38,7 +39,7 @@ auto BM_DynamicBitsetSet = [](benchmark::State& state, auto input) { using WordType = typename std::tuple_element<1, decltype(input)>::type; unsigned int total_bits = std::get<2>(input); sul::dynamic_bitset x(total_bits); - container_type boolvec1 = make_random_container (total_bits); + container_type boolvec1 = make_random_container(total_bits); for (auto i = 0; i < total_bits; ++i) { x[i] = boolvec1[i]; } @@ -53,7 +54,7 @@ auto BM_BoolSet = [](benchmark::State& state, auto input) { using container_type = typename std::tuple_element<0, decltype(input)>::type; unsigned int total_bits = std::get<2>(input); auto container_size = total_bits; - container_type boolvec1 = make_random_container (container_size); + container_type boolvec1 = make_random_container(container_size); for (auto _ : state) { @@ -98,7 +99,7 @@ auto BM_DynamicBitsetGet = [](benchmark::State& state, auto input) { using WordType = typename std::tuple_element<1, decltype(input)>::type; unsigned int total_bits = std::get<2>(input); sul::dynamic_bitset x(total_bits); - container_type boolvec1 = make_random_container (total_bits); + container_type boolvec1 = make_random_container(total_bits); for (auto i = 0; i < total_bits; ++i) { x[i] = boolvec1[i]; } @@ -113,7 +114,7 @@ auto BM_BoolGet = [](benchmark::State& state, auto input) { using container_type = typename std::tuple_element<0, decltype(input)>::type; unsigned int total_bits = std::get<2>(input); auto container_size = total_bits; - container_type boolvec1 = make_random_container (container_size); + container_type boolvec1 = make_random_container(container_size); bool x; for (auto _ : state) { diff --git a/benchmark/src/shift_bench.hpp b/benchmark/src/shift_bench.hpp index 8f38383e..3410b983 100644 --- a/benchmark/src/shift_bench.hpp +++ b/benchmark/src/shift_bench.hpp @@ -13,13 +13,14 @@ auto BM_BitShiftLeft = [](benchmark::State& state, auto input) { unsigned int total_bits = std::get<2>(input); auto digits = bit::binary_digits::value; auto container_size = ceil(float(total_bits) / digits); - container_type bitcont = make_random_container(container_size); + container_type bitcont = make_random_container(container_size); auto first = bit::bit_iterator(std::begin(bitcont)); auto last = bit::bit_iterator(std::end(bitcont)); auto n = total_bits / 2 - 1; for (auto _ : state) { - benchmark::DoNotOptimize(bit::shift_left(first, last, n)); - benchmark::ClobberMemory(); + auto result = bit::shift_left(first, last, n); + benchmark::DoNotOptimize(result); + benchmark::ClobberMemory(); } }; @@ -31,13 +32,14 @@ auto BM_BitShiftLeft_UU = [](benchmark::State& state, auto input) { unsigned int total_bits = std::get<2>(input); auto digits = bit::binary_digits::value; auto container_size = ceil(float(total_bits) / digits); - container_type bitcont = make_random_container(container_size); + container_type bitcont = make_random_container(container_size); bit::bit_iterator first = bit::bit_iterator(bitcont.begin()) + 1; bit::bit_iterator last = bit::bit_iterator(bitcont.end()) - 1; auto n = total_bits / 2 + 3; for (auto _ : state) { - benchmark::DoNotOptimize(bit::shift_left(first, last, n)); - benchmark::ClobberMemory(); + auto result = bit::shift_left(first, last, n); + benchmark::DoNotOptimize(result); + benchmark::ClobberMemory(); } }; @@ -72,13 +74,14 @@ auto BM_BoolShiftLeft = [](benchmark::State& state, auto input) { using container_type = std::vector; using num_type = typename container_type::value_type; unsigned int container_size = std::get<2>(input); - container_type cont = make_random_container(container_size); + container_type cont = make_random_container(container_size); auto first = cont.begin(); auto last = cont.end(); auto n = std::distance(first, last) / 2 - 1; for (auto _ : state) { - benchmark::DoNotOptimize(bit::word_shift_left(first, last, n)); - benchmark::ClobberMemory(); + auto result = bit::word_shift_left(first, last, n); + benchmark::DoNotOptimize(result); + benchmark::ClobberMemory(); } }; @@ -88,13 +91,14 @@ auto BM_BitShiftRight = [](benchmark::State& state, auto input) { unsigned int total_bits = std::get<2>(input); auto digits = bit::binary_digits::value; auto container_size = ceil(float(total_bits) / digits); - container_type bitcont = make_random_container(container_size); + container_type bitcont = make_random_container(container_size); auto first = bit::bit_iterator(std::begin(bitcont)); auto last = bit::bit_iterator(std::end(bitcont)); auto n = total_bits / 2 - 1; for (auto _ : state) { - benchmark::DoNotOptimize(bit::shift_right(first, last, n)); - benchmark::ClobberMemory(); + auto result = bit::shift_right(first, last, n); + benchmark::DoNotOptimize(result); + benchmark::ClobberMemory(); } }; @@ -104,13 +108,14 @@ auto BM_BitShiftRight_UU = [](benchmark::State& state, auto input) { unsigned int total_bits = std::get<2>(input); auto digits = bit::binary_digits::value; auto container_size = ceil(float(total_bits) / digits); - container_type bitcont = make_random_container(container_size); + container_type bitcont = make_random_container(container_size); auto first = bit::bit_iterator(std::begin(bitcont)) + 1; auto last = bit::bit_iterator(std::end(bitcont)) - 1; auto n = total_bits / 2 + 3; for (auto _ : state) { - benchmark::DoNotOptimize(bit::shift_right(first, last, n)); - benchmark::ClobberMemory(); + auto result = bit::shift_right(first, last, n); + benchmark::DoNotOptimize(result); + benchmark::ClobberMemory(); } }; @@ -144,7 +149,7 @@ auto BM_BoolShiftRight = [](benchmark::State& state, auto input) { using container_type = std::vector; using num_type = typename container_type::value_type; unsigned int container_size = std::get<2>(input); - container_type cont = make_random_container(container_size); + container_type cont = make_random_container(container_size); auto first = cont.begin(); auto last = cont.end(); auto n = std::distance(first, last) / 2 - 1; diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 10292aca..c7b775a4 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,8 +1,12 @@ -file( GLOB EXAMPLE_SOURCES src/*.cpp ) -foreach( examplesourcefile ${EXAMPLE_SOURCES} ) - # Cut off the file extension and directory path - get_filename_component( examplename ${examplesourcefile} NAME_WE ) - add_executable( ${examplename} ${examplesourcefile} ) - # Make sure YourLib is linked to each app - #target_link_libraries( ${examplename} YourLib ) -endforeach( examplesourcefile ${EXAMPLE_SOURCES} ) +add_executable(BitLib_Example1) + +target_sources(BitLib_Example1 PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/src/example1.cpp" +) +target_link_libraries(BitLib_Example1 PRIVATE bitlib::bitlib) + +add_executable(BitLib_Example2) +target_sources(BitLib_Example2 PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/src/example2.cpp" +) +target_link_libraries(BitLib_Example2 PRIVATE bitlib::bitlib bitlib_utils) diff --git a/profile/src/main.cpp b/example/src/example2.cpp similarity index 100% rename from profile/src/main.cpp rename to example/src/example2.cpp diff --git a/include/bitlib/bit-algorithms/libpopcnt.h b/include/bitlib/bit-algorithms/libpopcnt.h index ffcd976b..ff2d2e79 100644 --- a/include/bitlib/bit-algorithms/libpopcnt.h +++ b/include/bitlib/bit-algorithms/libpopcnt.h @@ -633,9 +633,24 @@ static inline uint64_t popcnt(const void* data, uint64_t size) cnt += popcnt64(*(const uint64_t*)(ptr + i)); for (; i < size; i++) cnt += popcnt64(ptr[i]); +#ifdef POPCNT_NO_UNALIGNED + // Address sanitizer will complain about unaligned accesses + // When we test with ASAN we enable aligned-only accesses + for (; ((uintptr_t)(ptr + i) % 8) && (i < size); i++) { + cnt += popcnt64(ptr[i]); + } +#endif - return cnt; + /* We use unaligned memory accesses here to improve performance */ + for (; (i + 8) <= size; i += 8) { + cnt += popcnt64(*(const uint64_t*)(ptr + i)); } + for (; i < size; i++) { + cnt += popcnt64(ptr[i]); + } + + return cnt; + } #endif #if !defined(HAVE_POPCNT) || \ @@ -644,8 +659,18 @@ static inline uint64_t popcnt(const void* data, uint64_t size) * Pure integer popcount algorithm. * We use unaligned memory accesses here to improve performance. */ - for (; i < size - size % 8; i += 8) + +#ifdef POPCNT_NO_UNALIGNED + // Address sanitizer will complain about unaligned accesses + // When we test with ASAN we enable aligned-only accesses + for (; ((uintptr_t)(ptr + i) % 8) && (i < size); i++) { + cnt += popcount64(ptr[i]); + } +#endif + + for (; (i + 8) <= size; i += 8) { cnt += popcount64(*(const uint64_t*)(ptr + i)); + } if (i < size) { diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index cd53277f..766a2aab 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -50,8 +50,9 @@ class bit_array { private: static constexpr std::size_t Words = (N * bitsof() + bitsof() - 1) / bitsof(); + static constexpr std::size_t AlignedWords = (((Words * sizeof(word_type) + static_cast(V) - 1) & ~(static_cast(V) - 1)) + sizeof(word_type) - 1) / sizeof(word_type); - alignas(static_cast(V)) std::array storage; + alignas(static_cast(V)) std::array storage; public: using iterator = typename std::conditional, @@ -210,7 +211,7 @@ constexpr bit_array::bit_array(const std::initializer_list ini template constexpr bit_array::bit_array(const std::initializer_list init) : storage(init) { - static_assert(init.size() == storage.size()); + static_assert(init.size() == Words); } template diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index fbf98fd6..88d4fab8 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -11,11 +11,12 @@ #include #include +#include // memcpy #include #include +#include #include // std::dynamic_extent #include -#include // memcpy #include "bitlib/bit-algorithms/bit_algorithm.hpp" #include "bitlib/bit-containers/bit_array.hpp" @@ -44,9 +45,24 @@ class bit_array { const word_type*>::type; private: + struct deleter { + size_type words; + void operator()(word_type* const p) const { + for (size_type i = 0; i < words; ++i) { + (p + i)->~word_type(); + } + ::operator delete(p, V); + } + }; + const size_type m_size; - const std::unique_ptr storage; - static constexpr std::size_t Words(std::size_t N) { return (N * bitsof() + bitsof() - 1) / bitsof(); }; + const std::unique_ptr storage; + static constexpr size_type Words(size_type N) { + return (N * bitsof() + bitsof() - 1) / bitsof(); + }; + static constexpr size_t AlignedBytes(size_t N) { + return (Words(N) * sizeof(word_type) + static_cast(V) - 1) & ~(static_cast(V) - 1); + }; public: /* @@ -142,11 +158,10 @@ constexpr bit_array::bit_array() noexcept template template constexpr bit_array::bit_array(const size_type size, const U& integral) -: m_size(size), - storage(std::unique_ptr( - static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { + : m_size(size), + storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { assert (bitsof() <= size); - for (std::size_t i = 0; i < Words(m_size); ++i) { + for (size_type i = 0; i < Words(m_size); ++i) { new (storage.get() + i) word_type(); } std::memcpy(&storage[0], &integral, sizeof(integral)); @@ -168,10 +183,9 @@ constexpr bit_array::bit_array(const size_type siz template constexpr bit_array::bit_array(const size_type size) : m_size(size), - storage(std::unique_ptr( - static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { + storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { //std::uninitialized_fill_n(this->begin(), Words(m_size), word_type()); - for (std::size_t i = 0; i < Words(m_size); ++i) { + for (size_type i = 0; i < Words(m_size); ++i) { new (storage.get() + i) word_type(); } } @@ -179,14 +193,13 @@ constexpr bit_array::bit_array(const size_type siz template constexpr bit_array::bit_array(const size_type size, const value_type bit_val) : m_size(size), - storage(std::unique_ptr( - static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { + storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { if constexpr (std::is_same::value) { - for (std::size_t i = 0; i < Words(m_size); ++i) { + for (size_type i = 0; i < Words(m_size); ++i) { new (storage.get() + i) word_type(bit_val); } } else { - for (std::size_t i = 0; i < Words(m_size); ++i) { + for (size_type i = 0; i < Words(m_size); ++i) { new (storage.get() + i) word_type(); } fill(bit_val); @@ -196,9 +209,8 @@ constexpr bit_array::bit_array(const size_type siz template constexpr bit_array::bit_array(const bit_array& other) : m_size(other.size()), - storage(std::unique_ptr( - static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { - for (std::size_t i = 0; i < Words(m_size); ++i) { + storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { + for (size_type i = 0; i < Words(m_size); ++i) { new (storage.get() + i) word_type(other.storage[i]); } } @@ -206,9 +218,8 @@ constexpr bit_array::bit_array(const bit_array constexpr bit_array::bit_array(const bit_array&& other) : m_size(other.size()), - storage(std::unique_ptr( - static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { - for (std::size_t i = 0; i < Words(m_size); ++i) { + storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { + for (size_type i = 0; i < Words(m_size); ++i) { new (storage.get() + i) word_type(other.storage[i]); } } @@ -217,8 +228,7 @@ template constexpr bit_array::bit_array(const std::initializer_list init) requires(!std::is_same_v) : m_size(init.size()), - storage(std::unique_ptr( - static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { + storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { std::copy(init.begin(), init.end(), this->begin()); } @@ -235,9 +245,8 @@ constexpr bit_array::bit_array(const std::initializer_lis template constexpr bit_array::bit_array(const std::initializer_list init) : m_size(bitsof() * init.size()), - storage(std::unique_ptr( - static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { - std::size_t i = 0; + storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { + size_type i = 0; auto&& it = init.begin(); for (; i < Words(m_size); ++i, ++it) { new (storage.get() + i) word_type(*it); @@ -248,8 +257,7 @@ template constexpr bit_array::bit_array(const std::string_view s) requires(std::is_same_v) : m_size((std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))), - storage(std::unique_ptr( - static_cast(std::aligned_alloc(static_cast(V), sizeof(word_type) * Words(m_size))), &std::free)) { + storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { size_type i = 0; for (char c : s) { if (c == '0') { @@ -288,7 +296,7 @@ constexpr void bit_array::swap(bit_arraym_size == other.m_size); W* it1 = this->storage.get(); W* it2 = other.storage.get(); - for (size_t i = 0; i < Words(this->m_size); i++, it1++, it2++) { + for (size_type i = 0; i < Words(this->m_size); i++, it1++, it2++) { std::swap(*it1, *it2); } } diff --git a/include/bitlib/bit-containers/bit_vector.hpp b/include/bitlib/bit-containers/bit_vector.hpp index 663d341f..53273db4 100644 --- a/include/bitlib/bit-containers/bit_vector.hpp +++ b/include/bitlib/bit-containers/bit_vector.hpp @@ -80,7 +80,7 @@ class bit_vector { using const_reference = const reference; using pointer = bit_pointer; using iterator = bit_iterator::iterator>; - using const_iterator = bit_iterator::const_iterator>; + using const_iterator = bit_iterator::const_iterator>; /* * Constructors, copies and moves... @@ -666,16 +666,14 @@ constexpr bit_span bit_vector constexpr std::string bit_vector::debug_string(const_iterator first, const_iterator end) { std::string ret = ""; - iterator mem = first; auto position = 0; - for (iterator it = first; it != end; ++it) { + for (const_iterator it = first; it != end; ++it) { if (position % digits == 0 && position != 0) { ret += " "; } else if (position % 8 == 0 && position != 0) { ret += '.'; } ret += *it == bit1 ? '1' : '0'; - mem = it; ++position; } return ret; diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index 805f1279..cb390bba 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -779,7 +779,11 @@ constexpr T _bitblend(T src0, T src1, T start, T len) noexcept static_assert(binary_digits::value, ""); constexpr T digits = binary_digits::value; constexpr T one = 1; - const T msk = ((one << len) * (len < digits) - one) << start; + // The digits_mask is solely here to prevent Undefined Sanitizer + // complaining about shift of len >= digits + // Note: on -O1 the (len & digits_mask) is optimized to simply (len) + constexpr T digits_mask = digits - one; + const T msk = ((one << (len & digits_mask)) * (len < digits) - one) << start; return src0 ^ ((src0 ^ src1) & msk * (start < digits)); } // -------------------------------------------------------------------------- // diff --git a/profile/CMakeLists.txt b/profile/CMakeLists.txt deleted file mode 100644 index 9fe3a14c..00000000 --- a/profile/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -# set output directory of builds -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - -# set build type -set(CMAKE_BUILD_TYPE RelWithDebInfo) - -# Add targets -file(GLOB PROFILE_SOURCES "src/*.cpp") -add_executable(bitlib-profile ${PROFILE_SOURCES}) - -target_link_libraries(bitlib-profile PRIVATE bitlib_utils) - -target_compile_options(bitlib-profile PUBLIC -O2 -ggdb -Wpedantic) -install(TARGETS bitlib-profile DESTINATION .) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 04cb8eb9..b1493537 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -44,12 +44,6 @@ if (BITLIB_MDSPAN) target_sources(bitlib-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/test-mdspan.cpp) endif() -target_compile_options(bitlib-tests PUBLIC -O0 -g -fno-omit-frame-pointer -Wpedantic -fno-inline) - -if (BITLIB_COVERAGE) - target_compile_options(bitlib-tests PUBLIC --coverage -fprofile-arcs -ftest-coverage -pg) -endif() - # ASAN throws no matter what when using Google HWY, not sure why but need to fix # if (NOT BITLIB_HWY) # target_compile_options(bitlib-tests PUBLIC -fsanitize=address) @@ -68,7 +62,3 @@ enable_testing() gtest_discover_tests( bitlib-tests EXTRA_ARGS --gtest_repeat=${BITLIB_GTEST_REPEAT}) - -#add_test(bitlib-check - #COMMAND env CTEST_OUTPUT_ON_FAILURE=1 GTEST_COLOR=1 ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/bitlib-tests - #DEPENDS bitlib-tests) From d0bfc09a2bccaaf0ac32fa994fa375e3eba15a7b Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sat, 3 May 2025 18:15:26 -0400 Subject: [PATCH 56/85] Add codecov yml to filter libpopcnt.h --- .codecov.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 00000000..118a2444 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,2 @@ +ignore: + - "**/libpopcnt.h" \ No newline at end of file From 96628065de18650a0284facb05699ca87fcc3d1c Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Sat, 3 May 2025 21:56:59 -0400 Subject: [PATCH 57/85] Break up bitlib_utils. Seed tests from the test case name to prevent varying coverage results (#2) * Break up bitlib_utils. Seed tests from the test case name to prevent varying coverage results * Build benchmark during Release configuration * Fix up coverage exclusions * Scope the build to specific target * cmake --build must come first * Backwards logic --- .codecov.yml | 5 +- .github/workflows/cmake-multi-platform.yml | 15 ++-- .github/workflows/coverage.yml | 2 +- CMakeLists.txt | 4 - benchmark/CMakeLists.txt | 18 ++-- benchmark/inc/benchmark_utils.hpp | 49 +++++++++++ benchmark/src/copy_backward_bench.hpp | 7 +- benchmark/src/copy_bench.hpp | 7 +- benchmark/src/count_bench.hpp | 9 +- benchmark/src/equal_bench.hpp | 7 +- benchmark/src/move_bench.hpp | 7 +- benchmark/src/rw_bench.hpp | 5 +- benchmark/src/swap_ranges-bench.hpp | 7 +- benchmark/src/transform_bench.hpp | 20 +++-- example/CMakeLists.txt | 8 +- test/CMakeLists.txt | 4 +- {utils => test/inc}/test_utils.hpp | 96 ++++++++++------------ 17 files changed, 154 insertions(+), 116 deletions(-) create mode 100644 benchmark/inc/benchmark_utils.hpp rename {utils => test/inc}/test_utils.hpp (67%) diff --git a/.codecov.yml b/.codecov.yml index 118a2444..464e2d45 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,2 +1,5 @@ ignore: - - "**/libpopcnt.h" \ No newline at end of file + - "**/libpopcnt.h" + - "test/inc/*" + - "benchmark/**" + - "example/**" \ No newline at end of file diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 61c1607d..ae516cc2 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -27,9 +27,6 @@ jobs: # # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. matrix: - distro: [ubuntu-latest] # removed windows-latest - build_type: [Debug, Release] - compiler: [gcc, clang] # removed cl include: - distro: ubuntu-latest os: linux @@ -37,6 +34,8 @@ jobs: c_compiler: gcc cpp_compiler: g++ stdlib: libstdcxx + preset: benchmark + target: bitlib-bench build_type: Release - distro: ubuntu-latest os: linux @@ -44,6 +43,8 @@ jobs: cpp_compiler: clang++-19 c_compiler: clang-19 stdlib: libcxx + preset: benchmark + target: bitlib-bench build_type: Release - distro: ubuntu-latest os: linux @@ -51,6 +52,8 @@ jobs: c_compiler: gcc cpp_compiler: g++ stdlib: libstdcxx + preset: tests + target: bitlib-tests build_type: Debug - distro: ubuntu-latest os: linux @@ -58,6 +61,8 @@ jobs: cpp_compiler: clang++-19 c_compiler: clang-19 stdlib: libcxx + preset: tests + target: bitlib-tests build_type: Debug steps: @@ -86,14 +91,14 @@ jobs: # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type run: > cmake -B ${{ steps.strings.outputs.build-output-dir }} -S ${{ github.workspace }} - --preset=tests_${{ matrix.os }}_${{ matrix.compiler }}_${{ matrix.stdlib }} + --preset=${{ matrix.preset }}_${{ matrix.os }}_${{ matrix.compiler }}_${{ matrix.stdlib }} -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - name: Build # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). - run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} --parallel + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --target ${{ matrix.target }} --config ${{ matrix.build_type }} --parallel - name: Test # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index da89c88d..bce83fa8 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -75,7 +75,7 @@ jobs: - name: Generate Coverage Report run: | lcov --capture --directory ${{ steps.strings.outputs.build-output-dir }} --output-file coverage.info --ignore-errors mismatch - lcov --remove coverage.info '/usr/*' '*/_deps/*' '*/include/bitlib/bit-algorithms/libpopcnt.h' '*/utils/test_utils.hpp' '*/test/src/*' --output-file coverage.info + lcov --remove coverage.info '/usr/*' '*/_deps/*' '*/include/bitlib/bit-algorithms/libpopcnt.h' '*/test/inc/*' '*/test/src/*' --output-file coverage.info lcov --list coverage.info # Generate an HTML report genhtml coverage.info --output-directory out/coverage diff --git a/CMakeLists.txt b/CMakeLists.txt index 55524491..f7235a55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,10 +58,6 @@ target_sources(bitlib INTERFACE target_compile_features(bitlib INTERFACE cxx_std_23) -add_library(bitlib_utils INTERFACE) -target_include_directories(bitlib_utils INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/utils) -target_sources(bitlib_utils INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/utils/test_utils.hpp) - if (BITLIB_MDSPAN) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if (NOT TARGET std::mdspan) diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index a6f43cbd..a302f7e9 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -2,6 +2,9 @@ set(BENCHMARK_ENABLE_GTEST_TESTS OFF) include(FetchContent) +set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) +set(BENCHMARK_ENABLE_WERROR OFF CACHE BOOL "" FORCE) + find_package(benchmark CONFIG) if (NOT benchmark_DIR OR "${benchmark_DIR}" STREQUAL "benchmark-NOTFOUND") FetchContent_Declare( @@ -12,18 +15,6 @@ if (NOT benchmark_DIR OR "${benchmark_DIR}" STREQUAL "benchmark-NOTFOUND") FetchContent_MakeAvailable(benchmark) endif() -#find_package(Boost) -#if (NOT Boost_DIR OR "${Boost_DIR}" STREQUAL "Boost-NOTFOUND") -# FetchContent_Declare( -# Boost -# GIT_REPOSITORY https://github.com/boostorg/boost.git -# GIT_TAG 199ef13d6034c85232431130142159af3adfce22 #v1.88.0 -# EXCLUDE_FROM_ALL -# FIND_PACKAGE_ARGS COMPONENTS dynamic_bitset -# ) -# FetchContent_MakeAvailable(Boost) -#endif() - find_package(sul-dynamic_bitset) if (NOT sul-dynamic_bitset_DIR OR "${sul-dynamic_bitset_DIR}" STREQUAL "sul-dynamic_bitset-NOTFOUND") FetchContent_Declare( @@ -72,14 +63,15 @@ target_sources(bitlib-bench PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/shift_bench.hpp ${CMAKE_CURRENT_SOURCE_DIR}/src/swap_ranges-bench.hpp ${CMAKE_CURRENT_SOURCE_DIR}/src/transform_bench.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/inc/benchmark_utils.hpp ) target_include_directories(bitlib-bench PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/inc ) target_link_libraries(bitlib-bench PRIVATE bitlib - bitlib_utils benchmark::benchmark sul::dynamic_bitset BitArray) diff --git a/benchmark/inc/benchmark_utils.hpp b/benchmark/inc/benchmark_utils.hpp new file mode 100644 index 00000000..ed4e7351 --- /dev/null +++ b/benchmark/inc/benchmark_utils.hpp @@ -0,0 +1,49 @@ +#ifndef _BENCHMARK_UTILS_HPP_ +#define _BENCHMARK_UTILS_HPP_ + +#include +#include +#include +#include +#include +#include + +// Produces container of random numbers from min to max +template +Container make_random_container( + std::size_t size, + T min = std::numeric_limits::min(), + T max = std::numeric_limits::max(), + const T& seed = T() +) +{ + Container c(size); + std::random_device device; + std::mt19937 engine(seed == T() ? device() : seed); + std::uniform_int_distribution distribution(min, max); + auto it = std::begin(c); + for (std::size_t i = 0; i < size; ++i) { + *it = distribution(engine); + ++it; + } + return c; +} + +template +std::vector get_random_vec( + unsigned long long int size, + WordType min = std::numeric_limits::min(), + WordType max = std::numeric_limits::max()) { + std::random_device device; + std::mt19937 mersenne_engine(device()); + std::uniform_int_distribution dist{min, max}; + + auto gen = [&dist, &mersenne_engine]() { + return dist(mersenne_engine); + }; + std::vector vec(size); + generate(begin(vec), end(vec), gen); + return vec; +} + +#endif diff --git a/benchmark/src/copy_backward_bench.hpp b/benchmark/src/copy_backward_bench.hpp index 229a08b5..da2408af 100644 --- a/benchmark/src/copy_backward_bench.hpp +++ b/benchmark/src/copy_backward_bench.hpp @@ -1,5 +1,6 @@ #include -#include "test_utils.hpp" + +#include "benchmark_utils.hpp" #include "bitlib/bitlib.hpp" auto BM_BitCopyBackward = [](benchmark::State& state, auto input) { @@ -30,8 +31,8 @@ auto BM_BoolCopyBackward = [](benchmark::State& state, auto input) { using container_type = typename std::tuple_element<0, decltype(input)>::type; unsigned int total_bits = std::get<2>(input); auto container_size = total_bits; - container_type boolvec1 = make_random_container (container_size); - container_type boolvec2 = make_random_container (container_size); + container_type boolvec1 = make_random_container(container_size); + container_type boolvec2 = make_random_container(container_size); auto first1 = boolvec1.begin(); auto first2 = boolvec2.begin(); diff --git a/benchmark/src/copy_bench.hpp b/benchmark/src/copy_bench.hpp index 7da3da86..8fe9246d 100644 --- a/benchmark/src/copy_bench.hpp +++ b/benchmark/src/copy_bench.hpp @@ -1,5 +1,6 @@ #include -#include "test_utils.hpp" + +#include "benchmark_utils.hpp" #include "bitlib/bitlib.hpp" auto BM_BitCopy = [](benchmark::State& state, auto input) { @@ -30,8 +31,8 @@ auto BM_BoolCopy = [](benchmark::State& state, auto input) { using container_type = typename std::tuple_element<0, decltype(input)>::type; unsigned int total_bits = std::get<2>(input); auto container_size = total_bits; - container_type boolvec1 = make_random_container (container_size); - container_type boolvec2 = make_random_container (container_size); + container_type boolvec1 = make_random_container(container_size); + container_type boolvec2 = make_random_container(container_size); auto first1 = boolvec1.begin(); auto first2 = boolvec2.begin(); diff --git a/benchmark/src/count_bench.hpp b/benchmark/src/count_bench.hpp index a169d005..476ef7f3 100644 --- a/benchmark/src/count_bench.hpp +++ b/benchmark/src/count_bench.hpp @@ -1,8 +1,9 @@ #include #include -#include "test_utils.hpp" -#include "bitlib/bit-algorithms/count.hpp" + +#include "benchmark_utils.hpp" #include "bit_array.h" +#include "bitlib/bit-algorithms/count.hpp" #include "sul/dynamic_bitset.hpp" auto BM_BitCount = [](benchmark::State& state, auto input) { @@ -11,7 +12,7 @@ auto BM_BitCount = [](benchmark::State& state, auto input) { unsigned int total_bits = std::get<2>(input); auto digits = bit::binary_digits::value; auto container_size = ceil(float(total_bits) / digits); - container_type bitcont = make_random_container(container_size); + container_type bitcont = make_random_container(container_size); auto first = bit::bit_iterator(std::begin(bitcont)); auto last = bit::bit_iterator(std::end(bitcont)); for (auto _ : state) { @@ -48,7 +49,7 @@ auto BM_BoolCount = [](benchmark::State& state, auto input) { using container_type = std::vector; using num_type = typename container_type::value_type; unsigned int container_size = std::get<2>(input); - container_type cont = make_random_container(container_size); + container_type cont = make_random_container(container_size); auto first = cont.begin(); auto last = cont.end(); for (auto _ : state) { diff --git a/benchmark/src/equal_bench.hpp b/benchmark/src/equal_bench.hpp index b6ce6f36..3e42376c 100644 --- a/benchmark/src/equal_bench.hpp +++ b/benchmark/src/equal_bench.hpp @@ -1,5 +1,6 @@ #include -#include "test_utils.hpp" + +#include "benchmark_utils.hpp" #include "bitlib/bitlib.hpp" auto BM_BitEqual = [](benchmark::State& state, auto input) { @@ -32,8 +33,8 @@ auto BM_BoolEqual = [](benchmark::State& state, auto input) { using container_type = typename std::tuple_element<0, decltype(input)>::type; unsigned int total_bits = std::get<2>(input); auto container_size = total_bits; - container_type boolvec1 = make_random_container (container_size); - container_type boolvec2 = make_random_container (container_size); + container_type boolvec1 = make_random_container(container_size); + container_type boolvec2 = make_random_container(container_size); auto first1 = boolvec1.begin(); auto first2 = boolvec2.begin(); diff --git a/benchmark/src/move_bench.hpp b/benchmark/src/move_bench.hpp index de76d6a3..237d9c2d 100644 --- a/benchmark/src/move_bench.hpp +++ b/benchmark/src/move_bench.hpp @@ -1,5 +1,6 @@ #include -#include "test_utils.hpp" + +#include "benchmark_utils.hpp" #include "bitlib/bitlib.hpp" auto BM_BitMove = [](benchmark::State& state, auto input) { @@ -29,8 +30,8 @@ auto BM_BoolMove = [](benchmark::State& state, auto input) { using container_type = typename std::tuple_element<0, decltype(input)>::type; unsigned int total_bits = std::get<2>(input); auto container_size = total_bits; - container_type boolvec1 = make_random_container (container_size); - container_type boolvec2 = make_random_container (container_size); + container_type boolvec1 = make_random_container(container_size); + container_type boolvec2 = make_random_container(container_size); auto first1 = boolvec1.begin(); auto first2 = boolvec2.begin(); diff --git a/benchmark/src/rw_bench.hpp b/benchmark/src/rw_bench.hpp index 4b61aae5..b9f976d9 100644 --- a/benchmark/src/rw_bench.hpp +++ b/benchmark/src/rw_bench.hpp @@ -1,7 +1,8 @@ #include -#include "test_utils.hpp" -#include "sul/dynamic_bitset.hpp" + +#include "benchmark_utils.hpp" #include "bitlib/bitlib.hpp" +#include "sul/dynamic_bitset.hpp" auto BM_BitSet = [](benchmark::State& state, auto input) { using container_type = typename std::tuple_element<0, decltype(input)>::type; diff --git a/benchmark/src/swap_ranges-bench.hpp b/benchmark/src/swap_ranges-bench.hpp index 92cc5845..9e5be48d 100644 --- a/benchmark/src/swap_ranges-bench.hpp +++ b/benchmark/src/swap_ranges-bench.hpp @@ -1,5 +1,6 @@ #include -#include "test_utils.hpp" + +#include "benchmark_utils.hpp" #include "bitlib/bitlib.hpp" auto BM_BitSwapRangesAA = [](benchmark::State& state, auto input) { @@ -51,8 +52,8 @@ auto BM_BoolSwapRanges = [](benchmark::State& state, auto input) { using container_type = typename std::tuple_element<0, decltype(input)>::type; unsigned int total_bits = std::get<2>(input); auto container_size = total_bits; - container_type boolvec1 = make_random_container (container_size); - container_type boolvec2 = make_random_container (container_size); + container_type boolvec1 = make_random_container(container_size); + container_type boolvec2 = make_random_container(container_size); auto first1 = boolvec1.begin(); auto first2 = boolvec2.begin(); diff --git a/benchmark/src/transform_bench.hpp b/benchmark/src/transform_bench.hpp index 673d4953..7576934c 100644 --- a/benchmark/src/transform_bench.hpp +++ b/benchmark/src/transform_bench.hpp @@ -1,10 +1,12 @@ -#include -#include #include -#include "test_utils.hpp" + +#include +#include + +#include "benchmark_utils.hpp" +#include "bit_array.h" #include "bitlib/bitlib.hpp" #include "sul/dynamic_bitset.hpp" -#include "bit_array.h" auto BM_BitTransformUnaryAA = [](benchmark::State& state, auto input) { using container_type = typename std::tuple_element<0, decltype(input)>::type; @@ -84,8 +86,8 @@ auto BM_BoolTransformUnary = [](benchmark::State& state, auto input) { using container_type = typename std::tuple_element<0, decltype(input)>::type; unsigned int total_bits = std::get<2>(input); auto container_size = total_bits; - container_type boolvec1 = make_random_container (container_size); - container_type boolvec2 = make_random_container (container_size); + container_type boolvec1 = make_random_container(container_size); + container_type boolvec2 = make_random_container(container_size); auto first1 = boolvec1.begin(); auto first2 = boolvec2.begin(); @@ -191,9 +193,9 @@ auto BM_BoolTransformBinary = [](benchmark::State& state, auto input) { using container_type = typename std::tuple_element<0, decltype(input)>::type; unsigned int total_bits = std::get<2>(input); auto container_size = total_bits; - container_type boolvec1 = make_random_container (container_size); - container_type boolvec2 = make_random_container (container_size); - container_type boolvec3 = make_random_container (container_size); + container_type boolvec1 = make_random_container(container_size); + container_type boolvec2 = make_random_container(container_size); + container_type boolvec3 = make_random_container(container_size); auto first1 = boolvec1.begin(); auto first2 = boolvec2.begin(); diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index c7b775a4..11206377 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,12 +1,6 @@ -add_executable(BitLib_Example1) +add_executable(BitLib_Example1 EXCLUDE_FROM_ALL) target_sources(BitLib_Example1 PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/example1.cpp" ) target_link_libraries(BitLib_Example1 PRIVATE bitlib::bitlib) - -add_executable(BitLib_Example2) -target_sources(BitLib_Example2 PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/src/example2.cpp" -) -target_link_libraries(BitLib_Example2 PRIVATE bitlib::bitlib bitlib_utils) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b1493537..6fef04b4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -38,7 +38,9 @@ target_sources(bitlib-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/test-transform.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-usecase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/vector_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/inc/test_utils.hpp ) +target_include_directories(bitlib-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/inc) if (BITLIB_MDSPAN) target_sources(bitlib-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/test-mdspan.cpp) @@ -51,7 +53,7 @@ endif() # endif() # specify test-specific libraries -target_link_libraries(bitlib-tests PRIVATE bitlib GTest::gtest GTest::gtest_main bitlib_utils) +target_link_libraries(bitlib-tests PRIVATE bitlib GTest::gtest GTest::gtest_main) target_link_options(bitlib-tests PRIVATE -pthread -lgcov --coverage) if (NOT BITLIB_GTEST_REPEAT) diff --git a/utils/test_utils.hpp b/test/inc/test_utils.hpp similarity index 67% rename from utils/test_utils.hpp rename to test/inc/test_utils.hpp index 8938abbd..3eead5cf 100644 --- a/utils/test_utils.hpp +++ b/test/inc/test_utils.hpp @@ -14,12 +14,14 @@ // ================================ PREAMBLE ================================ // // C++ standard library +#include + +#include #include +#include #include #include #include -#include -#include // Project sources #include "bitlib/bitlib.hpp" // Third-party libraries @@ -67,55 +69,44 @@ auto bitcont_to_boolcont(const Container bitcont){ return c; } -// Produces container of random numbers from min to max -template -Container make_random_container( - std::size_t size, - T min = std::numeric_limits::min(), - T max = std::numeric_limits::max(), - const T& seed = T() -) -{ - Container c(size); - std::random_device device; - std::mt19937 engine(seed == T() ? device() : seed); - std::uniform_int_distribution distribution(min, max); - auto it = std::begin(c); - for (std::size_t i = 0; i < size; ++i) { - *it = distribution(engine); - ++it; - } - return c; -} +inline std::mt19937 GetSeededRNGFromTestName() { + auto test_info = ::testing::UnitTest::GetInstance()->current_test_info(); + if (test_info != nullptr) { + std::string full_test_name = std::string(test_info->test_suite_name()) + "." + test_info->name(); + // Hash the name to get a seed + std::size_t hash_value = std::hash{}(full_test_name); -inline unsigned long long generate_random_number(size_t min, size_t max) { - // First create an instance of an engine. + // Use the hash as a seed + return std::mt19937(static_cast(hash_value)); + } else { std::random_device rnd_device; // Specify the engine and distribution. - std::mt19937 mersenne_engine {rnd_device()}; // Generates random integers - std::uniform_int_distribution dist {min, max}; + return std::mt19937(rnd_device()); + } +} - return dist(mersenne_engine); +inline unsigned long long generate_random_number(size_t min, size_t max) { + std::mt19937 mersenne_engine = GetSeededRNGFromTestName(); + std::uniform_int_distribution dist{min, max}; + + return dist(mersenne_engine); } template std::array get_random_arr( - WordType min = std::numeric_limits::min(), - WordType max = std::numeric_limits::max() -) { - // First create an instance of an engine. - std::random_device rnd_device; - // Specify the engine and distribution. - std::mt19937 mersenne_engine {rnd_device()}; // Generates random integers - std::uniform_int_distribution dist {min, max}; - - auto gen = [&dist, &mersenne_engine](){ - return dist(mersenne_engine); - }; - std::array arr{}; - generate(begin(arr), end(arr), gen); - return arr; + WordType min = std::numeric_limits::min(), + WordType max = std::numeric_limits::max()) { + // Specify the engine and distribution. + std::mt19937 mersenne_engine = GetSeededRNGFromTestName(); + std::uniform_int_distribution dist{min, max}; + + auto gen = [&dist, &mersenne_engine]() { + return dist(mersenne_engine); + }; + std::array arr{}; + generate(begin(arr), end(arr), gen); + return arr; } template @@ -124,18 +115,15 @@ std::vector get_random_vec( WordType min = std::numeric_limits::min(), WordType max = std::numeric_limits::max() ) { - // First create an instance of an engine. - std::random_device rnd_device; - // Specify the engine and distribution. - std::mt19937 mersenne_engine {rnd_device()}; // Generates random integers - std::uniform_int_distribution dist {min, max}; - - auto gen = [&dist, &mersenne_engine](){ - return dist(mersenne_engine); - }; - std::vector vec(size); - generate(begin(vec), end(vec), gen); - return vec; + std::mt19937 mersenne_engine = GetSeededRNGFromTestName(); + std::uniform_int_distribution dist{min, max}; + + auto gen = [&dist, &mersenne_engine]() { + return dist(mersenne_engine); + }; + std::vector vec(size); + generate(begin(vec), end(vec), gen); + return vec; } template From 0a31272173f07fa8201e89eb7eb6851a614935b5 Mon Sep 17 00:00:00 2001 From: Peter McLean <112978670+Peter-McLean-Altera@users.noreply.github.com> Date: Mon, 5 May 2025 13:55:24 -0400 Subject: [PATCH 58/85] Fix Wdeprecated-copy implicitly declared ... is deprecated (#4) --- include/bitlib/bit-iterator/bit_iterator.hpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/include/bitlib/bit-iterator/bit_iterator.hpp b/include/bitlib/bit-iterator/bit_iterator.hpp index 953b60a9..90b4828a 100644 --- a/include/bitlib/bit-iterator/bit_iterator.hpp +++ b/include/bitlib/bit-iterator/bit_iterator.hpp @@ -270,14 +270,28 @@ constexpr bit_iterator bit_iterator::operator-(difference_ty // Increments the iterator by several bits and returns it template constexpr bit_iterator& bit_iterator::operator+=(difference_type n) { - *this = *this + n; + constexpr difference_type digits = binary_digits::value; + const difference_type sum = _position + n; + difference_type diff = sum / digits; + if (sum < 0 && diff * digits != sum) { + --diff; + } + _current = std::next(_current, diff); + _position = (sum - diff * digits); return *this; } // Decrements the iterator by several bits and returns it template constexpr bit_iterator& bit_iterator::operator-=(difference_type n) { - *this = *this - n; + constexpr difference_type digits = binary_digits::value; + const difference_type sum = _position - n; + difference_type diff = sum / digits; + if (sum < 0 && diff * digits != sum) { + --diff; + } + _current = std::next(_current, diff); + _position = (sum - diff * digits); return *this; } // -------------------------------------------------------------------------- // From 701068a5042c1836bba2571aaaefc610163d82dc Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Tue, 6 May 2025 20:03:33 -0400 Subject: [PATCH 59/85] Make github action for benchmark (#3) * Make github action for benchmark * Combine github workflows with condition postprocessing * action file syntax * Post process benchmark json * json will be in subdirectory * shell bash in action.yml * require GitHub secret token * pass secrets to coverage epilogue * composite action cannot access secrets must use inputs * pass secret as input * properly reference inputs --- .github/actions/benchmark_epilogue/action.yml | 26 ++ .github/actions/coverage_epilogue/action.yml | 47 +++ .github/workflows/cmake-multi-platform.yml | 22 +- .github/workflows/coverage.yml | 102 ------ .gitmodules | 9 - CMakeLists.txt | 2 +- CMakePresets.json | 329 +++++++++++++++++- benchmark/CMakeLists.txt | 24 ++ 8 files changed, 447 insertions(+), 114 deletions(-) create mode 100644 .github/actions/benchmark_epilogue/action.yml create mode 100644 .github/actions/coverage_epilogue/action.yml delete mode 100644 .github/workflows/coverage.yml delete mode 100644 .gitmodules diff --git a/.github/actions/benchmark_epilogue/action.yml b/.github/actions/benchmark_epilogue/action.yml new file mode 100644 index 00000000..448cdb79 --- /dev/null +++ b/.github/actions/benchmark_epilogue/action.yml @@ -0,0 +1,26 @@ +name: "Benchmark Epilogue" +description: "Processes coverage information with lcov and uploads it to coveralls/codecov" +inputs: + build-output-dir: + required: true + description: 'Build output directory' +runs: + using: "composite" + steps: + # Download previous benchmark result from cache (if exists) + - name: Download previous benchmark data + uses: actions/cache@v4 + with: + path: ./cache + key: ${{ runner.os }}-benchmark + + - name: Store benchmark result + uses: benchmark-action/github-action-benchmark@v1 + with: + tool: 'googlecpp' + # Where the output from the benchmark tool is stored + output-file-path: ${{ inputs.build-output-dir }}/benchmark/benchmark_result.json + # Where the previous data file is stored + external-data-json-path: ./cache/benchmark_result.json + # Workflow will fail when an alert happens + fail-on-alert: true diff --git a/.github/actions/coverage_epilogue/action.yml b/.github/actions/coverage_epilogue/action.yml new file mode 100644 index 00000000..f5de5588 --- /dev/null +++ b/.github/actions/coverage_epilogue/action.yml @@ -0,0 +1,47 @@ +name: "Coverage Epilogue" +description: "Processes coverage information with lcov and uploads it to coveralls/codecov" +inputs: + build-output-dir: + required: true + description: 'Build output directory' + GITHUB_TOKEN: + required: true + +runs: + using: "composite" + steps: + - name: "Install lcov" + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y lcov + + - name: Generate Coverage Report + shell: bash + run: | + lcov --capture --directory ${{ inputs.build-output-dir }} --output-file coverage.info --ignore-errors mismatch + lcov --remove coverage.info '/usr/*' '*/_deps/*' '*/include/bitlib/bit-algorithms/libpopcnt.h' '*/test/inc/*' '*/test/src/*' --output-file coverage.info + lcov --list coverage.info + # Generate an HTML report + genhtml coverage.info --output-directory out/coverage + + - name: Upload coverage artifact + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: | + out/coverage + coverage.info + + - name: Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ inputs.GITHUB_TOKEN }} + path-to-lcov: coverage.info + + - name: Upload to Codecov + uses: codecov/codecov-action@v5 + with: + files: coverage.info + flags: unittests + name: codecov-coverage-report diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index ae516cc2..c0a37e00 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -64,12 +64,21 @@ jobs: preset: tests target: bitlib-tests build_type: Debug + - distro: ubuntu-latest + os: linux + compiler: gcc + c_compiler: gcc + cpp_compiler: g++ + stdlib: libstdcxx + preset: coverage + target: bitlib-tests + build_type: Debug steps: - uses: actions/checkout@v4 - name: "Install Clang 19" - if: matrix.distro == 'ubuntu-latest' && matrix.c_compiler == 'clang-19' + if: matrix.os == 'linux' && matrix.c_compiler == 'clang-19' run: | sudo apt-get update sudo apt-get install -y wget gnupg lsb-release @@ -104,3 +113,14 @@ jobs: # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest --test-dir ${{ steps.strings.outputs.build-output-dir }} --build-config ${{ matrix.build_type }} --output-on-failure --parallel + + - uses: ./.github/actions/coverage_epilogue + if: matrix.preset == 'coverage' + with: + build-output-dir: ${{ steps.strings.outputs.build-output-dir }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: ./.github/actions/benchmark_epilogue + if: matrix.preset == 'benchmark' + with: + build-output-dir: ${{ steps.strings.outputs.build-output-dir }} diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml deleted file mode 100644 index bce83fa8..00000000 --- a/.github/workflows/coverage.yml +++ /dev/null @@ -1,102 +0,0 @@ -name: Coverage - -on: - push: - branches: [ "master" ] - paths-ignore: - - '**/*.md' - pull_request: - branches: [ "master" ] - paths-ignore: - - '**/*.md' - -jobs: - coverage: - runs-on: ${{ matrix.distro }} - - strategy: - # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. - fail-fast: false - - # Set up a matrix to run the following 3 configurations: - # 1. - # 2. - # 3. - # - # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. - matrix: - distro: [ubuntu-latest] - build_type: [Debug] - compiler: [gcc] - include: - - distro: ubuntu-latest - os: linux - compiler: gcc - c_compiler: gcc - cpp_compiler: g++ - stdlib: libstdcxx - build_type: Debug - - steps: - - uses: actions/checkout@v4 - - - name: Set reusable strings - # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. - id: strings - shell: bash - run: | - echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" - - - name: "Install lcov" - run: | - sudo apt-get update - sudo apt-get install -y lcov - - - name: Configure CMake - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: > - cmake -B ${{ steps.strings.outputs.build-output-dir }} - -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} - -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} - -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - --preset=coverage_${{ matrix.os }}_${{ matrix.compiler }}_${{ matrix.stdlib }} - -S ${{ github.workspace }} - - - name: Build - # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). - run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} --parallel - - - name: Test - # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --test-dir ${{ steps.strings.outputs.build-output-dir }} --build-config ${{ matrix.build_type }} --output-on-failure --parallel - - - name: Generate Coverage Report - run: | - lcov --capture --directory ${{ steps.strings.outputs.build-output-dir }} --output-file coverage.info --ignore-errors mismatch - lcov --remove coverage.info '/usr/*' '*/_deps/*' '*/include/bitlib/bit-algorithms/libpopcnt.h' '*/test/inc/*' '*/test/src/*' --output-file coverage.info - lcov --list coverage.info - # Generate an HTML report - genhtml coverage.info --output-directory out/coverage - - - name: Upload coverage artifact - uses: actions/upload-artifact@v4 - with: - name: coverage-report - path: | - out/coverage - coverage.info - - - name: Coveralls - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: coverage.info - - - name: Upload to Codecov - uses: codecov/codecov-action@v5 - with: - files: coverage.info - flags: unittests - name: codecov-coverage-report diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 29bb15de..00000000 --- a/.gitmodules +++ /dev/null @@ -1,9 +0,0 @@ -[submodule "benchmark/ext/BitArray"] - path = benchmark/ext/BitArray - url = https://github.com/noporpoise/BitArray.git -[submodule "benchmark/ext/itsy_bitsy"] - path = benchmark/ext/itsy_bitsy - url = https://github.com/ThePhD/itsy_bitsy.git -[submodule "benchmark/ext/dynamic_bitset"] - path = benchmark/ext/dynamic_bitset - url = https://github.com/pinam45/dynamic_bitset.git diff --git a/CMakeLists.txt b/CMakeLists.txt index f7235a55..21ef3768 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,7 +83,7 @@ if (BITLIB_HWY) endif() if (BITLIB_BENCHMARK) - set(BENCHMARK_ENABLE_GTEST_TESTS OFF) + enable_testing() add_subdirectory(benchmark) endif() diff --git a/CMakePresets.json b/CMakePresets.json index 68baddd6..1a4cebb9 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -4,7 +4,7 @@ { "name": "base", "hidden": true, - "binaryDir": "${sourceDir}/build/${presetName}", + "binaryDir": "${sourceDir}/../build/${presetName}", "cacheVariables": { "CMAKE_EXPORT_COMPILE_COMMANDS": "YES" } @@ -88,6 +88,7 @@ "hidden": true, "inherits": "msvc_base", "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", "BITLIB_BENCHMARK": "ON" } }, @@ -118,6 +119,7 @@ "hidden": true, "inherits": "gnu_base", "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", "CMAKE_C_FLAGS": "-O3 -DNDEBUG -march=native", "CMAKE_CXX_FLAGS": "-O3 -DNDEBUG -march=native", "BITLIB_BENCHMARK": "ON" @@ -128,6 +130,7 @@ "hidden": true, "inherits": "gnu_base", "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", "CMAKE_C_FLAGS": "-pg -fno-omit-frame-pointer", "CMAKE_CXX_FLAGS": "-pg -fno-omit-frame-pointer", "CMAKE_C_FLAGS_RELEASE": "-O2 -DNDEBUG -march=native -pg -ggdb", @@ -198,5 +201,329 @@ "benchmark_gnu_base" ] } + ], + "buildPresets": [ + { + "name": "tests_windows_msvc_msvcstl", + "configurePreset": "tests_windows_msvc_msvcstl", + "targets": "bitlib-tests", + "configuration": "Debug", + "jobs": 0, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "benchmark_windows_msvc_msvcstl", + "configurePreset": "benchmark_windows_msvc_msvcstl", + "targets": "bitlib-bench", + "configuration": "Release", + "jobs": 0, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "tests_linux_gcc_libstdcxx", + "configurePreset": "tests_linux_gcc_libstdcxx", + "targets": "bitlib-tests", + "jobs": 0, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "coverage_linux_gcc_libstdcxx", + "configurePreset": "coverage_linux_gcc_libstdcxx", + "targets": "bitlib-tests", + "jobs": 0, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "benchmark_linux_gcc_libstdcxx", + "configurePreset": "benchmark_linux_gcc_libstdcxx", + "targets": "bitlib-bench", + "jobs": 0, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "tests_linux_clang_libcxx", + "configurePreset": "tests_linux_clang_libcxx", + "targets": "bitlib-tests", + "jobs": 0, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "coverage_linux_clang_libcxx", + "configurePreset": "coverage_linux_clang_libcxx", + "targets": "bitlib-tests", + "jobs": 0, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "benchmark_linux_clang_libcxx", + "configurePreset": "benchmark_linux_clang_libcxx", + "targets": "bitlib-bench", + "jobs": 0, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + } + ], + "testPresets": [ + { + "name": "tests_windows_msvc_msvcstl", + "configurePreset": "tests_windows_msvc_msvcstl", + "configuration": "Debug", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "benchmark_windows_msvc_msvcstl", + "configurePreset": "benchmark_windows_msvc_msvcstl", + "configuration": "Release", + "execution": { + "jobs": 1 + }, + "output": { + "verbosity": "verbose" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "tests_linux_gcc_libstdcxx", + "configurePreset": "tests_linux_gcc_libstdcxx", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "coverage_linux_gcc_libstdcxx", + "configurePreset": "coverage_linux_gcc_libstdcxx", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "benchmark_linux_gcc_libstdcxx", + "configurePreset": "benchmark_linux_gcc_libstdcxx", + "execution": { + "jobs": 1 + }, + "output": { + "verbosity": "verbose" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "tests_linux_clang_libcxx", + "configurePreset": "tests_linux_clang_libcxx", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "coverage_linux_clang_libcxx", + "configurePreset": "coverage_linux_clang_libcxx", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "benchmark_linux_clang_libcxx", + "configurePreset": "benchmark_linux_clang_libcxx", + "execution": { + "jobs": 1 + }, + "output": { + "verbosity": "verbose" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + } + ], + "workflowPresets": [ + { + "name": "tests_windows_msvc_msvcstl", + "steps": [ + { + "type": "configure", + "name": "tests_windows_msvc_msvcstl" + }, + { + "type": "build", + "name": "tests_windows_msvc_msvcstl" + }, + { + "type": "test", + "name": "tests_windows_msvc_msvcstl" + } + ] + }, + { + "name": "benchmark_windows_msvc_msvcstl", + "steps": [ + { + "type": "configure", + "name": "benchmark_windows_msvc_msvcstl" + }, + { + "type": "build", + "name": "benchmark_windows_msvc_msvcstl" + }, + { + "type": "test", + "name": "benchmark_windows_msvc_msvcstl" + } + ] + }, + { + "name": "tests_linux_gcc_libstdcxx", + "steps": [ + { + "type": "configure", + "name": "tests_linux_gcc_libstdcxx" + }, + { + "type": "build", + "name": "tests_linux_gcc_libstdcxx" + }, + { + "type": "test", + "name": "tests_linux_gcc_libstdcxx" + } + ] + }, + { + "name": "coverage_linux_gcc_libstdcxx", + "steps": [ + { + "type": "configure", + "name": "coverage_linux_gcc_libstdcxx" + }, + { + "type": "build", + "name": "coverage_linux_gcc_libstdcxx" + }, + { + "type": "test", + "name": "coverage_linux_gcc_libstdcxx" + } + ] + }, + { + "name": "benchmark_linux_gcc_libstdcxx", + "steps": [ + { + "type": "configure", + "name": "benchmark_linux_gcc_libstdcxx" + }, + { + "type": "build", + "name": "benchmark_linux_gcc_libstdcxx" + }, + { + "type": "test", + "name": "benchmark_linux_gcc_libstdcxx" + } + ] + }, + { + "name": "tests_linux_clang_libcxx", + "steps": [ + { + "type": "configure", + "name": "tests_linux_clang_libcxx" + }, + { + "type": "build", + "name": "tests_linux_clang_libcxx" + }, + { + "type": "test", + "name": "tests_linux_clang_libcxx" + } + ] + }, + { + "name": "coverage_linux_clang_libcxx", + "steps": [ + { + "type": "configure", + "name": "coverage_linux_clang_libcxx" + }, + { + "type": "build", + "name": "coverage_linux_clang_libcxx" + }, + { + "type": "test", + "name": "coverage_linux_clang_libcxx" + } + ] + }, + { + "name": "benchmark_linux_clang_libcxx", + "steps": [ + { + "type": "configure", + "name": "benchmark_linux_clang_libcxx" + }, + { + "type": "build", + "name": "benchmark_linux_clang_libcxx" + }, + { + "type": "test", + "name": "benchmark_linux_clang_libcxx" + } + ] + } ] } diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index a302f7e9..3a09d660 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -2,6 +2,7 @@ set(BENCHMARK_ENABLE_GTEST_TESTS OFF) include(FetchContent) +set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "" FORCE) set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) set(BENCHMARK_ENABLE_WERROR OFF CACHE BOOL "" FORCE) @@ -27,6 +28,24 @@ if (NOT sul-dynamic_bitset_DIR OR "${sul-dynamic_bitset_DIR}" STREQUAL "sul-dyna FetchContent_MakeAvailable(sul-dynamic_bitset) endif() +if (NOT TARGET itsy::bitsy) + + set(ITSY_BITSY_SINGLE OFF CACHE BOOL "" FORCE) + set(ITSY_BITSY_TESTS OFF CACHE BOOL "" FORCE) + + find_package(itsy.bitsy) + if (NOT itsy.bitsy_DIR OR "${itsy.bitsy_DIR}" STREQUAL "itsy.bitsy-NOTFOUND") + FetchContent_Declare( + itsy.bitsy + GIT_REPOSITORY https://github.com/ThePhD/itsy_bitsy.git + GIT_TAG d5b6bf9509bb2dff6235452d427f0b1c349d5f8b # main @ Aug 1, 2022 + EXCLUDE_FROM_ALL + FIND_PACKAGE_ARGS + ) + FetchContent_MakeAvailable(itsy.bitsy) + endif() +endif() + FetchContent_Declare( BitArray GIT_REPOSITORY https://github.com/noporpoise/BitArray.git @@ -75,3 +94,8 @@ target_link_libraries(bitlib-bench PRIVATE benchmark::benchmark sul::dynamic_bitset BitArray) + +add_test( + NAME BenchmarkTest + COMMAND $ --benchmark_format=json --benchmark_out=benchmark_result.json +) From a837637fd7d015f522ecea9cffd015204fc28899 Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Thu, 8 May 2025 19:58:05 -0400 Subject: [PATCH 60/85] compiler problem matchers (#6) * compiler problem matchers * Fix GTest discovery * Encode more metadata for cache * Archive benchmark json * Archive first because runners are eager to nuke the workflow * Remove stray link options in bitlib-tests * compose Artifact name --- .github/actions/benchmark_epilogue/action.yml | 17 ++++++++- .../clang_problem_matcher.json | 17 +++++++++ .../problem_matchers/gcc_problem_matcher.json | 17 +++++++++ .../msvc_problem_matcher.json | 18 ++++++++++ .github/workflows/cmake-multi-platform.yml | 9 +++++ test/CMakeLists.txt | 35 ++++++++----------- 6 files changed, 92 insertions(+), 21 deletions(-) create mode 100644 .github/problem_matchers/clang_problem_matcher.json create mode 100644 .github/problem_matchers/gcc_problem_matcher.json create mode 100644 .github/problem_matchers/msvc_problem_matcher.json diff --git a/.github/actions/benchmark_epilogue/action.yml b/.github/actions/benchmark_epilogue/action.yml index 448cdb79..c7222878 100644 --- a/.github/actions/benchmark_epilogue/action.yml +++ b/.github/actions/benchmark_epilogue/action.yml @@ -1,18 +1,33 @@ name: "Benchmark Epilogue" description: "Processes coverage information with lcov and uploads it to coveralls/codecov" inputs: + compiler: + required: True + description: 'Compiler used to build benchmark' + os: + required: True + description: 'OS used to build benchmark' + stdlib: + required: True + description: 'C++ standard library used to build benchmark' build-output-dir: required: true description: 'Build output directory' runs: using: "composite" steps: + - name: Archive benchmark results + uses: actions/upload-artifact@v4 + with: + name: benchmark_${{ inputs.os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_json + path: ${{ inputs.build-output-dir }}/benchmark/benchmark_result.json + # Download previous benchmark result from cache (if exists) - name: Download previous benchmark data uses: actions/cache@v4 with: path: ./cache - key: ${{ runner.os }}-benchmark + key: benchmark_${{ inputs.os }}_${{ inputs.compiler }}_${{ inputs.stdlib }} - name: Store benchmark result uses: benchmark-action/github-action-benchmark@v1 diff --git a/.github/problem_matchers/clang_problem_matcher.json b/.github/problem_matchers/clang_problem_matcher.json new file mode 100644 index 00000000..b3af1a14 --- /dev/null +++ b/.github/problem_matchers/clang_problem_matcher.json @@ -0,0 +1,17 @@ +{ + "problemMatcher": [ + { + "owner": "clang", + "pattern": [ + { + "regexp": "^(.*?):(\\d+):(\\d*):?\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + ] + } + ] +} \ No newline at end of file diff --git a/.github/problem_matchers/gcc_problem_matcher.json b/.github/problem_matchers/gcc_problem_matcher.json new file mode 100644 index 00000000..75374fac --- /dev/null +++ b/.github/problem_matchers/gcc_problem_matcher.json @@ -0,0 +1,17 @@ +{ + "problemMatcher": [ + { + "owner": "gcc", + "pattern": [ + { + "regexp": "^(.*?):(\\d+):(\\d*):?\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + ] + } + ] +} \ No newline at end of file diff --git a/.github/problem_matchers/msvc_problem_matcher.json b/.github/problem_matchers/msvc_problem_matcher.json new file mode 100644 index 00000000..5d85a18b --- /dev/null +++ b/.github/problem_matchers/msvc_problem_matcher.json @@ -0,0 +1,18 @@ +{ + "problemMatcher": [ + { + "owner": "msvc", + "pattern": [ + { + "regexp": "^(?:\\s+\\d+\\>)?([^\\s].*)\\((\\d+),?(\\d+)?(?:,\\d+,\\d+)?\\)\\s*:\\s+(error|warning|info)\\s+(\\w{1,2}\\d+)\\s*:\\s*(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "code": 5, + "message": 6 + } + ] + } + ] +} \ No newline at end of file diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index c0a37e00..8b8cf439 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -105,10 +105,16 @@ jobs: -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + - name: Register Compiler Problem Matcher + run: echo "::add-matcher::.github/problem_matchers/${{ matrix.compiler}}_problem_matcher.json" + - name: Build # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --target ${{ matrix.target }} --config ${{ matrix.build_type }} --parallel + - name: Unregister Compiler Problem Matcher + run: echo "::remove-matcher owner=${{ matrix.compiler}}::" + - name: Test # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail @@ -123,4 +129,7 @@ jobs: - uses: ./.github/actions/benchmark_epilogue if: matrix.preset == 'benchmark' with: + os: ${{ matrix.os }} + compiler: ${{ matrix.compiler }} + stdlib: ${{ matrix.stdlib }} build-output-dir: ${{ steps.strings.outputs.build-output-dir }} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6fef04b4..8c621da4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -5,17 +5,19 @@ include(FetchContent) -find_package(GTest) -if (NOT GTest OR "${GTest_DIR}" STREQUAL "GTest-NOTFOUND") - FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG 6910c9d9165801d8827d628cb72eb7ea9dd538c5 # v1.16.x - ) - # For Windows: Prevent overriding the parent project's compiler/linker settings - set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) - FetchContent_MakeAvailable(googletest) - include(GoogleTest) +if (NOT TARGET GTest::gtest) + find_package(GTest) + if (NOT GTest_DIR OR "${GTest_DIR}" STREQUAL "GTest_DIR-NOTFOUND") + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG 6910c9d9165801d8827d628cb72eb7ea9dd538c5 # v1.16.x + ) + # For Windows: Prevent overriding the parent project's compiler/linker settings + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(googletest) + include(GoogleTest) + endif() endif() add_executable(bitlib-tests) @@ -40,27 +42,20 @@ target_sources(bitlib-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/vector_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/inc/test_utils.hpp ) -target_include_directories(bitlib-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/inc) - if (BITLIB_MDSPAN) target_sources(bitlib-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/test-mdspan.cpp) endif() -# ASAN throws no matter what when using Google HWY, not sure why but need to fix -# if (NOT BITLIB_HWY) -# target_compile_options(bitlib-tests PUBLIC -fsanitize=address) -# target_link_options(bitlib-tests PUBLIC -fsanitize=address) -# endif() +target_include_directories(bitlib-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/inc) + # specify test-specific libraries target_link_libraries(bitlib-tests PRIVATE bitlib GTest::gtest GTest::gtest_main) -target_link_options(bitlib-tests PRIVATE -pthread -lgcov --coverage) if (NOT BITLIB_GTEST_REPEAT) set(BITLIB_GTEST_REPEAT 1) endif() -enable_testing() gtest_discover_tests( bitlib-tests EXTRA_ARGS --gtest_repeat=${BITLIB_GTEST_REPEAT}) From 152edeb9e0f3b2900136b4ebf5d35289cd94a9bf Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Sun, 11 May 2025 14:47:18 -0400 Subject: [PATCH 61/85] Common bit array base (#8) * Stab at creating useful base class for bit_array and bit_array_dynamic_extent * more churn * Move literal to its own file * Inline class functions * Common operator U() * bit_array_ref class * Move more functionality to CRTP base * Test slice modification * Proper slice syntax for vector * Bit Literal test * from range constructors (need std::from_range?) * range assignment operator= * Refine and improve coverage * Consolidate literal tests * Try to resolve ambiguous operator== * swap_ranges can throw, apparently * Close more coverage --- .../bitlib/bit-containers/bit-containers.hpp | 2 + include/bitlib/bit-containers/bit_array.hpp | 566 +++++------------- .../bitlib/bit-containers/bit_array_base.hpp | 203 +++++++ .../bit_array_dynamic_extent.hpp | 552 ++++++----------- .../bitlib/bit-containers/bit_array_ref.hpp | 201 +++++++ include/bitlib/bit-containers/bit_literal.hpp | 116 ++++ include/bitlib/bit-containers/bit_span.hpp | 12 +- include/bitlib/bit-containers/bit_vector.hpp | 6 +- include/bitlib/bit-iterator/bit_details.hpp | 2 + test/CMakeLists.txt | 2 + test/src/test-array.cpp | 86 ++- test/src/test-array_ref.cpp | 223 +++++++ test/src/test-literal.cpp | 97 +++ test/src/test-span.cpp | 2 +- test/src/vector_test.cpp | 2 +- 15 files changed, 1245 insertions(+), 827 deletions(-) create mode 100644 include/bitlib/bit-containers/bit_array_base.hpp create mode 100644 include/bitlib/bit-containers/bit_array_ref.hpp create mode 100644 include/bitlib/bit-containers/bit_literal.hpp create mode 100644 test/src/test-array_ref.cpp create mode 100644 test/src/test-literal.cpp diff --git a/include/bitlib/bit-containers/bit-containers.hpp b/include/bitlib/bit-containers/bit-containers.hpp index 921fcd5b..e4d0e6f8 100644 --- a/include/bitlib/bit-containers/bit-containers.hpp +++ b/include/bitlib/bit-containers/bit-containers.hpp @@ -9,6 +9,8 @@ // ============================== PREAMBLE ================================== // #include "bit_array.hpp" #include "bit_array_dynamic_extent.hpp" +#include "bit_array_ref.hpp" +#include "bit_literal.hpp" #include "bit_mdspan_accessor.hpp" #include "bit_span.hpp" #include "bit_vector.hpp" diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index 766a2aab..e1eab6e9 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -10,128 +10,210 @@ #define _BIT_ARRAY_HPP_INCLUDED // ========================================================================== // - - // ================================ PREAMBLE ================================ // // C++ standard library #include #include #include +#include // memcpy #include #include #include #include -#include // memcpy // Project sources #include "bitlib/bit-algorithms/bit_algorithm.hpp" +#include "bitlib/bit-containers/bit_array_base.hpp" #include "bitlib/bit-containers/bit_bitsof.hpp" #include "bitlib/bit-containers/bit_span.hpp" #include "bitlib/bit-iterator/bit.hpp" +#include "bitlib/bit_concepts.hpp" namespace bit { // ========================================================================== // +namespace detail { + +template +constexpr size_t Words() { return (N * bitsof() + bitsof() - 1) / bitsof(); } + +template +using bit_array_d_it = typename std::conditional, + bit_iterator()>::iterator>, + typename std::array()>::iterator>::type; + +template +using bit_array_d_cit = typename std::conditional, + bit_iterator()>::const_iterator>, + typename std::array()>::const_iterator>::type; +} // namespace detail + template , uint8_t, T>::type> -class bit_array { + typename W = std::conditional_t, uint8_t, T>> +class bit_array : public bit_array_base, T, W, detail::bit_array_d_it, detail::bit_array_d_cit> { public: + using base = bit_array_base, T, W, detail::bit_array_d_it, detail::bit_array_d_cit>; + using base::end; + using typename base::const_iterator; + using typename base::const_pointer; + using typename base::const_reference; + using typename base::difference_type; + using typename base::iterator; + using typename base::pointer; + using typename base::reference; + using typename base::size_type; + using typename base::value_type; + using typename base::word_type; + static constexpr std::size_t bits = N * bitsof(); - using word_type = W; - using value_type = T; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - using reference = typename std::conditional, bit_reference, T&>::type; - using const_reference = typename std::conditional, const bit_reference, const T&>::type; - using pointer = typename std::conditional, bit_pointer, T&>::type; - using const_pointer = const pointer; + + protected: + static constexpr std::size_t Words(std::size_t size_) { + return (size_ * bitsof() + bitsof() - 1) / bitsof(); + } private: - static constexpr std::size_t Words = (N * bitsof() + bitsof() - 1) / bitsof(); - static constexpr std::size_t AlignedWords = (((Words * sizeof(word_type) + static_cast(V) - 1) & ~(static_cast(V) - 1)) + sizeof(word_type) - 1) / sizeof(word_type); + static constexpr std::size_t Words_ = Words(N); + static constexpr std::size_t AlignedWords = (((Words_ * sizeof(word_type) + static_cast(V) - 1) & ~(static_cast(V) - 1)) + sizeof(word_type) - 1) / sizeof(word_type); alignas(static_cast(V)) std::array storage; public: - using iterator = typename std::conditional, - bit_iterator::iterator>, - typename std::array::iterator>::type; - using const_iterator = typename std::conditional, - bit_iterator::const_iterator>, - typename std::array::const_iterator>::type; /* * Constructors, copies and moves... */ - constexpr bit_array() noexcept; - constexpr bit_array(value_type bit_val); + constexpr bit_array() noexcept : storage{} {} + + constexpr bit_array(value_type bit_val) : storage{} { + this->fill(bit_val); + } + template - constexpr bit_array(const U& integral) requires (bitsof() <= bits); + constexpr bit_array(const U& integral) + requires(bitsof() <= bits) + { + std::memcpy(&storage[0], &integral, sizeof(integral)); + + bool sign_extend = false; + if constexpr (std::is_signed_v) { + sign_extend = (integral & (1 << (bitsof() - 1))) ? true : false; + } + if (sign_extend) { + for (auto it = begin() + bitsof(); it != end(); ++it) { + *it = bit1; + } + } else { + for (auto it = begin() + bitsof(); it != end(); ++it) { + *it = bit0; + } + } + } + constexpr bit_array(const bit_array& other) = default; - constexpr bit_array(const bit_array&& other) noexcept; + + constexpr bit_array(const bit_array&& other) noexcept + : storage(other.storage) {} + + constexpr bit_array(const bit_sized_range auto& other) { + if (other.size() != this->size()) [[unlikely]] { + throw std::invalid_argument("other bit_range contains an invalid number of bits for bit_array."); + } + ::bit::copy(other.begin(), other.end(), this->begin()); + }; + constexpr bit_array(const std::initializer_list init) - requires(!std::is_same_v); - constexpr bit_array(const std::initializer_list init); - constexpr bit_array(const std::initializer_list init); + requires(!std::is_same_v) + { + if (init.size() != bitsof(*this)) [[unlikely]] { + throw std::invalid_argument("initialize_list contains an invalid number of bits for bit_array."); + } + std::copy(init.begin(), init.end(), this->begin()); + } + + constexpr bit_array(const std::initializer_list init) { + if (init.size() != bitsof(*this)) [[unlikely]] { + throw std::invalid_argument("initialize_list contains an invalid number of bits for bit_array."); + } + std::copy(init.begin(), init.end(), this->begin()); + } + + constexpr bit_array(const std::initializer_list init) : storage{} { + // Make sure we handle the case where init.size() != Words + auto it = init.begin(); + for (size_type i = 0; i < std::min(AlignedWords, init.size()); ++i, ++it) { + storage[i] = *it; + } + } + constexpr bit_array(const std::string_view s) - requires(std::is_same_v); + requires(std::is_same_v) + { + if (bitsof(*this) != static_cast(std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))) [[unlikely]] { + throw std::invalid_argument("String contains an invalid number of bits for bit_array."); + }; + size_type i = 0; + for (char c : s) { + if (c == '0') { + begin()[i++] = bit0; + } else if (c == '1') { + begin()[i++] = bit1; + } + } + } ~bit_array() = default; /* * Assignment */ constexpr bit_array& operator=(const bit_array& other) = default; - constexpr bit_array& operator=(bit_array&& other) noexcept; - constexpr bool operator==(const bit_array& other) const noexcept; + constexpr bit_array& operator=(const bit_sized_range auto& other) { + if (other.size() != this->size()) [[unlikely]] { + throw std::invalid_argument("other bit_sized_range contains an invalid number of bits for bit_array."); + } + ::bit::copy(other.begin(), other.end(), this->begin()); + return *this; + }; - /* - * Element Access - */ - constexpr reference operator[](size_type pos); - constexpr const_reference operator[](size_type pos) const; - constexpr reference at(size_type pos); - constexpr const_reference at(size_type pos) const; - constexpr reference front(); - constexpr const_reference front() const; - constexpr reference back(); - constexpr const_reference back() const; - constexpr word_type* data() noexcept; - constexpr const word_type* data() const noexcept; + constexpr bit_array& operator=(bit_array&& other) noexcept { + std::copy(other.storage.begin(), other.storage.end(), storage.begin()); + return *this; + } + + constexpr word_type* data() noexcept { + return size() ? storage.data() : nullptr; + } + + constexpr const word_type* data() const noexcept { + return size() ? storage.data() : nullptr; + } /* * Iterators */ - constexpr iterator begin() noexcept; - constexpr iterator end() noexcept; - constexpr const_iterator begin() const noexcept; - constexpr const_iterator end() const noexcept; - constexpr const_iterator cbegin() const noexcept; - constexpr const_iterator cend() const noexcept; + constexpr iterator begin() noexcept { + return iterator(storage.begin()); + } + + constexpr const_iterator begin() const noexcept { + return const_iterator(storage.begin()); + } /* * Capacity */ - constexpr bool empty() const noexcept; - constexpr size_type size() const noexcept; - constexpr size_type max_size() const noexcept; - - /* - * Slice - */ - constexpr bit_span operator()(size_type offset, size_type right) const noexcept; - constexpr bit_span operator()(size_type offset, size_type right) noexcept; + constexpr size_type size() const noexcept { + return N; + } /* * Operations */ - constexpr void fill(value_type bit_val) noexcept; - constexpr void swap(bit_array& other) noexcept; - template - explicit constexpr operator U() const noexcept requires (bitsof() >= (bitsof() * N)); - constexpr std::string debug_string() const; - constexpr std::string debug_string(const_iterator first, const_iterator end) const; + constexpr void swap(bit_array& other) noexcept { + std::swap(this->storage, other.storage); + } }; static_assert(bit_range>, "bit_array does not satisfy bit_range concept!"); @@ -158,356 +240,6 @@ template bit_array(word_type*, std::size_t) -> bit_array; #endif -template -constexpr bit_array::bit_array() noexcept : storage{} {} - -template -constexpr bit_array::bit_array(bit_array::value_type bit_val) : storage{} { - fill(bit_val); -} - -template -template -constexpr bit_array::bit_array(const U& integral) requires (bitsof() <= bit_array::bits) { - std::memcpy(&storage[0], &integral, sizeof(integral)); - - bool sign_extend = false; - if constexpr (std::is_signed_v) { - sign_extend = (integral & (1 << (bitsof()-1))) ? true : false; - } - if (sign_extend) { - for (auto it = begin()+bitsof(); it != end(); ++it) { - *it = bit1; - } - } else { - for (auto it = begin()+bitsof(); it != end(); ++it) { - *it = bit0; - } - } -} - -template -constexpr void bit_array::fill(bit_array::value_type bit_val) noexcept { - std::fill(this->begin(), this->end(), bit_val); -} - -template -constexpr bit_array::bit_array(const std::initializer_list init) - requires(!std::is_same_v) -{ - if(init.size() != bitsof(*this)) [[unlikely]] { - throw std::invalid_argument("initialize_list contains an invalid number of bits for bit_array."); - } - std::copy(init.begin(), init.end(), this->begin()); -} - -template -constexpr bit_array::bit_array(const std::initializer_list init) { - if(init.size() != bitsof(*this)) [[unlikely]] { - throw std::invalid_argument("initialize_list contains an invalid number of bits for bit_array."); - } - std::copy(init.begin(), init.end(), this->begin()); -} - -template -constexpr bit_array::bit_array(const std::initializer_list init) : storage(init) { - static_assert(init.size() == Words); -} - -template -constexpr bit_array::bit_array(const std::string_view s) - requires(std::is_same_v) -{ - if (bitsof(*this) != static_cast(std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))) [[unlikely]] { - throw std::invalid_argument("String contains an invalid number of bits for bit_array."); - }; - size_type i = 0; - for (char c : s) { - if (c == '0') { - begin()[i++] = bit0; - } else if (c == '1') { - begin()[i++] = bit1; - } - } -} - -template -constexpr bit_array::bit_array(const bit_array&& other) noexcept - : storage(other.storage) {} - -template -constexpr bit_array& bit_array::operator=(bit_array&& other) noexcept { - std::copy(other.storage.begin(), other.storage.end(), storage.begin()); - return *this; -} +} // namespace bit -template -constexpr bool bit_array::operator==(const bit_array& other) const noexcept { - return equal(begin(), end(), other.begin()); -} - -template -constexpr void bit_array::swap(bit_array& other) noexcept { - std::swap(this->storage, other.storage); -} - -// -------------------------------------------------------------------------- // - /* - * Element Access - */ -template -constexpr typename bit_array::reference bit_array::operator[](size_type pos) { - return begin()[pos]; -} - -template -constexpr typename bit_array::const_reference bit_array::operator[](size_type pos) const { - return begin()[pos]; -} - -template -constexpr typename bit_array::reference bit_array::at(size_type pos) { - if (pos < size()) { - return begin()[pos]; - } else { - throw std::out_of_range("Position is out of range"); - } -} - -template -constexpr typename bit_array::const_reference bit_array::at(size_type pos) const { - if (pos < size()) { - return begin()[pos]; - } else { - throw std::out_of_range("Position is out of range"); - } -} - -template -constexpr typename bit_array::reference bit_array::front() { - return begin()[0]; -} - -template -constexpr typename bit_array::const_reference bit_array::front() const { - return begin()[0]; -} - -template -constexpr typename bit_array::reference bit_array::back() { - return begin()[size()-1]; -} - -template -constexpr typename bit_array::const_reference bit_array::back() const { - return begin()[size()-1]; -} - -template -constexpr typename bit_array::word_type* bit_array::data() noexcept { - return size() ? storage.data() : nullptr; -} - -template -constexpr const typename bit_array::word_type* bit_array::data() const noexcept { - return size() ? storage.data() : nullptr; -} - -// -------------------------------------------------------------------------- // - -template -constexpr typename bit_array::size_type bit_array::size() const noexcept { return N; } - -template -constexpr typename bit_array::size_type bit_array::max_size() const noexcept { return size(); } - -template -constexpr bool bit_array::empty() const noexcept { return 0 == size(); } - -// Iterators -// -------------------------------------------------------------------------- // - -template -constexpr typename bit_array::iterator bit_array::begin() noexcept { - return iterator(storage.begin()); -} - -template -constexpr typename bit_array::iterator bit_array::end() noexcept { - return begin() + size(); -} - -template -constexpr typename bit_array::const_iterator bit_array::begin() const noexcept { - return const_iterator(storage.begin()); -} - -template -constexpr typename bit_array::const_iterator bit_array::end() const noexcept { - return const_iterator(storage.begin()) + size(); -} - -template -constexpr typename bit_array::const_iterator bit_array::cbegin() const noexcept { - return const_iterator(storage.begin()); -} - -template -constexpr typename bit_array::const_iterator bit_array::cend() const noexcept { - return const_iterator(storage.begin()) + size(); -} - -// -------------------------------------------------------------------------- // - -template -constexpr bit_span bit_array::operator()(size_type begin, size_type end) const noexcept { - return bit_span(&this->at(begin), end - begin); -} - -template -constexpr bit_span bit_array::operator()(size_type begin, size_type end) noexcept { - return bit_span(&this->at(begin), end - begin); -} - -template -template -constexpr bit_array::operator U() const noexcept - requires(bitsof() >= (bitsof() * N)) -{ - U result; - std::memcpy(&result, &storage[0], sizeof(U)); - - if constexpr (std::is_signed_v && begin()[size()-1]) { - for (size_type i = size(); i < bitsof(); ++i) { - result |= (static_cast(1) << i); - } - } else { - for (size_type i = size(); i < bitsof(); ++i) { - result &= ~(static_cast(1) << i); - } - } - return result; -} - -template -constexpr std::string bit_array::debug_string() const { - return debug_string(begin(), end()); -} - -template -constexpr std::string bit_array::debug_string( - bit_array::const_iterator first, - bit_array::const_iterator end) const { - auto digits = bitsof(); - std::string ret = ""; - auto position = 0; - for (auto it = first; it != end; ++it) { - if (position % digits == 0 && position != 0) { - ret += " "; - } else if (position % 8 == 0 && position != 0) { - ret += '.'; - } - ret += *it == bit1 ? '1' : '0'; - ++position; - } - return ret; -} - -// ========================================================================== // -template -constexpr void _parameter_pack_base_bits_prefix_len(size_t& base, size_t& bits, size_t& prefix_len, uint64_t& num) { - if ('\'' == Bit) { - if (0 != bits) { - return; //skip. Should we, though? It seems like it will be confusing - } - bits = num; - num = 0; - ++prefix_len; - if (0 == base) { - base = 10; - } - } else if (0 == base) { - if (Bit == 'b' || Bit == 'B') { - base = 2; - num = 0; - } else if (Bit == 'x' || Bit == 'X') { - base = 16; - num = 0; - } else if (Bit == 'd' || Bit == 'D') { - base = 10; - num = 0; - } else { - num = (num * 10) + (Bit - '0'); - } - ++prefix_len; - } else { - uint8_t decimal; - switch (base) { - case 2: - decimal = static_cast(Bit - '0'); - break; - case 10: - decimal = static_cast(Bit - '0'); - break; - case 16: - decimal = static_cast(Bit - '0'); - if (Bit >= 'a' && Bit <= 'f') { - decimal = static_cast(Bit - 'a') + 10u; - } - if (Bit >= 'A' && Bit <= 'F') { - decimal = static_cast(Bit - 'A') + 10u; - } - break; - } - num = num * base + decimal; - } -} - -template -constexpr void _parameter_pack_base_bits_prefix_len(size_t& base, size_t& bits, size_t& prefix_len, uint64_t& num) - requires(sizeof...(Bits) > 0) -{ - _parameter_pack_base_bits_prefix_len(base, bits, prefix_len, num); - _parameter_pack_base_bits_prefix_len(base, bits, prefix_len, num); -} - -template -constexpr std::pair _parameter_pack_decode_prefixed_num() { - size_t base = 0; - size_t bits = 0; - size_t prefix_len = 0; - uint64_t num = 0; - _parameter_pack_base_bits_prefix_len(base, bits, prefix_len, num); - size_t digits = (sizeof...(Bits) - prefix_len); - if (0 == base) { - base = 10; - digits = prefix_len; - prefix_len = 0; - } - if (0 == bits) { - if (base == 2) { - bits = digits; - } else if (base == 10) { - bits = std::bit_width(num); - } else { - bits = 4 * digits; - } - } - return {bits, num}; -} - -} // namespace bit - -template -constexpr bit::bit_array().first> operator""_b() { - bit::bit_array().first> arr{}; - auto [bits, num] = bit::_parameter_pack_decode_prefixed_num(); - for (int i = 0; i < arr.size(); i++) { - arr[i] = (num & 0x1) ? bit::bit1 : bit::bit0; - num >>= 1; - } - return arr; -} - -#endif // _BIT_ARRAY_HPP_INCLUDED -// ========================================================================== // +#endif // _BIT_ARRAY_HPP_INCLUDED \ No newline at end of file diff --git a/include/bitlib/bit-containers/bit_array_base.hpp b/include/bitlib/bit-containers/bit_array_base.hpp new file mode 100644 index 00000000..c8fd0f3e --- /dev/null +++ b/include/bitlib/bit-containers/bit_array_base.hpp @@ -0,0 +1,203 @@ +// ================================= BIT_ARRAY_BASE =================================== // +// Project: The Experimental Bit Algorithms Library +// \file bit_array_base.hpp +// Description: Base implementation for bit_array variants +// Creator: Vincent Reverdy +// Contributor: Peter McLean [2025] +// License: BSD 3-Clause License +// ========================================================================== // +#ifndef _BIT_ARRAY_BASE_HPP_INCLUDED +#define _BIT_ARRAY_BASE_HPP_INCLUDED +// ========================================================================== // + +// ================================ PREAMBLE ================================ // +// C++ standard library +#include +#include +#include +#include // memcpy +#include +#include +#include +#include + +// Project sources +#include "bitlib/bit-algorithms/bit_algorithm.hpp" +#include "bitlib/bit-containers/bit_bitsof.hpp" +#include "bitlib/bit-containers/bit_span.hpp" +#include "bitlib/bit-iterator/bit.hpp" + +namespace bit { + +template +class bit_array_ref; +// ========================================================================== // + +/** + * @brief Base class template for bit_array implementations + * + * This is a CRTP (Curiously Recurring Template Pattern) base class that provides + * common functionality for bit_array variants. + * + * @tparam Derived The derived class (CRTP pattern) + * @tparam T The value type (typically bit_value) + * @tparam W The word type used for storage + * @tparam It The iterator type for the derived class + * @tparam CIt The const_iterator type for the derived class + */ +template +class bit_array_base { + public: + using word_type = W; + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = typename std::conditional, bit_reference, T&>::type; + using const_reference = typename std::conditional, const bit_reference, const T&>::type; + using pointer = typename std::conditional, bit_pointer, T&>::type; + using const_pointer = const pointer; + using iterator = It; + using const_iterator = CIt; + + // Element access + constexpr reference operator[](size_type pos) { + return derived().begin()[pos]; + } + + constexpr const_reference operator[](size_type pos) const { + return derived().begin()[pos]; + } + + constexpr reference at(size_type pos) { + if (pos < derived().size()) { + return derived().begin()[pos]; + } else { + throw std::out_of_range("Position is out of range"); + } + } + + constexpr const_reference at(size_type pos) const { + if (pos < derived().size()) { + return derived().begin()[pos]; + } else { + throw std::out_of_range("Position is out of range"); + } + } + + constexpr reference front() { + return derived().begin()[0]; + } + + constexpr const_reference front() const { + return derived().begin()[0]; + } + + constexpr reference back() { + return derived().begin()[derived().size() - 1]; + } + + constexpr const_reference back() const { + return derived().begin()[derived().size() - 1]; + } + + constexpr iterator end() noexcept { + return derived().begin() + derived().size(); + } + + constexpr const_iterator end() const noexcept { + return const_iterator(derived().begin()) + derived().size(); + } + + constexpr const_iterator cbegin() const noexcept { + return const_iterator(derived().begin()); + } + + constexpr const_iterator cend() const noexcept { + return const_iterator(derived().end()); + } + + // Capacity + constexpr bool empty() const noexcept { + return 0 == derived().size(); + } + + constexpr size_type max_size() const noexcept { + return derived().size(); + } + + // String representation + constexpr std::string debug_string() const { + return debug_string(derived().begin(), derived().end()); + } + + constexpr std::string debug_string(const_iterator first, const_iterator last) const { + std::string ret = ""; + auto position = 0; + for (auto it = first; it != last; ++it) { + if (position % bitsof() == 0 && position != 0) { + ret += " "; + } else if (position % 8 == 0 && position != 0) { + ret += '.'; + } + ret += *it == bit1 ? '1' : '0'; + ++position; + } + return ret; + } + + /** + * @brief Slice operations - returns a bit_array_ref + */ + constexpr auto operator()(size_type offset, size_type right) const noexcept { + return bit_array_ref(&this->at(offset), right - offset); + } + + /** + * @brief Slice operations - returns a bit_array_ref + */ + constexpr auto operator()(size_type offset, size_type right) noexcept { + return bit_array_ref(&this->at(offset), right - offset); + } + + // Common operations + constexpr void fill(value_type bit_val) noexcept { + std::fill(derived().begin(), derived().end(), bit_val); + } + + /** + * @brief Explicit conversion to integral types + */ + template + explicit constexpr operator U() const noexcept { + assert(derived().size() <= bitsof()); + U integral; + bit_span()> integral_ref(reinterpret_cast(&integral)); + copy(derived().begin(), derived().begin() + bitsof(), integral_ref.begin()); + if constexpr (std::is_signed_v) { + ::bit::fill(integral_ref.begin() + derived().size(), integral_ref.end(), integral_ref[bitsof() - 1]); + } else { + ::bit::fill(integral_ref.begin() + derived().size(), integral_ref.end(), bit0); + } + return integral; + } + + protected: + constexpr Derived& derived() noexcept { + return static_cast(*this); + } + + constexpr const Derived& derived() const noexcept { + return static_cast(*this); + } +}; +constexpr bool operator==(const bit_sized_range auto& lhs, const bit_sized_range auto& rhs) { + if (lhs.size() != rhs.size()) { + return false; + } + return ::bit::equal(lhs.begin(), lhs.end(), rhs.begin()); +} + +} // namespace bit + +#endif // _BIT_ARRAY_BASE_HPP_INCLUDED + // ========================================================================== // \ No newline at end of file diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index 88d4fab8..23c0acac 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -20,29 +20,39 @@ #include "bitlib/bit-algorithms/bit_algorithm.hpp" #include "bitlib/bit-containers/bit_array.hpp" +#include "bitlib/bit-containers/bit_array_base.hpp" #include "bitlib/bit-containers/bit_bitsof.hpp" #include "bitlib/bit-containers/bit_span.hpp" #include "bitlib/bit-iterator/bit.hpp" +#include "bitlib/bit_concepts.hpp" namespace bit { - -template -class bit_array { +namespace detail { +template +using bit_array_it = typename std::conditional, + bit_iterator, + word_type*>::type; +template +using bit_array_cit = typename std::conditional, + bit_iterator, + const word_type*>::type; +} // namespace detail +template +class bit_array + : public bit_array_base, T, W, detail::bit_array_it, detail::bit_array_cit> { public: - using word_type = W; - using value_type = T; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - using reference = typename std::conditional, bit_reference, T&>::type; - using const_reference = typename std::conditional, const bit_reference, const T&>::type; - using pointer = typename std::conditional, bit_pointer, T&>::type; - using const_pointer = const pointer; - using iterator = typename std::conditional, - bit_iterator, - word_type*>::type; - using const_iterator = typename std::conditional, - bit_iterator, - const word_type*>::type; + using base = bit_array_base, T, W, detail::bit_array_it, detail::bit_array_cit>; + using base::end; + using typename base::const_iterator; + using typename base::const_pointer; + using typename base::const_reference; + using typename base::difference_type; + using typename base::iterator; + using typename base::pointer; + using typename base::reference; + using typename base::size_type; + using typename base::value_type; + using typename base::word_type; private: struct deleter { @@ -57,9 +67,11 @@ class bit_array { const size_type m_size; const std::unique_ptr storage; + static constexpr size_type Words(size_type N) { return (N * bitsof() + bitsof() - 1) / bitsof(); }; + static constexpr size_t AlignedBytes(size_t N) { return (Words(N) * sizeof(word_type) + static_cast(V) - 1) & ~(static_cast(V) - 1); }; @@ -69,402 +81,204 @@ class bit_array { * Constructors, copies and moves... */ bit_array() = delete; - constexpr bit_array(const size_type size); - template - constexpr bit_array(const size_type size, const U& integral); - constexpr bit_array(const size_type size, const value_type bit_val); - constexpr bit_array(const bit_array& other); - constexpr bit_array(const bit_array&& other); - constexpr bit_array(const std::initializer_list init) - requires(!std::is_same_v); -#if 0 - constexpr bit_array(const std::initializer_list init); -#endif - constexpr bit_array(const std::initializer_list init); - constexpr bit_array(const std::string_view s) - requires(std::is_same_v); - - ~bit_array() = default; - /* - * Assignment - */ - constexpr bit_array& operator=(const bit_array& other); - constexpr bit_array& operator=(bit_array&& other); - - constexpr bool operator==(const bit_array& other) const noexcept; - - /* - * Element Access - */ - constexpr reference operator[](size_type pos); - constexpr const_reference operator[](size_type pos) const; - constexpr reference at(size_type pos); - constexpr const_reference at(size_type pos) const; - constexpr reference front(); - constexpr const_reference front() const; - constexpr reference back(); - constexpr const_reference back() const; - constexpr word_type* data() noexcept; - constexpr const word_type* data() const noexcept; - - /* - * Iterators - */ - constexpr iterator begin() noexcept; - constexpr iterator end() noexcept; - constexpr const_iterator begin() const noexcept; - constexpr const_iterator end() const noexcept; - constexpr const_iterator cbegin() const noexcept; - constexpr const_iterator cend() const noexcept; - - /* - * Capacity - */ - constexpr bool empty() const noexcept; - constexpr size_type size() const noexcept; - constexpr size_type max_size() const noexcept; - /* - * Slice - */ - constexpr bit_span operator()(size_type offset, size_type right) const noexcept; + constexpr bit_array(const size_type size) + : m_size(size), + storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { + //std::uninitialized_fill_n(this->begin(), Words(m_size), word_type()); + for (size_type i = 0; i < Words(m_size); ++i) { + new (storage.get() + i) word_type(); + } + } - /* - * Operations - */ - constexpr void fill(value_type bit_val) noexcept; - constexpr void swap(bit_array& other) noexcept; template - explicit constexpr operator U() const noexcept; - //constexpr std::synth-three-way-result operator<=>() const noexcept; - constexpr std::string debug_string() const; - constexpr std::string debug_string(const_iterator first, const_iterator end) const; -}; - -static_assert(bit_range>, "bit_array<> does not satisfy bit_contiguous_range concept!"); -static_assert(bit_sized_range>, "bit_array<> does not satisfy bit_contiguous_sized_range concept!"); -#ifdef CONTIGUOUS_RANGE -static_assert(bit_contiguous_range>, "bit_array<> does not satisfy bit_contiguous_range concept!"); -static_assert(bit_contiguous_sized_range>, "bit_array<> does not satisfy bit_contiguous_sized_range concept!"); -#endif - -/* Can't imagine any use for a default constructor -template -constexpr bit_array::bit_array() noexcept - : m_size(0), storage(std::unique_ptr(nullptr, &std::free)) { -} -*/ - -template -template -constexpr bit_array::bit_array(const size_type size, const U& integral) - : m_size(size), - storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { - assert (bitsof() <= size); - for (size_type i = 0; i < Words(m_size); ++i) { - new (storage.get() + i) word_type(); - } - std::memcpy(&storage[0], &integral, sizeof(integral)); - bool sign_extend = false; - if constexpr (std::is_signed_v) { - sign_extend = (integral & (1 << (bitsof()-1))) ? true : false; - } - if (sign_extend) { - for (auto it = begin()+bitsof(); it != end(); ++it) { - *it = bit1; + constexpr bit_array(const size_type size, const U& integral) + : m_size(size), + storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { + assert(bitsof() <= size); + for (size_type i = 0; i < Words(m_size); ++i) { + new (storage.get() + i) word_type(); } - } else { - for (auto it = begin()+bitsof(); it != end(); ++it) { - *it = bit0; + std::memcpy(storage.get(), &integral, sizeof(integral)); + bool sign_extend = false; + if constexpr (std::is_signed_v) { + sign_extend = (integral & (1 << (bitsof() - 1))) ? true : false; + } + if (sign_extend) { + for (auto it = begin() + bitsof(); it != end(); ++it) { + *it = bit1; + } + } else { + for (auto it = begin() + bitsof(); it != end(); ++it) { + *it = bit0; + } } } -} -template -constexpr bit_array::bit_array(const size_type size) - : m_size(size), - storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { - //std::uninitialized_fill_n(this->begin(), Words(m_size), word_type()); - for (size_type i = 0; i < Words(m_size); ++i) { - new (storage.get() + i) word_type(); + constexpr bit_array(const size_type size, const value_type bit_val) + : m_size(size), + storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { + if constexpr (std::is_same::value) { + for (size_type i = 0; i < Words(m_size); ++i) { + new (storage.get() + i) word_type(bit_val); + } + } else { + for (size_type i = 0; i < Words(m_size); ++i) { + new (storage.get() + i) word_type(); + } + this->fill(bit_val); + } } -} -template -constexpr bit_array::bit_array(const size_type size, const value_type bit_val) - : m_size(size), - storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { - if constexpr (std::is_same::value) { + constexpr bit_array(const bit_array& other) + : m_size(other.size()), + storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { for (size_type i = 0; i < Words(m_size); ++i) { - new (storage.get() + i) word_type(bit_val); + new (storage.get() + i) word_type(*(other.storage.get() + i)); } - } else { - for (size_type i = 0; i < Words(m_size); ++i) { - new (storage.get() + i) word_type(); - } - fill(bit_val); } -} -template -constexpr bit_array::bit_array(const bit_array& other) - : m_size(other.size()), - storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { - for (size_type i = 0; i < Words(m_size); ++i) { - new (storage.get() + i) word_type(other.storage[i]); + constexpr bit_array(const bit_sized_range auto& other) + : m_size(other.size()), + storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { + ::bit::copy(other.begin(), other.end(), this->begin()); } -} -template -constexpr bit_array::bit_array(const bit_array&& other) - : m_size(other.size()), - storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { - for (size_type i = 0; i < Words(m_size); ++i) { - new (storage.get() + i) word_type(other.storage[i]); + constexpr bit_array(const bit_array&& other) + : m_size(other.size()), + storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { + for (size_type i = 0; i < Words(m_size); ++i) { + new (storage.get() + i) word_type(*(other.storage.get() + i)); + } } -} -template -constexpr bit_array::bit_array(const std::initializer_list init) - requires(!std::is_same_v) - : m_size(init.size()), - storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { - std::copy(init.begin(), init.end(), this->begin()); -} + constexpr bit_array(const std::initializer_list init) + requires(!std::is_same_v) + : m_size(init.size()), + storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { + for (size_type i = 0; i < Words(m_size); ++i) { + new (storage.get() + i) word_type(); + } + std::copy(init.begin(), init.end(), this->begin()); + } #if 0 -No known conversion from bool to bit_value -bit_value has explicit constructor from bool to bit_value so this doesnt work -constexpr bit_array::bit_array(const std::initializer_list init) - : storage(std::make_unique(Words(init.size()))), - m_size(init.size()) { - std::copy(init.begin(), init.end(), this->begin()); -} + No known conversion from bool to bit_value + bit_value has explicit constructor from bool to bit_value so this doesnt work + constexpr bit_array::bit_array(const std::initializer_list init) + : storage(std::make_unique(Words(init.size()))), + m_size(init.size()) { + std::copy(init.begin(), init.end(), this->begin()); + } #endif -template -constexpr bit_array::bit_array(const std::initializer_list init) - : m_size(bitsof() * init.size()), - storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { - size_type i = 0; - auto&& it = init.begin(); - for (; i < Words(m_size); ++i, ++it) { - new (storage.get() + i) word_type(*it); + constexpr bit_array(const std::initializer_list init) + : m_size(bitsof() * init.size()), + storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { + size_type i = 0; + auto it = init.begin(); + for (; i < Words(m_size) && it != init.end(); ++i, ++it) { + new (storage.get() + i) word_type(*it); + } } -} -template -constexpr bit_array::bit_array(const std::string_view s) - requires(std::is_same_v) - : m_size((std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))), - storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { - size_type i = 0; - for (char c : s) { - if (c == '0') { - begin()[i++] = bit0; - } else if (c == '1') { - begin()[i++] = bit1; + constexpr bit_array(const std::string_view s) + requires(std::is_same_v) + : m_size((std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))), + storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { + for (size_type i = 0; i < Words(m_size); ++i) { + new (storage.get() + i) word_type(); + } + size_type i = 0; + for (char c : s) { + if (c == '0') { + begin()[i++] = bit0; + } else if (c == '1') { + begin()[i++] = bit1; + } } } -} -template -constexpr bit_array& bit_array::operator=(const bit_array& other) { - if (nullptr == storage.get() || m_size != other.m_size) { - throw std::invalid_argument("Cannot reassign bit_array size"); - } - std::copy(other.begin(), other.end(), this->begin()); - return *this; -} + ~bit_array() = default; -template -constexpr bit_array& bit_array::operator=(bit_array&& other) { - if (nullptr == storage.get() || m_size != other.m_size) { - throw std::invalid_argument("Cannot reassign bit_array size"); + /* + * Assignment + */ + constexpr bit_array& operator=(const bit_array& other) { + if (nullptr == storage.get() || m_size != other.m_size) { + throw std::invalid_argument("Cannot reassign bit_array size"); + } + std::copy(other.begin(), other.end(), this->begin()); + return *this; } - std::copy(other.begin(), other.end(), this->begin()); - return *this; -} -template -constexpr bool bit_array::operator==(const bit_array& other) const noexcept { - return equal(begin(), end(), other.begin()); -} + constexpr bit_array& operator=(const bit_sized_range auto& other) { + if (other.size() != this->size()) [[unlikely]] { + throw std::invalid_argument("other bit_range contains an invalid number of bits for bit_array."); + } + ::bit::copy(other.begin(), other.end(), this->begin()); + return *this; + }; -template -constexpr void bit_array::swap(bit_array& other) noexcept { - assert(this->m_size == other.m_size); - W* it1 = this->storage.get(); - W* it2 = other.storage.get(); - for (size_type i = 0; i < Words(this->m_size); i++, it1++, it2++) { - std::swap(*it1, *it2); + constexpr bit_array& operator=(bit_array&& other) { + if (nullptr == storage.get() || m_size != other.m_size) { + throw std::invalid_argument("Cannot reassign bit_array size"); + } + std::copy(other.begin(), other.end(), this->begin()); + return *this; } -} -template -constexpr void bit_array::fill(value_type bit_val) noexcept { - std::fill(this->begin(), this->end(), bit_val); -} - -// -------------------------------------------------------------------------- // -/* - * Element Access - */ -template -constexpr typename bit_array::reference bit_array::operator[](size_type pos) { - return begin()[pos]; -} - -template -constexpr typename bit_array::const_reference bit_array::operator[](size_type pos) const { - return begin()[pos]; -} - -template -constexpr typename bit_array::reference bit_array::at(size_type pos) { - if (pos < size()) { - return begin()[pos]; - } else { - throw std::out_of_range("Position is out of range"); + /* + * Element Access + */ + constexpr word_type* data() noexcept { + return size() ? storage.get() : nullptr; } -} -template -constexpr typename bit_array::const_reference bit_array::at(size_type pos) const { - if (pos < size()) { - return begin()[pos]; - } else { - throw std::out_of_range("Position is out of range"); + constexpr const word_type* data() const noexcept { + return size() ? storage.get() : nullptr; } -} - -template -constexpr typename bit_array::reference bit_array::front() { - return begin()[0]; -} - -template -constexpr typename bit_array::const_reference bit_array::front() const { - return begin()[0]; -} - -template -constexpr typename bit_array::reference bit_array::back() { - return begin()[size() - 1]; -} - -template -constexpr typename bit_array::const_reference bit_array::back() const { - return begin()[size() - 1]; -} - -template -constexpr typename bit_array::word_type* bit_array::data() noexcept { - return size() ? storage.get() : nullptr; -} - -template -constexpr const typename bit_array::word_type* bit_array::data() const noexcept { - return size() ? storage.get() : nullptr; -} - -// -------------------------------------------------------------------------- // - -template -constexpr typename bit_array::size_type bit_array::size() const noexcept { return m_size; } - -template -constexpr typename bit_array::size_type bit_array::max_size() const noexcept { return size(); } - -template -constexpr bool bit_array::empty() const noexcept { return 0 == size(); } -// Iterators -// -------------------------------------------------------------------------- // - -template -constexpr typename bit_array::iterator bit_array::begin() noexcept { - return iterator(storage.get()); -} - -template -constexpr typename bit_array::iterator bit_array::end() noexcept { - return begin() + size(); -} - -template -constexpr typename bit_array::const_iterator bit_array::begin() const noexcept { - return const_iterator(storage.get()); -} - -template -constexpr typename bit_array::const_iterator bit_array::end() const noexcept { - return const_iterator(storage.get()) + size(); -} - -template -constexpr typename bit_array::const_iterator bit_array::cbegin() const noexcept { - return const_iterator(storage.get()); -} - -template -constexpr typename bit_array::const_iterator bit_array::cend() const noexcept { - return const_iterator(storage.get()) + size(); -} - -// -------------------------------------------------------------------------- // - -template -constexpr bit_span bit_array::operator()(size_type begin, size_type end) const noexcept { - return bit_span(&this->at(begin), end - begin); -} - -template -template -constexpr bit_array::operator U() const noexcept { - assert(size() <= bitsof()); - U result; - std::memcpy(&result, &storage[0], sizeof(U)); + /* + * Iterators + */ + constexpr iterator begin() noexcept { + return iterator(storage.get()); + } - if constexpr (std::is_signed_v && begin()[size()-1]) { - for (size_type i = size(); i < bitsof(); ++i) { - result |= (static_cast(1) << i); - } - } else { - for (size_type i = size(); i < bitsof(); ++i) { - result &= ~(static_cast(1) << i); - } + constexpr const_iterator begin() const noexcept { + return const_iterator(storage.get()); } - return result; -} + /* + * Capacity + */ + constexpr size_type size() const noexcept { + return m_size; + } -template -constexpr std::string bit_array::debug_string() const { - return debug_string(begin(), end()); -} -template -constexpr std::string bit_array::debug_string( - bit_array::const_iterator first, - bit_array::const_iterator end) const { - auto digits = bitsof(); - std::string ret = ""; - auto position = 0; - for (auto it = first; it != end; ++it) { - if (position % digits == 0 && position != 0) { - ret += " "; - } else if (position % 8 == 0 && position != 0) { - ret += '.'; - } - ret += *it == bit1 ? '1' : '0'; - ++position; + /* + * Operations + */ + constexpr void swap(bit_array& other) noexcept { + assert(this->m_size == other.m_size); + // Cannot just swap storage as it is const. + W* it1 = this->storage.get(); + W* it2 = other.storage.get(); + for (size_type i = 0; i < Words(this->m_size); i++, it1++, it2++) { + std::swap(*it1, *it2); + } } - return ret; -} +}; +static_assert(bit_range>, "bit_array<> does not satisfy bit_contiguous_range concept!"); +static_assert(bit_sized_range>, "bit_array<> does not satisfy bit_contiguous_sized_range concept!"); +#ifdef CONTIGUOUS_RANGE +static_assert(bit_contiguous_range>, "bit_array<> does not satisfy bit_contiguous_range concept!"); +static_assert(bit_contiguous_sized_range>, "bit_array<> does not satisfy bit_contiguous_sized_range concept!"); +#endif // ========================================================================== // } // namespace bit #endif // _BIT_ARRAY_DYNAMIC_EXTENT_HPP_INCLUDED -// ========================================================================== // diff --git a/include/bitlib/bit-containers/bit_array_ref.hpp b/include/bitlib/bit-containers/bit_array_ref.hpp new file mode 100644 index 00000000..be86279f --- /dev/null +++ b/include/bitlib/bit-containers/bit_array_ref.hpp @@ -0,0 +1,201 @@ +// ================================= BIT_ARRAY_REF =================================== // +// Project: The Experimental Bit Algorithms Library +// \file bit_array_ref.hpp +// Description: Implementation of bit_array_ref +// Creator: Vincent Reverdy +// Contributor: Peter McLean [2025] +// License: BSD 3-Clause License +// ========================================================================== // +#ifndef _BIT_ARRAY_REF_HPP_INCLUDED +#define _BIT_ARRAY_REF_HPP_INCLUDED + +#include +#include +#include // memcpy +#include +#include +#include +#include // std::dynamic_extent +#include + +#include "bitlib/bit-algorithms/bit_algorithm.hpp" +#include "bitlib/bit-containers/bit_array_base.hpp" +#include "bitlib/bit-containers/bit_bitsof.hpp" +#include "bitlib/bit-containers/bit_span.hpp" +#include "bitlib/bit-iterator/bit.hpp" +#include "bitlib/bit_concepts.hpp" + +namespace bit { + +/** + * @brief A non-owning reference to a bit array + * + * Similar to bit_array_dynamic_extent but does not allocate or deallocate memory. + * The pointer and size are const class members and cannot be re-bound. + * Assignment operations always copy content and can't rebind the pointer/size. + * + * @tparam T The value type (typically bit_value) + * @tparam W The word type used for storage + */ +template +class bit_array_ref + : public bit_array_base, T, W, bit_iterator, bit_iterator> { + public: + using base = bit_array_base, T, W, bit_iterator, bit_iterator>; + using base::end; + using typename base::const_iterator; + using typename base::const_pointer; + using typename base::const_reference; + using typename base::difference_type; + using typename base::iterator; + using typename base::pointer; + using typename base::reference; + using typename base::size_type; + using typename base::value_type; + using typename base::word_type; + + private: + const bit_pointer m_storage; + const size_type m_size; + + public: + // Constructors + bit_array_ref() = delete; + + /** + * @brief Constructs a non-owning reference to a bit array + * + * @param storage Pointer to the storage + * @param size Number of bits + */ + constexpr bit_array_ref(word_type* storage, size_type size) + : m_storage(storage), + m_size(size) { + } + + /** + * @brief Constructs a non-owning reference to a bit array using a bit_pointer + * + * @param storage bit_pointer to the storage + * @param size Number of bits + */ + constexpr bit_array_ref(bit_pointer storage, size_type size) + : m_storage(storage), + m_size(size) { + } + + /** + * @brief Constructs a non-owning reference to a bit array from a bit_sized_range + * + * @param other bit_sized_range + */ + constexpr bit_array_ref(bit_range auto& other, size_type size) + : m_storage(&(*other.begin())), + m_size(size) { + assert(size <= (other.end() - other.begin())); + } + + /** + * @brief Constructs a non-owning reference to a bit array from a bit_sized_range + * + * @param other bit_sized_range + */ + constexpr bit_array_ref(const bit_range auto& other, size_type size) + requires(std::is_const_v) + : m_storage(&(*other.begin())), + m_size(size) { + assert(size <= (other.end() - other.begin())); + } + + /** + * @brief Copy constructor + */ + constexpr bit_array_ref(const bit_array_ref& other) = default; + + /** + * @brief Move constructor + */ + constexpr bit_array_ref(bit_array_ref&& other) = default; + + /** + * @brief Range Assignment operator - copies content but doesn't rebind + */ + constexpr bit_array_ref& operator=(const bit_sized_range auto& other) { + if (m_size != other.size()) { + throw std::invalid_argument("Cannot assign from bit_array_ref of different size"); + } + ::bit::copy(other.begin(), other.end(), this->begin()); + return *this; + } + + /** + * @brief Copy Assignment operator - copies content but doesn't rebind + */ + constexpr bit_array_ref& operator=(const bit_array_ref& other) { + if (this != &other) { + if (m_size != other.m_size) { + throw std::invalid_argument("Cannot assign from bit_array_ref of different size"); + } + ::bit::copy(other.begin(), other.end(), this->begin()); + } + return *this; + } + + /** + * @brief Move assignment operator - copies content but doesn't rebind + */ + constexpr bit_array_ref& operator=(bit_array_ref&& other) { + if (this != &other) { + if (m_size != other.size()) { + throw std::invalid_argument("Cannot assign from bit_array_ref of different size"); + } + ::bit::copy(other.begin(), other.end(), this->begin()); + } + return *this; + } + + /** + * @brief No destructor needed as we don't own the memory + */ + ~bit_array_ref() = default; + + /* + * Iterators + */ + constexpr iterator begin() noexcept { + return iterator(m_storage); + } + + constexpr const_iterator begin() const noexcept { + return const_iterator(m_storage); + } + + /* + * Capacity + */ + constexpr size_type size() const noexcept { + return m_size; + } + + /* + * Operations + */ + constexpr void swap(bit_array_ref& other) { + if (m_size != other.m_size) { + throw std::invalid_argument("Cannot swap bit_array_ref of different sizes"); + } + swap_ranges(begin(), end(), other.begin()); + } +}; + +static_assert(bit_range>, "bit_array_ref<> does not satisfy bit_range concept!"); +static_assert(bit_sized_range>, "bit_array_ref<> does not satisfy bit_sized_range concept!"); +#ifdef CONTIGUOUS_RANGE +static_assert(bit_contiguous_range>, "bit_array_ref<> does not satisfy bit_contiguous_range concept!"); +static_assert(bit_contiguous_sized_range>, "bit_array_ref<> does not satisfy bit_contiguous_sized_range concept!"); +#endif + +// ========================================================================== // +} // namespace bit + +#endif // _BIT_ARRAY_REF_HPP_INCLUDED \ No newline at end of file diff --git a/include/bitlib/bit-containers/bit_literal.hpp b/include/bitlib/bit-containers/bit_literal.hpp new file mode 100644 index 00000000..44a0cccb --- /dev/null +++ b/include/bitlib/bit-containers/bit_literal.hpp @@ -0,0 +1,116 @@ +// ================================= BIT_ARRAY =================================== // +// Project: The Experimental Bit Algorithms Library +// \file bit_literal.hpp +// Description: Implementation of bit_array user defined literal +// Creator: Vincent Reverdy +// Contributor: Peter McLean [2025] +// License: BSD 3-Clause License +// ========================================================================== // +#ifndef _BIT_LITERAL_HPP_INCLUDED +#define _BIT_LITERAL_HPP_INCLUDED + +// ================================ PREAMBLE ================================ // +// C++ standard library +#include + +// Project sources +#include "bitlib/bit-containers/bit_array.hpp" + +namespace bit { + +// ========================================================================== // +template +constexpr void _parameter_pack_base_bits_prefix_len(size_t& base, size_t& bits, size_t& prefix_len, uint64_t& num) { + if ('\'' == Bit) { + if (0 != bits) { + // This happens when there are more than one apostrophe in the sequence. + // We ignore this apostrophe, it should be part of the 'data', not the width. + return; + } + bits = num; + num = 0; + ++prefix_len; + if (0 == base) { + base = 10; + } + } else if (0 == base) { + if (Bit == 'b' || Bit == 'B') { + base = 2; + num = 0; + } else if (Bit == 'x' || Bit == 'X') { + base = 16; + num = 0; + } else { + num = (num * 10) + (Bit - '0'); + } + ++prefix_len; + } else { + uint8_t decimal; + switch (base) { + case 2: + decimal = static_cast(Bit - '0'); + break; + case 10: + decimal = static_cast(Bit - '0'); + break; + case 16: + decimal = static_cast(Bit - '0'); + if (Bit >= 'a' && Bit <= 'f') { + decimal = static_cast(Bit - 'a') + 10u; + } + if (Bit >= 'A' && Bit <= 'F') { + decimal = static_cast(Bit - 'A') + 10u; + } + break; + } + num = num * base + decimal; + } +} + +template +constexpr void _parameter_pack_base_bits_prefix_len(size_t& base, size_t& bits, size_t& prefix_len, uint64_t& num) + requires(sizeof...(Bits) > 0) +{ + _parameter_pack_base_bits_prefix_len(base, bits, prefix_len, num); + _parameter_pack_base_bits_prefix_len(base, bits, prefix_len, num); +} + +template +constexpr std::pair _parameter_pack_decode_prefixed_num() { + size_t base = 0; + size_t bits = 0; + size_t prefix_len = 0; + uint64_t num = 0; + _parameter_pack_base_bits_prefix_len(base, bits, prefix_len, num); + size_t digits = (sizeof...(Bits) - prefix_len); + if (0 == base) { + base = 10; + digits = prefix_len; + prefix_len = 0; + } + if (0 == bits) { + if (base == 2) { + bits = digits; + } else if (base == 10) { + bits = std::bit_width(num); + } else { + bits = 4 * digits; + } + } + return {bits, num}; +} + +} // namespace bit + +template +constexpr bit::bit_array().first> operator""_b() { + bit::bit_array().first> arr{}; + auto [bits, num] = bit::_parameter_pack_decode_prefixed_num(); + for (int i = 0; i < arr.size(); i++) { + arr[i] = (num & 0x1) ? bit::bit1 : bit::bit0; + num >>= 1; + } + return arr; +} + +#endif diff --git a/include/bitlib/bit-containers/bit_span.hpp b/include/bitlib/bit-containers/bit_span.hpp index cb5debda..74c8ed2f 100644 --- a/include/bitlib/bit-containers/bit_span.hpp +++ b/include/bitlib/bit-containers/bit_span.hpp @@ -10,14 +10,14 @@ #include #include // Project sources -#include "bitlib/bit-iterator/bit.hpp" #include "bitlib/bit-algorithms/bit_algorithm.hpp" #include "bitlib/bit-containers/bit_bitsof.hpp" +#include "bitlib/bit-iterator/bit.hpp" namespace bit { -template -concept Scalar = std::is_scalar_v; +template +class bit_array_ref; // Helper storage: for a fixed extent no runtime size is stored. template @@ -130,7 +130,7 @@ class bit_span : private bit_span_storage { constexpr bit_span subspan(size_type offset, size_type count = std::dynamic_extent) const noexcept requires(Extent == std::dynamic_extent); - constexpr bit_span operator[](size_type begin, size_type end) const noexcept; + constexpr bit_array_ref operator()(size_type begin, size_type end) const noexcept; template constexpr bit_span first() const noexcept @@ -304,8 +304,8 @@ constexpr bit_span bit_span::sub } template -constexpr bit_span bit_span::operator[](size_type begin, size_type end) const noexcept { - return bit_span(&(this->begin()[begin]), end - begin); +constexpr bit_array_ref bit_span::operator()(size_type begin, size_type end) const noexcept { + return bit_array_ref(&(this->begin()[begin]), end - begin); } template diff --git a/include/bitlib/bit-containers/bit_vector.hpp b/include/bitlib/bit-containers/bit_vector.hpp index 53273db4..cb0b3ff2 100644 --- a/include/bitlib/bit-containers/bit_vector.hpp +++ b/include/bitlib/bit-containers/bit_vector.hpp @@ -186,7 +186,7 @@ class bit_vector { /* * Slice */ - constexpr bit_span operator[](size_type begin, size_type end) const noexcept; + constexpr bit_array_ref<> operator()(size_type begin, size_type end) const noexcept; /* * Helper functions @@ -658,8 +658,8 @@ constexpr void bit_vector::append_range(R&& range) { * Slice */ template -constexpr bit_span bit_vector::operator[](size_type begin, size_type end) const noexcept { - return bit_span(&this->at(begin), end - begin); +constexpr bit_array_ref<> bit_vector::operator()(size_type begin, size_type end) const noexcept { + return bit_array_ref<>(&this->at(begin), end - begin); } // ------------------------ BIT VECTOR: DEBUGGING -------------------------- // diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index cb390bba..e1cc9385 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -68,6 +68,7 @@ template constexpr std::size_t binary_digits_v = binary_digits::value; /* ************************************************************************** */ +#if 0 template using smallest_integral = std::conditional_t< (sizeof(T) <= sizeof(std::uint8_t)), @@ -82,6 +83,7 @@ using smallest_integral = std::conditional_t< (sizeof(T) <= sizeof(std::uint64_t)), std::uint64_t, T>>>>; +#endif /* *************** IMPLEMENTATION DETAILS: CV ITERATOR TRAITS *************** */ // Cv iterator traits structure definition diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8c621da4..cd893599 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -31,6 +31,7 @@ target_sources(bitlib-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/test-equal.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-fill.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-find.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-literal.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-move.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-reverse.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-rotate.cpp @@ -39,6 +40,7 @@ target_sources(bitlib-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/test-swap_ranges.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-transform.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-usecase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-array_ref.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/vector_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/inc/test_utils.hpp ) diff --git a/test/src/test-array.cpp b/test/src/test-array.cpp index f4355141..578ce026 100644 --- a/test/src/test-array.cpp +++ b/test/src/test-array.cpp @@ -1,4 +1,5 @@ #include +#include #include #include "bitlib/bit-containers/bit_array.hpp" @@ -147,6 +148,8 @@ TEST(ArrayTest, ComparisonOperators) { EXPECT_EQ(ba1, ba2); ba2[2] = bit::bit_value(true); // Change one element EXPECT_NE(ba1, ba2); + bit::bit_array ba3{bit::bit1, bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; + EXPECT_NE(ba1, ba3); } // Test the data() method to access the underlying storage. @@ -210,6 +213,34 @@ TEST(ArrayTest, MoveSemantics) { EXPECT_TRUE(ba2[4]); } +TEST(ArrayTest, Throws) { + bit::bit_array ba1{bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; + EXPECT_THROW(ba1.at(5), std::out_of_range); + bit::bit_array ba2{bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; + bit::bit_array ba3(ba2); + EXPECT_EQ(ba1, ba3); + EXPECT_EQ(ba1, ba2); + bit::bit_array ba4{bit::bit1, bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; + + using barr5 = bit::bit_array; // command in template messes up gtest macro + EXPECT_THROW(barr5{ba4}, std::invalid_argument) << "Copy constructor must take the same size"; + EXPECT_THROW(barr5{bit::bit0}, std::invalid_argument) << "Initializer list must be the correct size"; + + using namespace std::literals; + EXPECT_THROW(barr5{"010101"sv}, std::invalid_argument) << "String view constructor must be the correct size"; +} + +TEST(BitArrayDynamicTest, Throws) { + bit::bit_array<> ba1(5); + ba1 = {bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; + EXPECT_THROW(ba1.at(5), std::out_of_range); + bit::bit_array<> ba2(6); + ba2 = {bit::bit0, bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; + EXPECT_THROW(ba1 = ba2, std::invalid_argument); + EXPECT_THROW(ba1 = std::move(ba2), std::invalid_argument); + EXPECT_NO_THROW(ba1 = ba2(0, 5)); +} + // // Test Suite for bit::bit_array<> // @@ -245,6 +276,17 @@ TEST(BitArrayDynamicTest, CopyConstructorCopiesContent) { EXPECT_TRUE(copy == original); } +// Test copy constructor and copy assignment operator. +TEST(BitArrayDynamicTest, CopyAndAssignment) { + bit::bit_array ba1{bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; + bit::bit_array ba_copy(ba1); + EXPECT_EQ(ba1, ba_copy); + + bit::bit_array ba2{bit::bit1, bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; + EXPECT_THROW(ba1 = ba2, std::invalid_argument) << "Copy assignment from invalid size should throw"; + EXPECT_THROW(ba1 = ba2(0, 6), std::invalid_argument) << "Assign from bit_sized_range (bit_array_ref) of unequal size should throw"; +} + TEST(BitArrayDynamicTest, MoveConstructorMovesContent) { const std::size_t size = 8; bit::bit_array<> original(size, bit::bit_value(true)); @@ -415,36 +457,6 @@ TEST(BitArrayDynamicTest, StringConstructor) { EXPECT_EQ(arr[0], bit::bit0); } -TEST(BitArrayDynamicTest, UserDefinedLiteral) { - auto arr = 0b01001101_b; - EXPECT_EQ(arr.size(), 8); - EXPECT_EQ(arr[7], bit::bit0); - EXPECT_EQ(arr[0], bit::bit1); - auto arr2 = 0b1'01001101_b; - EXPECT_EQ(arr2.size(), 1); -} - -TEST(BitArrayDynamicTest, UserDefinedHexLiteral) { - auto arr = 0x010A110A_b; - EXPECT_EQ(arr.size(), 32); - EXPECT_EQ(arr[0], bit::bit0); - EXPECT_EQ(arr[1], bit::bit1); - EXPECT_EQ(arr[2], bit::bit0); - EXPECT_EQ(arr[3], bit::bit1); - EXPECT_EQ(arr[7], bit::bit0); - auto arr2 = 0x19'010A110A_b; - EXPECT_EQ(arr2.size(), 25); -} - -TEST(BitArrayDynamicTest, UserDefinedDecLiteral) { - auto arr = 16'12345_b; - EXPECT_EQ(arr.size(), 16); - auto arr2 = 0x3039_b; - EXPECT_EQ(arr, arr2); - auto arr3 = 16'123'45_b; - EXPECT_EQ(arr, arr3); -} - TEST(BitArrayDynamicTest, TwoDBitArraySizeValueConstructor) { bit::bit_array> arr( 16, @@ -461,6 +473,17 @@ TEST(BitArrayDynamicTest, TwoDBitArraySizeConstructor) { EXPECT_EQ(arr[0].size(), 4); } +// Test comparison operators (== and !=). +TEST(BitArrayDynamicTest, ComparisonOperators) { + bit::bit_array ba1{bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; + bit::bit_array ba2{bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; + EXPECT_EQ(ba1, ba2); + ba2[2] = bit::bit_value(true); // Change one element + EXPECT_NE(ba1, ba2); + bit::bit_array ba3{bit::bit0, bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; + EXPECT_NE(ba1, ba3); +} + TEST(BitArrayTest, Slice) { auto arr = 0x20'DEADBEEF_b; auto span2 = arr(4, 8); @@ -469,6 +492,8 @@ TEST(BitArrayTest, Slice) { EXPECT_EQ(span2[1], (0xE & (1 << 1)) ? bit::bit1 : bit::bit0); EXPECT_EQ(span2[2], (0xE & (1 << 2)) ? bit::bit1 : bit::bit0); EXPECT_EQ(span2[3], (0xE & (1 << 3)) ? bit::bit1 : bit::bit0); + span2 = 0x4'A_b; + EXPECT_EQ(arr, 0x20'DEADBEAF_b); } TEST(BitArrayTest, SliceModify) { @@ -481,4 +506,5 @@ TEST(BitArrayTest, SliceModify) { EXPECT_EQ(span2[3], (0xE & (1 << 3)) ? bit::bit1 : bit::bit0); span2[3] = bit::bit0; EXPECT_EQ(span2[3], bit::bit0); + EXPECT_EQ(arr, 0x24'DEADBE6F_b); } diff --git a/test/src/test-array_ref.cpp b/test/src/test-array_ref.cpp new file mode 100644 index 00000000..4db45b17 --- /dev/null +++ b/test/src/test-array_ref.cpp @@ -0,0 +1,223 @@ +#include + +#include "bitlib/bitlib.hpp" +#include "gtest/gtest.h" + +TEST(BitArrayRef, ConstructorAndAssignment) { + // Prepare original bit array + bit::bit_array<> original(64, bit::bit0); + original[0] = bit::bit1; + original[10] = bit::bit1; + original[63] = bit::bit1; + + // Create a bit_array_ref to original's storage + bit::bit_array_ref<> ref(original.data(), original.size()); + + // Verify the reference matches the original content + EXPECT_EQ(ref.size(), original.size()); + EXPECT_EQ(ref[0], original[0]); + EXPECT_EQ(ref[10], original[10]); + EXPECT_EQ(ref[63], original[63]); + + // Modify through the reference + ref[20] = bit::bit1; + ref[30] = bit::bit1; + + // Verify the original was modified + EXPECT_EQ(original[20], bit::bit1); + EXPECT_EQ(original[30], bit::bit1); + + // Create another array + bit::bit_array<> other(64, bit::bit1); + + // Create another reference + bit::bit_array_ref<> other_ref(other.data(), other.size()); + + // Copy content (not pointer) + ref = other_ref; + + // Verify content was copied + for (size_t i = 0; i < ref.size(); ++i) { + EXPECT_EQ(ref[i], bit::bit1); + EXPECT_EQ(original[i], bit::bit1); + } +} + +TEST(BitArrayRef, SliceOperations) { + // Prepare original bit array + bit::bit_array<> original(64, bit::bit0); + original[10] = bit::bit1; + original[20] = bit::bit1; + original[30] = bit::bit1; + + // Create a slice (bit_array_ref) from offset 5 to 35 + auto slice = original(5, 35); + + // Verify the slice's content + EXPECT_EQ(slice.size(), 30); + EXPECT_EQ(slice[5], bit::bit1); // original[10] + EXPECT_EQ(slice[15], bit::bit1); // original[20] + EXPECT_EQ(slice[25], bit::bit1); // original[30] + + // Modify through the slice + slice[10] = bit::bit1; // original[15] + + // Verify the original was modified + EXPECT_EQ(original[15], bit::bit1); + + // Create another slice from our slice + auto subslice = slice(10, 20); + + // Verify the subslice's content + EXPECT_EQ(subslice.size(), 10); + EXPECT_EQ(subslice[0], bit::bit1); // slice[10], original[15] + EXPECT_EQ(subslice[5], bit::bit1); // slice[15], original[20] + + // Modify through the subslice + subslice[2] = bit::bit1; // slice[12], original[17] + + // Verify the original and slice were modified + EXPECT_EQ(slice[12], bit::bit1); + EXPECT_EQ(original[17], bit::bit1); +} + +TEST(BitArrayRef, Operations) { + // Prepare original bit array + bit::bit_array<> original(64, bit::bit0); + + // Create a bit_array_ref to original's storage + bit::bit_array_ref<> ref(original.data(), original.size()); + + // Test fill + ref.fill(bit::bit1); + + // Verify the original was filled with bit1 + for (size_t i = 0; i < original.size(); ++i) { + EXPECT_EQ(original[i], bit::bit1); + } + + // Create two arrays + bit::bit_array<> array1(32, bit::bit1); + bit::bit_array<> array2(32, bit::bit0); + + // Create refs + bit::bit_array_ref<> ref1(array1.data(), array1.size()); + bit::bit_array_ref<> ref2(array2.data(), array2.size()); + + // Test swap + ref1.swap(ref2); + + // Verify contents were swapped + for (size_t i = 0; i < array1.size(); ++i) { + EXPECT_EQ(array1[i], bit::bit0); + EXPECT_EQ(array2[i], bit::bit1); + } + + // Test equality + EXPECT_NE(ref1, ref2); + + ref1.fill(bit::bit1); + ref2.fill(bit::bit1); + + EXPECT_EQ(ref1, ref2); +} + +TEST(BitArrayRef, IntegralConversion) { + // Test small values + bit::bit_array<> value16(16); + value16[0] = bit::bit1; // 1 + value16[1] = bit::bit1; // 2 + value16[2] = bit::bit1; // 4 + + bit::bit_array_ref<> ref16(value16.data(), value16.size()); + + // Conversion + uint16_t int_val = static_cast(ref16); + EXPECT_EQ(int_val, 7u); // 2^0 + 2^1 + 2^2 = 1 + 2 + 4 = 7 + + // Test larger values + bit::bit_array<> value32(32); + value32[0] = bit::bit1; // 1 + value32[16] = bit::bit1; // 65536 + value32[31] = bit::bit1; // 2^31 + + bit::bit_array_ref<> ref32(value32.data(), value32.size()); + + // Conversion + uint32_t int_val32 = static_cast(ref32); + EXPECT_EQ(int_val32, 1u + 65536u + (1u << 31)); +} + +TEST(BitArrayRef, BitPointerConstructor) { + // Prepare original bit array + bit::bit_array<> original(64, bit::bit0); + original[0] = bit::bit1; + original[10] = bit::bit1; + original[63] = bit::bit1; + + // Create a bit_pointer to original's storage + bit::bit_pointer bit_ptr(original.data()); + + // Create a bit_array_ref using the bit_pointer + bit::bit_array_ref<> ref(bit_ptr, original.size()); + + // Verify the reference matches the original content + EXPECT_EQ(ref.size(), original.size()); + EXPECT_EQ(ref[0], original[0]); + EXPECT_EQ(ref[10], original[10]); + EXPECT_EQ(ref[63], original[63]); + + // Modify through the reference + ref[20] = bit::bit1; + ref[30] = bit::bit1; + + // Verify the original was modified + EXPECT_EQ(original[20], bit::bit1); + EXPECT_EQ(original[30], bit::bit1); +} + +TEST(BitArrayRef, MoveAssignment) { + bit::bit_array<> original(64, bit::bit0); + original[3] = bit::bit1; + original[13] = bit::bit1; + original[33] = bit::bit1; + original[43] = bit::bit1; + bit::bit_array_ref<> ref1(original, 32); + bit::bit_array_ref<> ref2(&original[32], 32); + ref1 = ref2; + EXPECT_EQ(ref1, original(32, 64)); +} + +TEST(BitArrayRef, OffsetBitPointerConstructor) { + // Prepare original bit array + bit::bit_array<> original(64, bit::bit0); + original[3] = bit::bit1; + original[13] = bit::bit1; + + // Create an offset bit_pointer (starting from the 3rd bit) + bit::bit_pointer bit_ptr(original.data(), 3); + + // Create a bit_array_ref using the offset bit_pointer + bit::bit_array_ref<> ref(bit_ptr, 32); + + // Verify the reference matches the expected content + EXPECT_EQ(ref.size(), 32); + EXPECT_EQ(ref[0], bit::bit1); // This should be original[3] + EXPECT_EQ(ref[10], bit::bit1); // This should be original[13] + + // Modify through the reference + ref[5] = bit::bit1; + + // Verify the original was modified at the correct offset + EXPECT_EQ(original[8], bit::bit1); // 3 + 5 = 8 +} + +TEST(BitArrayRef, Throws) { + bit::bit_array<> original(64, bit::bit0); + bit::bit_array_ref<> ref(original, 32); + bit::bit_array<> right_side(33, bit::bit0); + EXPECT_THROW(ref = right_side, std::invalid_argument); + bit::bit_array_ref<> ref2(&original[31], 33); + EXPECT_THROW(ref = ref2, std::invalid_argument); + EXPECT_THROW(ref.swap(ref2), std::invalid_argument); +} diff --git a/test/src/test-literal.cpp b/test/src/test-literal.cpp new file mode 100644 index 00000000..b8411515 --- /dev/null +++ b/test/src/test-literal.cpp @@ -0,0 +1,97 @@ +// =============================== FIXTURES ================================= // +// Project: The Experimental Bit Algorithms Library +// Description: Fixtures for testing +// Contributor(s): Bryce Kille +// License: BSD 3-Clause License +// ========================================================================== // + + +// ============================== PREAMBLE ================================== // +// C++ standard library +#include +#include +// Project sources +#include "bitlib/bit-containers/bit-containers.hpp" +#include "fixtures.hpp" +// Third-party libraries +#include "gtest/gtest.h" +// Miscellaneous +// ========================================================================== // + +TEST(BitLiteral, dec_base) { + auto one = 1'1_b; + EXPECT_EQ(one.size(), 1); + EXPECT_EQ(one[0], bit::bit1); + auto three = 2'1'1_b; + EXPECT_EQ(three.size(), 2); + EXPECT_EQ(three[0], bit::bit1); + EXPECT_EQ(three[1], bit::bit1); + auto ten = 4'10_b; + EXPECT_EQ(ten.size(), 4); + EXPECT_EQ(ten[0], bit::bit0); + EXPECT_EQ(ten[1], bit::bit1); + EXPECT_EQ(ten[2], bit::bit0); + EXPECT_EQ(ten[3], bit::bit1); + auto ten_2 = 10_b; + EXPECT_EQ(ten_2.size(), 4); + EXPECT_EQ(ten_2[0], bit::bit0); + EXPECT_EQ(ten_2[1], bit::bit1); + EXPECT_EQ(ten_2[2], bit::bit0); + EXPECT_EQ(ten_2[3], bit::bit1); +} + +TEST(BitLiteral, hex_base) { + auto fifteen = 0xF'1234_b; + EXPECT_EQ(fifteen.size(), 15); + EXPECT_EQ(fifteen[0], bit::bit0); + EXPECT_EQ(fifteen[1], bit::bit0); + EXPECT_EQ(fifteen[2], bit::bit1); + auto unsized = 0x1234_b; + EXPECT_EQ(unsized.size(), 16); + EXPECT_EQ(unsized[0], bit::bit0); + EXPECT_EQ(unsized[1], bit::bit0); + EXPECT_EQ(unsized[2], bit::bit1); +} + +TEST(BitLiteral, bin_base) { + auto fifteen = 0b1111'001000110100_b; + EXPECT_EQ(fifteen.size(), 15); + EXPECT_EQ(fifteen[0], bit::bit0); + EXPECT_EQ(fifteen[1], bit::bit0); + EXPECT_EQ(fifteen[2], bit::bit1); + auto unsized = 0b01101_b; + EXPECT_EQ(unsized.size(), 5); + EXPECT_EQ(unsized[0], bit::bit1); + EXPECT_EQ(unsized[1], bit::bit0); + EXPECT_EQ(unsized[2], bit::bit1); +} + +TEST(BitLiteral, UserDefinedLiteral) { + auto arr = 0b01001101_b; + EXPECT_EQ(arr.size(), 8); + EXPECT_EQ(arr[7], bit::bit0); + EXPECT_EQ(arr[0], bit::bit1); + auto arr2 = 0b1'01001101_b; + EXPECT_EQ(arr2.size(), 1); +} + +TEST(BitLiteral, UserDefinedHexLiteral) { + auto arr = 0x010A110A_b; + EXPECT_EQ(arr.size(), 32); + EXPECT_EQ(arr[0], bit::bit0); + EXPECT_EQ(arr[1], bit::bit1); + EXPECT_EQ(arr[2], bit::bit0); + EXPECT_EQ(arr[3], bit::bit1); + EXPECT_EQ(arr[7], bit::bit0); + auto arr2 = 0x19'010A110A_b; + EXPECT_EQ(arr2.size(), 25); +} + +TEST(BitLiteral, UserDefinedDecLiteral) { + auto arr = 16'12345_b; + EXPECT_EQ(arr.size(), 16); + auto arr2 = 0x3039_b; + EXPECT_EQ(arr, arr2); + auto arr3 = 16'123'45_b; + EXPECT_EQ(arr, arr3); +} diff --git a/test/src/test-span.cpp b/test/src/test-span.cpp index 3f1e025a..262377a2 100644 --- a/test/src/test-span.cpp +++ b/test/src/test-span.cpp @@ -207,7 +207,7 @@ TEST(BitSpanTest, CopyConstructionFixed) { TEST(BitSpanTest, Slice) { uint32_t word = 0xDEADBEEF; bit::bit_span span1(word); - auto span2 = span1[4, 8]; + auto span2 = span1(4, 8); EXPECT_EQ(span2.size(), 4); EXPECT_EQ(span2[0], (0xE & (1 << 0)) ? bit::bit1 : bit::bit0); EXPECT_EQ(span2[1], (0xE & (1 << 1)) ? bit::bit1 : bit::bit0); diff --git a/test/src/vector_test.cpp b/test/src/vector_test.cpp index f7f332ff..5c896b48 100644 --- a/test/src/vector_test.cpp +++ b/test/src/vector_test.cpp @@ -752,7 +752,7 @@ TEST(BitVectorTest, Slice) { uint32_t word = 0xDEADBEEF; bit::bit_span span1(word); auto vec = bit::bit_vector(std::from_range, span1); - auto span2 = span1[4, 8]; + auto span2 = span1(4, 8); EXPECT_EQ(span2.size(), 4); EXPECT_EQ(span2[0], (0xE & (1 << 0)) ? bit::bit1 : bit::bit0); EXPECT_EQ(span2[1], (0xE & (1 << 1)) ? bit::bit1 : bit::bit0); From 09dd4e61e63920ffa72718179852959ca3106db9 Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Sun, 11 May 2025 19:38:37 -0400 Subject: [PATCH 62/85] Generate baseline coverage before running tests (#9) * Generate baseline coverage before running tests * Hunt and peck failing gcc inline flag * ignore mismatch on baseline * Move build so it's easier to generate baseline without * Clean checkout * Clean any cmake probing files that create coverage * Try both gcna gcno clean before build * Post configure clean up build dir too * Typo * set build dir to absolute --- .github/actions/coverage_epilogue/action.yml | 31 +++++++++++++++----- .github/actions/coverage_prologue/action.yml | 24 +++++++++++++++ .github/workflows/cmake-multi-platform.yml | 24 ++++++++++----- CMakePresets.json | 4 +-- 4 files changed, 66 insertions(+), 17 deletions(-) create mode 100644 .github/actions/coverage_prologue/action.yml diff --git a/.github/actions/coverage_epilogue/action.yml b/.github/actions/coverage_epilogue/action.yml index f5de5588..12c373b8 100644 --- a/.github/actions/coverage_epilogue/action.yml +++ b/.github/actions/coverage_epilogue/action.yml @@ -10,18 +10,32 @@ inputs: runs: using: "composite" steps: - - name: "Install lcov" - shell: bash - run: | - sudo apt-get update - sudo apt-get install -y lcov - - name: Generate Coverage Report shell: bash run: | - lcov --capture --directory ${{ inputs.build-output-dir }} --output-file coverage.info --ignore-errors mismatch - lcov --remove coverage.info '/usr/*' '*/_deps/*' '*/include/bitlib/bit-algorithms/libpopcnt.h' '*/test/inc/*' '*/test/src/*' --output-file coverage.info + # Capture actual coverage data after tests + lcov --capture \ + --directory "${{ inputs.build-output-dir }}" \ + --output-file coverage.info \ + --ignore-errors mismatch + + # Combine with baseline + lcov --add-tracefile coverage.baseline \ + --add-tracefile coverage.info\ + --output-file coverage.info + + # Clean the coverage report from system and external deps + lcov --remove coverage.info \ + '/usr/*' \ + '*/_deps/*' \ + '*/include/bitlib/bit-algorithms/libpopcnt.h' \ + '*/test/inc/*' \ + '*/test/src/*' \ + --output-file coverage.info + + # Show a summary in the logs lcov --list coverage.info + # Generate an HTML report genhtml coverage.info --output-directory out/coverage @@ -31,6 +45,7 @@ runs: name: coverage-report path: | out/coverage + coverage.baseline coverage.info - name: Coveralls diff --git a/.github/actions/coverage_prologue/action.yml b/.github/actions/coverage_prologue/action.yml new file mode 100644 index 00000000..0aea436c --- /dev/null +++ b/.github/actions/coverage_prologue/action.yml @@ -0,0 +1,24 @@ +name: "Coverage Prologue" +description: "Installs lcov and generates the baseline coverage" +inputs: + build-output-dir: + required: true + description: 'Build output directory' + +runs: + using: "composite" + steps: + - name: "Install lcov" + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y lcov + + - name: "Initialize 0% coverage baseline" + shell: bash + run: | + lcov --capture --initial \ + --base-directory "$GITHUB_WORKSPACE" \ + --directory "${{ inputs.build-output-dir }}" \ + --output-file coverage.baseline \ + --ignore-errors mismatch diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 8b8cf439..d3e327f1 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -93,18 +93,23 @@ jobs: id: strings shell: bash run: | - echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" - + echo "build-output-dir=$(readlink -f "${{ github.workspace }}/../build")" >> "$GITHUB_OUTPUT" + - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: > - cmake -B ${{ steps.strings.outputs.build-output-dir }} -S ${{ github.workspace }} - --preset=${{ matrix.preset }}_${{ matrix.os }}_${{ matrix.compiler }}_${{ matrix.stdlib }} - -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} - -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + run: | + # Configure + cmake -B ${{ steps.strings.outputs.build-output-dir }} -S ${{ github.workspace }} \ + --preset=${{ matrix.preset }}_${{ matrix.os }}_${{ matrix.compiler }}_${{ matrix.stdlib }} \ + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} \ + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} \ -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + # Delete any coverage files created from CMake probing + find ${{ github.workspace }} \( -name '*.gcno' -o -name '*.gcda' \) -delete + find ${{ steps.strings.outputs.build-output-dir }} \( -name '*.gcno' -o -name '*.gcda' \) -delete + - name: Register Compiler Problem Matcher run: echo "::add-matcher::.github/problem_matchers/${{ matrix.compiler}}_problem_matcher.json" @@ -115,6 +120,11 @@ jobs: - name: Unregister Compiler Problem Matcher run: echo "::remove-matcher owner=${{ matrix.compiler}}::" + - uses: ./.github/actions/coverage_prologue + if: matrix.preset == 'coverage' + with: + build-output-dir: ${{ steps.strings.outputs.build-output-dir }} + - name: Test # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail diff --git a/CMakePresets.json b/CMakePresets.json index 1a4cebb9..d17d5ed2 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -41,8 +41,8 @@ "name": "gnu_base", "hidden": true, "cacheVariables": { - "CMAKE_C_FLAGS_DEBUG": "-DPOPCNT_NO_UNALIGNED -g3 -fno-omit-frame-pointer -fstack-protector-all -fsanitize=address -fsanitize=undefined -finstrument-functions -fno-inline", - "CMAKE_CXX_FLAGS_DEBUG": "-DPOPCNT_NO_UNALIGNED -g3 -fno-omit-frame-pointer -DPOPCNT_NO_UNALIGNED -fstack-protector-all -fsanitize=address -fsanitize=undefined -finstrument-functions -fno-inline", + "CMAKE_C_FLAGS_DEBUG": "-DPOPCNT_NO_UNALIGNED -g3 -fno-omit-frame-pointer -fstack-protector-all -fsanitize=address -fsanitize=undefined -finstrument-functions -fno-inline -fno-inline-small-functions -fno-default-inline", + "CMAKE_CXX_FLAGS_DEBUG": "-DPOPCNT_NO_UNALIGNED -g3 -fno-omit-frame-pointer -DPOPCNT_NO_UNALIGNED -fstack-protector-all -fsanitize=address -fsanitize=undefined -finstrument-functions -fno-inline -fno-inline-small-functions -fno-default-inline", "CMAKE_C_FLAGS_RELEASE": "-O3 -DNDEBUG -march=native", "CMAKE_CXX_FLAGS_RELEASE": "-O3 -DNDEBUG -march=native" } From 9126838eb1cc7952aaefaa528f9b7fc826b5847f Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Tue, 13 May 2025 07:26:17 -0400 Subject: [PATCH 63/85] Add github workflow targets for warnings (#7) * WError cmake option * Add a build-only workflow for warnings * Add clang warnings build target * Try enabling coverage w/ clang * Refactor presets * composite actions are apparently syntax strict * Try to symlink llvm/clang tools * Use absolute path for coverage.profraw * ctest interferes with llvm coverage, likely due to parallel. try a cleaner approach * Codecov only supports lcov/gcov * Prevent artifact conflicts * missing github token * Different artifact names --- .github/actions/coverage_clang/action.yml | 60 +++++ .github/actions/coverage_epilogue/action.yml | 26 +- .github/actions/coverage_gcc/action.yml | 81 ++++++ .github/actions/coverage_prologue/action.yml | 10 + .github/workflows/cmake-multi-platform.yml | 54 +++- CMakeLists.txt | 1 + CMakePresets.json | 231 +++++++++++----- include/bitlib/bit-algorithms/libpopcnt.h | 263 +++++++++---------- test/CMakeLists.txt | 9 +- 9 files changed, 516 insertions(+), 219 deletions(-) create mode 100644 .github/actions/coverage_clang/action.yml create mode 100644 .github/actions/coverage_gcc/action.yml diff --git a/.github/actions/coverage_clang/action.yml b/.github/actions/coverage_clang/action.yml new file mode 100644 index 00000000..e75f6698 --- /dev/null +++ b/.github/actions/coverage_clang/action.yml @@ -0,0 +1,60 @@ +name: "Coverage Clang" +description: "enerates the baseline coverage" +inputs: + build-output-dir: + required: true + description: 'Build output directory' + GITHUB_TOKEN: + required: true + +runs: + using: "composite" + steps: + - name: Test + shell: bash + run: + LLVM_PROFILE_FILE=coverage.profdataraw ${{ inputs.build-output-dir }}/test/bitlib-tests + + - name: Generate Coverage Report + shell: bash + run: | + # Merge raw coverage into .profdata + llvm-profdata merge -sparse coverage.profdataraw -o coverage.profdata + + # Show summary coverage report + llvm-cov report ${{ inputs.build-output-dir }}/test/bitlib-tests \ + -instr-profile=coverage.profdata \ + -ignore-filename-regex='/usr/|_deps/|libpopcnt\.h|test/inc/|test/src/' + + # Generate HTML report + llvm-cov show ${{ inputs.build-output-dir }}/test/bitlib-tests \ + -instr-profile=coverage.profdata \ + -format=html \ + -output-dir=out/coverage_clang \ + -ignore-filename-regex='/usr/|_deps/|libpopcnt\.h|test/inc/|test/src/' + + llvm-cov export ${{ inputs.build-output-dir }}/test/bitlib-tests \ + -instr-profile=coverage.profdata \ + -format=lcov > coverage_clang.info + + - name: Upload coverage artifact + uses: actions/upload-artifact@v4 + with: + name: coverage_clang_report + path: | + out/coverage_clang + coverage.profdata + coverage_clang.info + + - name: Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ inputs.GITHUB_TOKEN }} + path-to-lcov: coverage_clang.info + + - name: Upload to Codecov + uses: codecov/codecov-action@v5 + with: + files: coverage_clang.info + flags: unittests + name: codecov-coverage-report diff --git a/.github/actions/coverage_epilogue/action.yml b/.github/actions/coverage_epilogue/action.yml index 12c373b8..9515efc8 100644 --- a/.github/actions/coverage_epilogue/action.yml +++ b/.github/actions/coverage_epilogue/action.yml @@ -4,13 +4,17 @@ inputs: build-output-dir: required: true description: 'Build output directory' + compiler: + required: true + description: 'Compiler in use' GITHUB_TOKEN: required: true runs: using: "composite" steps: - - name: Generate Coverage Report + - name: Generate Coverage Report (gcc) + if: ${{ inputs.compiler == 'gcc' }} shell: bash run: | # Capture actual coverage data after tests @@ -39,6 +43,25 @@ runs: # Generate an HTML report genhtml coverage.info --output-directory out/coverage + - name: Generate Coverage Report (clang) + if: ${{ inputs.compiler == 'clang' }} + shell: bash + run: | + # Merge raw coverage into .profdata + llvm-profdata merge -sparse coverage.profraw -o coverage.info + + # Show summary coverage report + llvm-cov report ${{ inputs.build-output-dir }}/test/bitlib-tests \ + -instr-profile=coverage.info \ + -ignore-filename-regex='/usr/|_deps/|libpopcnt\.h|test/inc/|test/src/' + + # Generate HTML report + llvm-cov show ${{ inputs.build-output-dir }}/test/bitlib-tests \ + -instr-profile=coverage.info \ + -format=html \ + -output-dir=out/coverage \ + -ignore-filename-regex='/usr/|_deps/|libpopcnt\.h|test/inc/|test/src/' + - name: Upload coverage artifact uses: actions/upload-artifact@v4 with: @@ -49,6 +72,7 @@ runs: coverage.info - name: Coveralls + if: ${{ inputs.compiler != 'clang' }} uses: coverallsapp/github-action@master with: github-token: ${{ inputs.GITHUB_TOKEN }} diff --git a/.github/actions/coverage_gcc/action.yml b/.github/actions/coverage_gcc/action.yml new file mode 100644 index 00000000..4d3ef65c --- /dev/null +++ b/.github/actions/coverage_gcc/action.yml @@ -0,0 +1,81 @@ +name: "Coverage GCC" +description: "Installs lcov, generates the baseline coverage, runs tests, processes coverage and uploads reports" +inputs: + build-output-dir: + required: true + description: 'Build output directory' + GITHUB_TOKEN: + required: true + +runs: + using: "composite" + steps: + - name: "Install lcov" + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y lcov + + - name: "Initialize 0% coverage baseline" + shell: bash + run: | + lcov --capture --initial \ + --base-directory "$GITHUB_WORKSPACE" \ + --directory "${{ inputs.build-output-dir }}" \ + --output-file coverage.baseline \ + --ignore-errors mismatch + + - name: Test + shell: bash + run: ${{ inputs.build-output-dir }}/test/bitlib-tests + + - name: Generate Coverage Report + shell: bash + run: | + # Capture actual coverage data after tests + lcov --capture \ + --directory "${{ inputs.build-output-dir }}" \ + --output-file coverage_gcc.info \ + --ignore-errors mismatch + + # Combine with baseline + lcov --add-tracefile coverage.baseline \ + --add-tracefile coverage_gcc.info\ + --output-file coverage_gcc.info + + # Clean the coverage report from system and external deps + lcov --remove coverage_gcc.info \ + '/usr/*' \ + '*/_deps/*' \ + '*/include/bitlib/bit-algorithms/libpopcnt.h' \ + '*/test/inc/*' \ + '*/test/src/*' \ + --output-file coverage_gcc.info + + # Show a summary in the logs + lcov --list coverage_gcc.info + + # Generate an HTML report + genhtml coverage_gcc.info --output-directory out/coverage_gcc + + - name: Upload coverage artifact + uses: actions/upload-artifact@v4 + with: + name: coverage_gcc_report + path: | + out/coverage_gcc + coverage.baseline + coverage_gcc.info + + - name: Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ inputs.GITHUB_TOKEN }} + path-to-lcov: coverage_gcc.info + + - name: Upload to Codecov + uses: codecov/codecov-action@v5 + with: + files: coverage_gcc.info + flags: unittests + name: codecov-coverage-report diff --git a/.github/actions/coverage_prologue/action.yml b/.github/actions/coverage_prologue/action.yml index 0aea436c..883b77c6 100644 --- a/.github/actions/coverage_prologue/action.yml +++ b/.github/actions/coverage_prologue/action.yml @@ -4,17 +4,22 @@ inputs: build-output-dir: required: true description: 'Build output directory' + compiler: + required: true + description: 'Compiler in use' runs: using: "composite" steps: - name: "Install lcov" + if: ${{ inputs.compiler == 'gcc' }} shell: bash run: | sudo apt-get update sudo apt-get install -y lcov - name: "Initialize 0% coverage baseline" + if: ${{ inputs.compiler == 'gcc' }} shell: bash run: | lcov --capture --initial \ @@ -22,3 +27,8 @@ runs: --directory "${{ inputs.build-output-dir }}" \ --output-file coverage.baseline \ --ignore-errors mismatch + + - name: "LLVM profile env var" + if: ${{ inputs.compiler == 'clang' }} + shell: bash + run: echo "LLVM_PROFILE_FILE=${GITHUB_WORKSPACE}/coverage.profraw" >> $GITHUB_ENV \ No newline at end of file diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index d3e327f1..b73281b4 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -55,6 +55,24 @@ jobs: preset: tests target: bitlib-tests build_type: Debug + - distro: ubuntu-latest + os: linux + compiler: gcc + c_compiler: gcc + cpp_compiler: g++ + stdlib: libstdcxx + preset: warnings + target: bitlib-tests + build_type: Debug + - distro: ubuntu-latest + os: linux + compiler: clang + c_compiler: clang-19 + cpp_compiler: clang++-19 + stdlib: libcxx + preset: warnings + target: bitlib-tests + build_type: Debug - distro: ubuntu-latest os: linux compiler: clang @@ -73,6 +91,15 @@ jobs: preset: coverage target: bitlib-tests build_type: Debug + - distro: ubuntu-latest + os: linux + compiler: clang + cpp_compiler: clang++-19 + c_compiler: clang-19 + stdlib: libcxx + preset: coverage + target: bitlib-tests + build_type: Debug steps: - uses: actions/checkout@v4 @@ -87,6 +114,11 @@ jobs: sudo ./llvm.sh 19 # Install libc++ and libc++abi for Clang 19 sudo apt-get install -y libc++-19-dev libc++abi-19-dev + sudo ln -sf /usr/bin/llvm-cov-19 /usr/local/bin/llvm-cov + sudo ln -sf /usr/bin/llvm-profdata-19 /usr/local/bin/llvm-profdata + sudo ln -sf /usr/bin/clang-19 /usr/local/bin/clang + sudo ln -sf /usr/bin/clang++-19 /usr/local/bin/clang++ + sudo ln -sf /usr/bin/clang-format-19 /usr/local/bin/clang-format - name: Set reusable strings # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. @@ -94,7 +126,7 @@ jobs: shell: bash run: | echo "build-output-dir=$(readlink -f "${{ github.workspace }}/../build")" >> "$GITHUB_OUTPUT" - + - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type @@ -120,22 +152,26 @@ jobs: - name: Unregister Compiler Problem Matcher run: echo "::remove-matcher owner=${{ matrix.compiler}}::" - - uses: ./.github/actions/coverage_prologue - if: matrix.preset == 'coverage' + - name: Run coverage for Clang + if: matrix.preset == 'coverage' && matrix.compiler == 'clang' + uses: ./.github/actions/coverage_clang + with: + build-output-dir: ${{ steps.strings.outputs.build-output-dir }} + GITHUB_TOKEN: ${{ secrets.github_token }} + + - name: Run coverage for GCC + if: matrix.preset == 'coverage' && matrix.compiler == 'gcc' + uses: ./.github/actions/coverage_gcc with: build-output-dir: ${{ steps.strings.outputs.build-output-dir }} + GITHUB_TOKEN: ${{ secrets.github_token }} - name: Test + if: matrix.preset != 'coverage' # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest --test-dir ${{ steps.strings.outputs.build-output-dir }} --build-config ${{ matrix.build_type }} --output-on-failure --parallel - - uses: ./.github/actions/coverage_epilogue - if: matrix.preset == 'coverage' - with: - build-output-dir: ${{ steps.strings.outputs.build-output-dir }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: ./.github/actions/benchmark_epilogue if: matrix.preset == 'benchmark' with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 21ef3768..2d47ec40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ option(BITLIB_HWY "Build with google highway SIMD extensions" OFF) option(BITLIB_BENCHMARK "Build bitlib benchmarks" OFF) option(BITLIB_EXAMPLE "Build bitlib examples" OFF) option(BITLIB_TEST "Build bitlib tests" OFF) +option(BITLIB_TEST_WERROR "Build bitlib tests with -Werror" OFF) option(BITLIB_MDSPAN "Accessor Support for mdspan" ON) add_library(bitlib INTERFACE) diff --git a/CMakePresets.json b/CMakePresets.json index d17d5ed2..7417bde5 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -38,167 +38,214 @@ } }, { - "name": "gnu_base", + "name": "tests_base", "hidden": true, "cacheVariables": { - "CMAKE_C_FLAGS_DEBUG": "-DPOPCNT_NO_UNALIGNED -g3 -fno-omit-frame-pointer -fstack-protector-all -fsanitize=address -fsanitize=undefined -finstrument-functions -fno-inline -fno-inline-small-functions -fno-default-inline", - "CMAKE_CXX_FLAGS_DEBUG": "-DPOPCNT_NO_UNALIGNED -g3 -fno-omit-frame-pointer -DPOPCNT_NO_UNALIGNED -fstack-protector-all -fsanitize=address -fsanitize=undefined -finstrument-functions -fno-inline -fno-inline-small-functions -fno-default-inline", - "CMAKE_C_FLAGS_RELEASE": "-O3 -DNDEBUG -march=native", - "CMAKE_CXX_FLAGS_RELEASE": "-O3 -DNDEBUG -march=native" + "BITLIB_TEST": "ON" } }, { - "name": "clang_libcxx_base", + "name": "benchmark_base", "hidden": true, "cacheVariables": { - "CMAKE_C_COMPILER": "clang", - "CMAKE_CXX_COMPILER": "clang++", - "CMAKE_CXX_FLAGS": "-stdlib=libc++", - "CMAKE_EXE_LINKER_FLAGS": "-stdlib=libc++" + "CMAKE_BUILD_TYPE": "Release", + "BITLIB_BENCHMARK": "ON" } }, { - "name": "clang_libstdcxx_base", + "name": "warnings_base", "hidden": true, + "environment": { + "TARGET_FLAGS": "-Wall -Wextra -Wpedantic -Wold-style-cast -Wconversion -Wshadow -Wno-error" + } + }, + { + "name": "gnu_base", + "hidden": true, + "environment": { + "GNU_FLAGS_DEBUG": "-DPOPCNT_NO_UNALIGNED -g3 -fno-omit-frame-pointer -fstack-protector-all -fsanitize=address -fsanitize=undefined -finstrument-functions -fno-inline", + "GNU_FLAGS_RELEASE": "-O3 -DNDEBUG -march=native" + }, "cacheVariables": { - "CMAKE_C_COMPILER": "clang", - "CMAKE_CXX_COMPILER": "clang++", - "CMAKE_CXX_FLAGS": "-stdlib=libstdc++", - "CMAKE_EXE_LINKER_FLAGS": "-stdlib=libstdc++" + "CMAKE_C_FLAGS": "$env{GNU_FLAGS} $env{CC_FLAGS} $env{TARGET_FLAGS}", + "CMAKE_CXX_FLAGS": "$env{GNU_FLAGS} $env{CC_FLAGS} $env{STDLIB_FLAGS} $env{TARGET_FLAGS}", + "CMAKE_EXE_LINKER_FLAGS": "$env{STDLIB_FLAGS}", + "CMAKE_C_FLAGS_DEBUG": "$env{GNU_FLAGS_DEBUG} $env{CC_FLAGS_DEBUG} $env{TARGET_FLAGS_DEBUG}", + "CMAKE_CXX_FLAGS_DEBUG": "$env{GNU_FLAGS_DEBUG} $env{CC_FLAGS_DEBUG} $env{TARGET_FLAGS_DEBUG}", + "CMAKE_C_FLAGS_RELEASE": "$env{GNU_FLAGS_RELEASE} $env{CC_FLAGS_RELEASE} $env{TARGET_FLAGS_RELEASE}", + "CMAKE_CXX_FLAGS_RELEASE": "$env{GNU_FLAGS_RELEASE} $env{CC_FLAGS_RELEASE} $env{TARGET_FLAGS_RELEASE}" } }, { "name": "gcc_base", "hidden": true, + "inherits": "gnu_base", "cacheVariables": { "CMAKE_C_COMPILER": "gcc", "CMAKE_CXX_COMPILER": "g++" + }, + "environment": { + "CC_FLAGS_DEBUG": "-fno-inline-small-functions -fno-default-inline", + "STDLIB_FLAGS": "" } }, { - "name": "tests_msvc_base", + "name": "libcxx_base", "hidden": true, - "inherits": "msvc_base", - "cacheVariables": { - "BITLIB_TEST": "ON" + "environment": { + "STDLIB_FLAGS": "-stdlib=libc++" } }, { - "name": "benchmark_msvc_base", + "name": "libstdcxx_base", "hidden": true, - "inherits": "msvc_base", - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release", - "BITLIB_BENCHMARK": "ON" + "environment": { + "STDLIB_FLAGS": "-stdlib=libstdc++" } }, { - "name": "tests_gnu_base", + "name": "clang_base", "hidden": true, "inherits": "gnu_base", "cacheVariables": { - "BITLIB_TEST": "ON", - "CMAKE_C_FLAGS": "-DPOPCNT_NO_UNALIGNED -fstack-protector-strong -fsanitize=undefined -fsanitize=address -fsanitize=bounds -D_FORTIFY_SOURCE=2", - "CMAKE_CXX_FLAGS": "-DPOPCNT_NO_UNALIGNED -fstack-protector-strong -fsanitize=undefined -fsanitize=address -fsanitize=bounds -D_FORTIFY_SOURCE=2" + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" } }, { - "name": "coverage_gnu_base", + "name": "coverage_gcc_base", "hidden": true, - "inherits": "gnu_base", - "cacheVariables": { - "CMAKE_C_FLAGS": "--coverage", - "CMAKE_CXX_FLAGS": "--coverage", - "CMAKE_C_FLAGS_RELEASE": "-O2 -g", - "CMAKE_CXX_FLAGS_RELEASE": "-O2 -g", - "BITLIB_TEST": "ON" + "environment": { + "TARGET_FLAGS": "--coverage", + "TARGET_FLAGS_RELEASE": "-O2 -g" } }, { - "name": "benchmark_gnu_base", + "name": "coverage_clang_base", "hidden": true, - "inherits": "gnu_base", - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release", - "CMAKE_C_FLAGS": "-O3 -DNDEBUG -march=native", - "CMAKE_CXX_FLAGS": "-O3 -DNDEBUG -march=native", - "BITLIB_BENCHMARK": "ON" + "environment": { + "TARGET_FLAGS": "-fprofile-instr-generate -fcoverage-mapping", + "TARGET_FLAGS_RELEASE": "-O2 -g" } }, { "name": "profile_gnu_base", "hidden": true, - "inherits": "gnu_base", - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release", - "CMAKE_C_FLAGS": "-pg -fno-omit-frame-pointer", - "CMAKE_CXX_FLAGS": "-pg -fno-omit-frame-pointer", - "CMAKE_C_FLAGS_RELEASE": "-O2 -DNDEBUG -march=native -pg -ggdb", - "CMAKE_CXX_FLAGS_RELEASE": "-O2 -DNDEBUG -march=native -pg -ggdb", - "BITLIB_TEST": "ON", - "BITLIB_BENCHMARK": "ON" + "environment": { + "TARGET_FLAGS_RELEASE": "-pg -fno-omit-frame-pointer -O2 -DNDEBUG -march=native -pg -ggdb" } }, { "name": "tests_windows_msvc_msvcstl", "inherits": [ - "windows_base", - "tests_msvc_base" + "tests_base", + "windows_base" ] }, { "name": "benchmark_windows_msvc_msvcstl", "inherits": [ - "windows_base", - "benchmark_msvc_base" - ] + "benchmark_base", + "windows_base" + ], + "cacheVariables": { + "CMAKE_C_FLAGS": "$env{MSVC_FLAGS}", + "CMAKE_CXX_FLAGS": "$env{MSVC_FLAGS}" + } }, { "name": "tests_linux_gcc_libstdcxx", "inherits": [ + "tests_base", "linux_base", - "gcc_base", - "tests_gnu_base" + "gcc_base" + ] + }, + { + "name": "tests_linux_clang_libcxx", + "inherits": [ + "tests_base", + "linux_base", + "clang_base", + "libcxx_base" + ] + }, + { + "name": "tests_linux_clang_libstdcxx", + "inherits": [ + "tests_base", + "linux_base", + "clang_base", + "libstdcxx_base" ] }, { "name": "coverage_linux_gcc_libstdcxx", "inherits": [ + "tests_base", "linux_base", "gcc_base", - "coverage_gnu_base" + "coverage_gcc_base" ] }, { - "name": "benchmark_linux_gcc_libstdcxx", + "name": "coverage_linux_clang_libcxx", "inherits": [ + "tests_base", "linux_base", - "gcc_base", - "benchmark_gnu_base" + "clang_base", + "coverage_clang_base", + "libcxx_base" ] }, { - "name": "tests_linux_clang_libcxx", + "name": "coverage_linux_clang_libstdcxx", "inherits": [ + "tests_base", "linux_base", - "clang_libcxx_base", - "tests_gnu_base" + "clang_base", + "coverage_clang_base", + "libstdcxx_base" ] }, { - "name": "coverage_linux_clang_libcxx", + "name": "benchmark_linux_gcc_libstdcxx", "inherits": [ + "benchmark_base", "linux_base", - "clang_libcxx_base", - "coverage_gnu_base" + "gcc_base" ] }, { "name": "benchmark_linux_clang_libcxx", "inherits": [ + "benchmark_base", + "linux_base", + "clang_base", + "libcxx_base" + ] + }, + { + "name": "benchmark_linux_clang_libstdcxx", + "inherits": [ + "benchmark_base", "linux_base", - "clang_libcxx_base", - "benchmark_gnu_base" + "clang_base", + "libstdcxx_base" + ] + }, + { + "name": "warnings_linux_gcc_libstdcxx", + "inherits": [ + "tests_linux_gcc_libstdcxx", + "warnings_base" + ] + }, + { + "name": "warnings_linux_clang_libcxx", + "inherits": [ + "tests_linux_clang_libcxx", + "warnings_base" ] } ], @@ -238,6 +285,28 @@ "rhs": "Linux" } }, + { + "name": "warnings_linux_gcc_libstdcxx", + "configurePreset": "warnings_linux_gcc_libstdcxx", + "targets": "bitlib-tests", + "jobs": 0, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "warnings_linux_clang_libcxx", + "configurePreset": "warnings_linux_clang_libcxx", + "targets": "bitlib-tests", + "jobs": 0, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, { "name": "coverage_linux_gcc_libstdcxx", "configurePreset": "coverage_linux_gcc_libstdcxx", @@ -282,6 +351,17 @@ "rhs": "Linux" } }, + { + "name": "coverage_linux_clang_libstdcxx", + "configurePreset": "coverage_linux_clang_libstdcxx", + "targets": "bitlib-tests", + "jobs": 0, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, { "name": "benchmark_linux_clang_libcxx", "configurePreset": "benchmark_linux_clang_libcxx", @@ -372,6 +452,15 @@ "rhs": "Linux" } }, + { + "name": "coverage_linux_clang_libstdcxx", + "configurePreset": "coverage_linux_clang_libstdcxx", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, { "name": "benchmark_linux_clang_libcxx", "configurePreset": "benchmark_linux_clang_libcxx", diff --git a/include/bitlib/bit-algorithms/libpopcnt.h b/include/bitlib/bit-algorithms/libpopcnt.h index ff2d2e79..db317b62 100644 --- a/include/bitlib/bit-algorithms/libpopcnt.h +++ b/include/bitlib/bit-algorithms/libpopcnt.h @@ -204,15 +204,14 @@ static inline uint64_t popcnt64(uint64_t x) static inline uint64_t popcnt64(uint64_t x) { - return _mm_popcnt_u32((uint32_t) x) + + return _mm_popcnt_u32((uint32_t)x) + _mm_popcnt_u32((uint32_t)(x >> 32)); } /* non x86 CPUs */ #elif defined(HAVE_BUILTIN_POPCOUNT) -static inline uint64_t popcnt64(uint64_t x) -{ +static inline uint64_t popcnt64(uint64_t x) { return __builtin_popcountll(x); } @@ -220,8 +219,7 @@ static inline uint64_t popcnt64(uint64_t x) * use pure integer algorithm */ #else -static inline uint64_t popcnt64(uint64_t x) -{ +static inline uint64_t popcnt64(uint64_t x) { return popcount64(x); } @@ -230,15 +228,15 @@ static inline uint64_t popcnt64(uint64_t x) #if defined(HAVE_CPUID) #if defined(_MSC_VER) - #include - #include +#include +#include #endif /* %ecx bit flags */ #define bit_POPCNT (1 << 23) /* %ebx bit flags */ -#define bit_AVX2 (1 << 5) +#define bit_AVX2 (1 << 5) #define bit_AVX512 (1 << 30) /* xgetbv bit flags */ @@ -246,31 +244,31 @@ static inline uint64_t popcnt64(uint64_t x) #define XSTATE_YMM (1 << 2) #define XSTATE_ZMM (7 << 5) -static inline void run_cpuid(int eax, int ecx, int* abcd) -{ +static inline void run_cpuid(int eax, int ecx, int* abcd) { #if defined(_MSC_VER) __cpuidex(abcd, eax, ecx); #else int ebx = 0; int edx = 0; - #if defined(__i386__) && \ - defined(__PIC__) - /* in case of PIC under 32-bit EBX cannot be clobbered */ - __asm__ ("movl %%ebx, %%edi;" - "cpuid;" - "xchgl %%ebx, %%edi;" - : "=D" (ebx), - "+a" (eax), - "+c" (ecx), - "=d" (edx)); - #else - __asm__ ("cpuid;" - : "+b" (ebx), - "+a" (eax), - "+c" (ecx), - "=d" (edx)); - #endif +#if defined(__i386__) && \ + defined(__PIC__) + /* in case of PIC under 32-bit EBX cannot be clobbered */ + __asm__( + "movl %%ebx, %%edi;" + "cpuid;" + "xchgl %%ebx, %%edi;" + : "=D"(ebx), + "+a"(eax), + "+c"(ecx), + "=d"(edx)); +#else + __asm__("cpuid;" + : "+b"(ebx), + "+a"(eax), + "+c"(ecx), + "=d"(edx)); +#endif abcd[0] = eax; abcd[1] = ebx; @@ -282,14 +280,13 @@ static inline void run_cpuid(int eax, int ecx, int* abcd) #if defined(HAVE_AVX2) || \ defined(HAVE_AVX512) -static inline int get_xcr0() -{ +static inline int get_xcr0() { int xcr0; #if defined(_MSC_VER) - xcr0 = (int) _xgetbv(0); + xcr0 = (int)_xgetbv(0); #else - __asm__ ("xgetbv" : "=a" (xcr0) : "c" (0) : "%edx" ); + __asm__("xgetbv" : "=a"(xcr0) : "c"(0) : "%edx"); #endif return xcr0; @@ -297,15 +294,15 @@ static inline int get_xcr0() #endif -static inline int get_cpuid() -{ +static inline int get_cpuid() { int flags = 0; int abcd[4]; run_cpuid(1, 0, abcd); - if ((abcd[2] & bit_POPCNT) == bit_POPCNT) + if ((abcd[2] & bit_POPCNT) == bit_POPCNT) { flags |= bit_POPCNT; + } #if defined(HAVE_AVX2) || \ defined(HAVE_AVX512) @@ -313,25 +310,26 @@ static inline int get_cpuid() int osxsave_mask = (1 << 27); /* ensure OS supports extended processor state management */ - if ((abcd[2] & osxsave_mask) != osxsave_mask) + if ((abcd[2] & osxsave_mask) != osxsave_mask) { return 0; + } int ymm_mask = XSTATE_SSE | XSTATE_YMM; int zmm_mask = XSTATE_SSE | XSTATE_YMM | XSTATE_ZMM; int xcr0 = get_xcr0(); - if ((xcr0 & ymm_mask) == ymm_mask) - { + if ((xcr0 & ymm_mask) == ymm_mask) { run_cpuid(7, 0, abcd); - if ((abcd[1] & bit_AVX2) == bit_AVX2) + if ((abcd[1] & bit_AVX2) == bit_AVX2) { flags |= bit_AVX2; + } - if ((xcr0 & zmm_mask) == zmm_mask) - { - if ((abcd[1] & bit_AVX512) == bit_AVX512) + if ((xcr0 & zmm_mask) == zmm_mask) { + if ((abcd[1] & bit_AVX512) == bit_AVX512) { flags |= bit_AVX512; + } } } @@ -347,33 +345,31 @@ static inline int get_cpuid() #include #if !defined(_MSC_VER) - __attribute__ ((target ("avx2"))) +__attribute__((target("avx2"))) #endif -static inline void CSA256(__m256i* h, __m256i* l, __m256i a, __m256i b, __m256i c) -{ +static inline void +CSA256(__m256i* h, __m256i* l, __m256i a, __m256i b, __m256i c) { __m256i u = _mm256_xor_si256(a, b); *h = _mm256_or_si256(_mm256_and_si256(a, b), _mm256_and_si256(u, c)); *l = _mm256_xor_si256(u, c); } #if !defined(_MSC_VER) - __attribute__ ((target ("avx2"))) +__attribute__((target("avx2"))) #endif -static inline __m256i popcnt256(__m256i v) -{ +static inline __m256i +popcnt256(__m256i v) { __m256i lookup1 = _mm256_setr_epi8( 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, - 5, 6, 6, 7, 6, 7, 7, 8 - ); + 5, 6, 6, 7, 6, 7, 7, 8); __m256i lookup2 = _mm256_setr_epi8( 4, 3, 3, 2, 3, 2, 2, 1, 3, 2, 2, 1, 2, 1, 1, 0, 4, 3, 3, 2, 3, 2, 2, 1, - 3, 2, 2, 1, 2, 1, 1, 0 - ); + 3, 2, 2, 1, 2, 1, 1, 0); __m256i low_mask = _mm256_set1_epi8(0x0f); __m256i lo = _mm256_and_si256(v, low_mask); @@ -392,10 +388,10 @@ static inline __m256i popcnt256(__m256i v) * @see https://arxiv.org/abs/1611.07612 */ #if !defined(_MSC_VER) - __attribute__ ((target ("avx2"))) +__attribute__((target("avx2"))) #endif -static inline uint64_t popcnt_avx2(const __m256i* ptr, uint64_t size) -{ +static inline uint64_t +popcnt_avx2(const __m256i* ptr, uint64_t size) { __m256i cnt = _mm256_setzero_si256(); __m256i ones = _mm256_setzero_si256(); __m256i twos = _mm256_setzero_si256(); @@ -408,8 +404,7 @@ static inline uint64_t popcnt_avx2(const __m256i* ptr, uint64_t size) uint64_t limit = size - size % 16; uint64_t* cnt64; - for(; i < limit; i += 16) - { + for (; i < limit; i += 16) { CSA256(&twosA, &ones, ones, _mm256_loadu_si256(ptr + i + 0), _mm256_loadu_si256(ptr + i + 1)); CSA256(&twosB, &ones, ones, _mm256_loadu_si256(ptr + i + 2), _mm256_loadu_si256(ptr + i + 3)); CSA256(&foursA, &twos, twos, twosA, twosB); @@ -435,10 +430,11 @@ static inline uint64_t popcnt_avx2(const __m256i* ptr, uint64_t size) cnt = _mm256_add_epi64(cnt, _mm256_slli_epi64(popcnt256(twos), 1)); cnt = _mm256_add_epi64(cnt, popcnt256(ones)); - for(; i < size; i++) + for (; i < size; i++) { cnt = _mm256_add_epi64(cnt, popcnt256(_mm256_loadu_si256(ptr + i))); + } - cnt64 = (uint64_t*) &cnt; + cnt64 = (uint64_t*)&cnt; return cnt64[0] + cnt64[1] + @@ -453,10 +449,10 @@ static inline uint64_t popcnt_avx2(const __m256i* ptr, uint64_t size) #include #if !defined(_MSC_VER) - __attribute__ ((target ("avx512bw"))) +__attribute__((target("avx512bw"))) #endif -static inline __m512i popcnt512(__m512i v) -{ +static inline __m512i +popcnt512(__m512i v) { __m512i m1 = _mm512_set1_epi8(0x55); __m512i m2 = _mm512_set1_epi8(0x33); __m512i m4 = _mm512_set1_epi8(0x0F); @@ -472,10 +468,10 @@ static inline __m512i popcnt512(__m512i v) } #if !defined(_MSC_VER) - __attribute__ ((target ("avx512bw"))) +__attribute__((target("avx512bw"))) #endif -static inline void CSA512(__m512i* h, __m512i* l, __m512i a, __m512i b, __m512i c) -{ +static inline void +CSA512(__m512i* h, __m512i* l, __m512i a, __m512i b, __m512i c) { *l = _mm512_ternarylogic_epi32(c, b, a, 0x96); *h = _mm512_ternarylogic_epi32(c, b, a, 0xe8); } @@ -488,10 +484,10 @@ static inline void CSA512(__m512i* h, __m512i* l, __m512i a, __m512i b, __m512i * @see https://arxiv.org/abs/1611.07612 */ #if !defined(_MSC_VER) - __attribute__ ((target ("avx512bw"))) +__attribute__((target("avx512bw"))) #endif -static inline uint64_t popcnt_avx512(const __m512i* ptr, const uint64_t size) -{ +static inline uint64_t +popcnt_avx512(const __m512i* ptr, const uint64_t size) { __m512i cnt = _mm512_setzero_si512(); __m512i ones = _mm512_setzero_si512(); __m512i twos = _mm512_setzero_si512(); @@ -504,8 +500,7 @@ static inline uint64_t popcnt_avx512(const __m512i* ptr, const uint64_t size) uint64_t limit = size - size % 16; uint64_t* cnt64; - for(; i < limit; i += 16) - { + for (; i < limit; i += 16) { CSA512(&twosA, &ones, ones, _mm512_loadu_si512(ptr + i + 0), _mm512_loadu_si512(ptr + i + 1)); CSA512(&twosB, &ones, ones, _mm512_loadu_si512(ptr + i + 2), _mm512_loadu_si512(ptr + i + 3)); CSA512(&foursA, &twos, twos, twosA, twosB); @@ -531,10 +526,11 @@ static inline uint64_t popcnt_avx512(const __m512i* ptr, const uint64_t size) cnt = _mm512_add_epi64(cnt, _mm512_slli_epi64(popcnt512(twos), 1)); cnt = _mm512_add_epi64(cnt, popcnt512(ones)); - for(; i < size; i++) + for (; i < size; i++) { cnt = _mm512_add_epi64(cnt, popcnt512(_mm512_loadu_si512(ptr + i))); + } - cnt64 = (uint64_t*) &cnt; + cnt64 = (uint64_t*)&cnt; return cnt64[0] + cnt64[1] + @@ -556,11 +552,10 @@ static inline uint64_t popcnt_avx512(const __m512i* ptr, const uint64_t size) * @data: An array * @size: Size of data in bytes */ -static inline uint64_t popcnt(const void* data, uint64_t size) -{ +static inline uint64_t popcnt(const void* data, uint64_t size) { uint64_t i = 0; uint64_t cnt = 0; - const uint8_t* ptr = (const uint8_t*) data; + const uint8_t* ptr = (const uint8_t*)data; /* * CPUID runtime checks are only enabled if this is needed. @@ -568,86 +563,80 @@ static inline uint64_t popcnt(const void* data, uint64_t size) * code using -march=native on a CPU with AVX512. */ #if defined(HAVE_CPUID) - #if defined(__cplusplus) - /* C++11 thread-safe singleton */ - static const int cpuid = get_cpuid(); - #else - static int cpuid_ = -1; - int cpuid = cpuid_; - if (cpuid == -1) - { - cpuid = get_cpuid(); +#if defined(__cplusplus) + /* C++11 thread-safe singleton */ + static const int cpuid = get_cpuid(); +#else + static int cpuid_ = -1; + int cpuid = cpuid_; + if (cpuid == -1) { + cpuid = get_cpuid(); - #if defined(_MSC_VER) - _InterlockedCompareExchange(&cpuid_, cpuid, -1); - #else - __sync_val_compare_and_swap(&cpuid_, -1, cpuid); - #endif - } - #endif +#if defined(_MSC_VER) + _InterlockedCompareExchange(&cpuid_, cpuid, -1); +#else + __sync_val_compare_and_swap(&cpuid_, -1, cpuid); +#endif + } +#endif #endif #if defined(HAVE_AVX512) - #if defined(__AVX512__) || defined(__AVX512BW__) - /* AVX512 requires arrays >= 1024 bytes */ - if (i + 1024 <= size) - #else - if ((cpuid & bit_AVX512) && - i + 1024 <= size) - #endif - { - const __m512i* ptr512 = (const __m512i*)(ptr + i); - cnt += popcnt_avx512(ptr512, (size - i) / 64); - i = size - size % 64; - } +#if defined(__AVX512__) || defined(__AVX512BW__) + /* AVX512 requires arrays >= 1024 bytes */ + if (i + 1024 <= size) +#else + if ((cpuid & bit_AVX512) && + i + 1024 <= size) +#endif + { + const __m512i* ptr512 = (const __m512i*)(ptr + i); + cnt += popcnt_avx512(ptr512, (size - i) / 64); + i = size - size % 64; + } #endif #if defined(HAVE_AVX2) - #if defined(__AVX2__) - /* AVX2 requires arrays >= 512 bytes */ - if (i + 512 <= size) - #else - if ((cpuid & bit_AVX2) && - i + 512 <= size) - #endif - { - const __m256i* ptr256 = (const __m256i*)(ptr + i); - cnt += popcnt_avx2(ptr256, (size - i) / 32); - i = size - size % 32; - } +#if defined(__AVX2__) + /* AVX2 requires arrays >= 512 bytes */ + if (i + 512 <= size) +#else + if ((cpuid & bit_AVX2) && + i + 512 <= size) +#endif + { + const __m256i* ptr256 = (const __m256i*)(ptr + i); + cnt += popcnt_avx2(ptr256, (size - i) / 32); + i = size - size % 32; + } #endif #if defined(HAVE_POPCNT) - /* +/* * The user has compiled without -mpopcnt. * Unfortunately the MSVC compiler does not have * a POPCNT macro so we cannot get rid of the * runtime check for MSVC. */ - #if !defined(__POPCNT__) - if (cpuid & bit_POPCNT) - #endif - { - /* We use unaligned memory accesses here to improve performance */ - for (; i < size - size % 8; i += 8) - cnt += popcnt64(*(const uint64_t*)(ptr + i)); - for (; i < size; i++) - cnt += popcnt64(ptr[i]); +#if !defined(__POPCNT__) + if (cpuid & bit_POPCNT) +#endif + { #ifdef POPCNT_NO_UNALIGNED - // Address sanitizer will complain about unaligned accesses - // When we test with ASAN we enable aligned-only accesses - for (; ((uintptr_t)(ptr + i) % 8) && (i < size); i++) { - cnt += popcnt64(ptr[i]); - } + // Address sanitizer will complain about unaligned accesses + // When we test with ASAN we enable aligned-only accesses + for (; ((uintptr_t)(ptr + i) % 8) && (i < size); i++) { + cnt += popcnt64(ptr[i]); + } #endif - /* We use unaligned memory accesses here to improve performance */ - for (; (i + 8) <= size; i += 8) { - cnt += popcnt64(*(const uint64_t*)(ptr + i)); - } - for (; i < size; i++) { - cnt += popcnt64(ptr[i]); - } + /* We use unaligned memory accesses here to improve performance */ + for (; (i + 8) <= size; i += 8) { + cnt += popcnt64(*(const uint64_t*)(ptr + i)); + } + for (; i < size; i++) { + cnt += popcnt64(ptr[i]); + } return cnt; } @@ -726,7 +715,7 @@ static inline uint64_t popcnt(const void* data, uint64_t size) * and 31 * 8 bits = 248 which is OK. */ uint64_t limit = (i + 31 < iters) ? i + 31 : iters; - + /* Each iteration processes 64 bytes */ for (; i < limit; i++) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cd893599..d8c29d29 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,6 +4,7 @@ ########### include(FetchContent) +include(GoogleTest) if (NOT TARGET GTest::gtest) find_package(GTest) @@ -16,7 +17,6 @@ if (NOT TARGET GTest::gtest) # For Windows: Prevent overriding the parent project's compiler/linker settings set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) - include(GoogleTest) endif() endif() @@ -50,6 +50,13 @@ endif() target_include_directories(bitlib-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/inc) +if (BITLIB_TEST_WERROR) + if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang") + target_compile_options(bitlib-tests PRIVATE -Werror) + elseif (MSVC) + target_compile_options(bitlib-tests PRIVATE /WX) + endif() +endif() # specify test-specific libraries target_link_libraries(bitlib-tests PRIVATE bitlib GTest::gtest GTest::gtest_main) From bbc929dd99bc0653d8991db255caec7454708316 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Mon, 19 May 2025 22:56:12 -0400 Subject: [PATCH 64/85] Remove unused linear_overload class --- CMakeLists.txt | 1 - .../bitlib/bit-iterator/linear_overload.hpp | 242 ------------------ 2 files changed, 243 deletions(-) delete mode 100644 include/bitlib/bit-iterator/linear_overload.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d47ec40..061f11a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,6 @@ target_sources(bitlib INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/bit_iterator.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/bit_reference.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/bit_value.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-iterator/linear_overload.hpp ) target_compile_features(bitlib INTERFACE cxx_std_23) diff --git a/include/bitlib/bit-iterator/linear_overload.hpp b/include/bitlib/bit-iterator/linear_overload.hpp deleted file mode 100644 index f912708e..00000000 --- a/include/bitlib/bit-iterator/linear_overload.hpp +++ /dev/null @@ -1,242 +0,0 @@ -// ============================ LINEAR OVERLOAD ============================= // -// Project: The C++ Bit Library -// Name: linear_overload.hpp -// Description: Utilities to invoke the first valid call of an overload set -// Creator: Vincent Reverdy -// Contributor(s): Vincent Reverdy [2015-2017] -// License: BSD 3-Clause License -// ========================================================================== // -#ifndef _LINEAR_OVERLOAD_HPP_INCLUDED -#define _LINEAR_OVERLOAD_HPP_INCLUDED -// ========================================================================== // - - - -// ================================ PREAMBLE ================================ // -// C++ standard library -#include -#include -#include -// Project sources -// Third-party libraries -// Miscellaneous -namespace bit { -// ========================================================================== // - - - -/* **************************** LINEAR OVERLOAD ***************************** */ -// Linear overload class definition -template -class linear_overload -{ - // Types - public: - using tuple = std::tuple; - - // Lifecycle - public: - template - explicit constexpr linear_overload(G&&... g); - - // Access - public: - template - decltype(auto) get() noexcept; - template - constexpr decltype(auto) get() const noexcept; - template - decltype(auto) get() noexcept; - template - constexpr decltype(auto) get() const noexcept; - - // Capacity - public: - static constexpr bool empty() noexcept; - static constexpr std::size_t size() noexcept; - static constexpr std::size_t max_size() noexcept; - - // Call - public: - template < - std::size_t N = 0, - class... Args, - class = typename std::enable_if= size()>::type - > - void operator()(Args&&...); - template < - std::size_t N = 0, - class = typename std::enable_if::type, - class = decltype(std::get(std::declval())()) - > - decltype(auto) operator()(); - template < - std::size_t N = 0, - class Arg, - class... Args, - class = typename std::enable_if::type, - class = decltype(std::get(std::declval())( - std::declval(), - std::declval()... - )) - > - decltype(auto) operator()(Arg&& arg, Args&&... args); - template < - std::size_t N = 0, - class... Args, - class = typename std::enable_if::type - > - decltype(auto) operator()(Args&&... args); - - // Implementation details: data members - private: - tuple _f; - - // Maker - public: - template - friend constexpr linear_overload overload_linearly(G&&... g); -}; -/* ************************************************************************** */ - - - -// ----------------------- LINEAR OVERLOAD: LIFECYCLE ----------------------- // -// Explicitly constructs a linear overload from a list of functions -template -template -constexpr linear_overload::linear_overload( - G&&... g -) -: _f{std::forward(g)...} -{ -} -// -------------------------------------------------------------------------- // - - - -// ------------------------ LINEAR OVERLOAD: ACCESS ------------------------- // -// Gets the i-th function of the linear overload -template -template -decltype(auto) linear_overload::get( -) noexcept -{ - return std::get(_f); -} - -// Gets the i-th function of the immutable linear overload -template -template -constexpr decltype(auto) linear_overload::get( -) const noexcept -{ - return std::get(_f); -} - -// Gets the function of the given type from the linear overload -template -template -decltype(auto) linear_overload::get() noexcept -{ - return std::get(_f); -} - -// Gets the function of the given type from the immutable linear overload -template -template -constexpr decltype(auto) linear_overload::get( -) const noexcept -{ - return std::get(_f); -} -// -------------------------------------------------------------------------- // - - - -// ----------------------- LINEAR OVERLOAD: CAPACITY ------------------------ // -// Checks whether the linear overload is empty -template -constexpr bool linear_overload::empty( -) noexcept -{ - return std::tuple_size::value == 0; -} - -// Returns the number of functions in the linear overload -template -constexpr std::size_t linear_overload::size( -) noexcept -{ - return std::tuple_size::value; -} - -// Returns the maximum possible number of functions in the linear overload -template -constexpr std::size_t linear_overload::max_size( -) noexcept -{ - return std::tuple_size::value; -} -// -------------------------------------------------------------------------- // - - - -// ------------------------- LINEAR OVERLOAD: CALL -------------------------- // -// Calls the linear overload with the provided arguments: no valid overload -template -template -void linear_overload::operator()( - Args&&... -) -{ -} - -// Calls the linear overload with the provided arguments: no argument -template -template -decltype(auto) linear_overload::operator()( -) -{ - return std::get(_f)(); -} - -// Calls the linear overload with the provided arguments: valid call -template -template -decltype(auto) linear_overload::operator()( - Arg&& arg, - Args&&... args -) -{ - return std::get(_f)(std::forward(arg), std::forward(args)...); -} - -// Calls the linear overload with the provided arguments: invalid call -template -template -decltype(auto) linear_overload::operator()( - Args&&... args -) -{ - return operator()(std::forward(args)...); -} -// -------------------------------------------------------------------------- // - - - -// ------------------------- LINEAR OVERLOAD: MAKER ------------------------- // -// Builds a linear overload from a list of functions -template -constexpr linear_overload overload_linearly(G&&... g) -{ - return linear_overload(std::forward(g)...); -} -// -------------------------------------------------------------------------- // - - - -// ========================================================================== // -} // namespace bit -#endif // _LINEAR_OVERLOAD_HPP_INCLUDED -// ========================================================================== // From d45f839f085d81b7f926f53dc66f2db43cd68ab7 Mon Sep 17 00:00:00 2001 From: Peter McLean <112978670+Peter-McLean-Altera@users.noreply.github.com> Date: Fri, 23 May 2025 16:02:22 -0400 Subject: [PATCH 65/85] Add directly compatible copy constructor and assignment (#13) --- include/bitlib/bit-iterator/bit_iterator.hpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/include/bitlib/bit-iterator/bit_iterator.hpp b/include/bitlib/bit-iterator/bit_iterator.hpp index 90b4828a..f26fc8f2 100644 --- a/include/bitlib/bit-iterator/bit_iterator.hpp +++ b/include/bitlib/bit-iterator/bit_iterator.hpp @@ -57,6 +57,7 @@ class bit_iterator // Lifecycle public: constexpr bit_iterator(); + constexpr bit_iterator(const bit_iterator& other); template constexpr bit_iterator(const bit_iterator& other) requires std::constructible_from; @@ -67,6 +68,7 @@ class bit_iterator // Assignment public: + constexpr bit_iterator& operator=(const bit_iterator& other); template constexpr bit_iterator& operator=(const bit_iterator& other); @@ -125,6 +127,12 @@ constexpr bit_iterator::bit_iterator() } // Implicitly constructs a bit iterator from another bit iterator +template +constexpr bit_iterator::bit_iterator(const bit_iterator& other) + : _current(other.base()), _position(other.position()) { + assert(_position < bitsof()); +} + template template constexpr bit_iterator::bit_iterator(const bit_iterator& other) @@ -157,6 +165,16 @@ constexpr bit_iterator::bit_iterator(const pointer& ptr) // ------------------------ BIT ITERATOR: ASSIGNMENT ------------------------ // +// Assigns a bit iterator to the bit iterator +template +constexpr bit_iterator& bit_iterator::operator=( + const bit_iterator& other) { + _current = other._current; + _position = other._position; + assert(_position < bitsof()); + return *this; +} + // Assigns a bit iterator to the bit iterator template template From b5df9fb14081ddf8b793a23f55029b2d9519054f Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Sat, 24 May 2025 19:21:41 -0400 Subject: [PATCH 66/85] Add support for signed word types. (#14) * Add support for signed word types. Use logical shift right and avoid signed multiplication. Someone builtin_popcount is also sensitive to sign * Use common mask function to avoid signed word underflows * Allow _mask to optimize by default --- .github/actions/benchmark_epilogue/action.yml | 74 +- .github/workflows/cmake-multi-platform.yml | 5 + benchmark/CMakeLists.txt | 2 +- .../bit-algorithms/bit_algorithm_details.hpp | 220 +++-- include/bitlib/bit-algorithms/copy.hpp | 10 +- include/bitlib/bit-algorithms/count.hpp | 2 +- include/bitlib/bit-algorithms/equal.hpp | 14 +- include/bitlib/bit-algorithms/find.hpp | 26 +- include/bitlib/bit-algorithms/move.hpp | 10 +- include/bitlib/bit-algorithms/reverse.hpp | 11 +- include/bitlib/bit-algorithms/rotate.hpp | 17 +- include/bitlib/bit-algorithms/shift.hpp | 40 +- include/bitlib/bit-algorithms/transform.hpp | 25 +- .../bit-algorithms/transform_compare.hpp | 134 +++ include/bitlib/bit-iterator/bit_details.hpp | 834 +++++++++--------- include/bitlib/bit-iterator/bit_reference.hpp | 2 +- test/src/fixtures.hpp | 87 +- 17 files changed, 809 insertions(+), 704 deletions(-) create mode 100644 include/bitlib/bit-algorithms/transform_compare.hpp diff --git a/.github/actions/benchmark_epilogue/action.yml b/.github/actions/benchmark_epilogue/action.yml index c7222878..1b9b0d2e 100644 --- a/.github/actions/benchmark_epilogue/action.yml +++ b/.github/actions/benchmark_epilogue/action.yml @@ -1,6 +1,9 @@ name: "Benchmark Epilogue" description: "Processes coverage information with lcov and uploads it to coveralls/codecov" inputs: + workspace: + required: True + description: "Source code / workspace" compiler: required: True description: 'Compiler used to build benchmark' @@ -13,29 +16,64 @@ inputs: build-output-dir: required: true description: 'Build output directory' + base_ref: + required: true + description: 'Base ref for PR/comparison' + cpp_compiler: + required: true + description: 'Cpp compiler to use' + c_compiler: + required: true + description: 'C compiler to use' + build_type: + required: true + description: 'Build type (release/debug)' + runs: using: "composite" steps: - - name: Archive benchmark results - uses: actions/upload-artifact@v4 + - name: Checkout target branch (BASE) + uses: actions/checkout@v4 with: - name: benchmark_${{ inputs.os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_json - path: ${{ inputs.build-output-dir }}/benchmark/benchmark_result.json + ref: ${{ inputs.base_ref }} - # Download previous benchmark result from cache (if exists) - - name: Download previous benchmark data - uses: actions/cache@v4 + - name: Setup Python + uses: actions/setup-python@v5 with: - path: ./cache - key: benchmark_${{ inputs.os }}_${{ inputs.compiler }}_${{ inputs.stdlib }} + python-version: '3.11' + + - name: Install benchmark compare.py requirements + shell: bash + run: | + pip install --upgrade setuptools + pip install -r ${{ inputs.build-output-dir}}/_deps/benchmark-src/tools/requirements.txt + + - name: Benchmark Workflow + shell: bash + run: | + mv ${{ inputs.build-output-dir }}/benchmark/benchmark_result.json ${{ inputs.build-output-dir }}/benchmark/benchmark_result_new.json + cmake -B ${{ inputs.build-output-dir}} -S ${{ inputs.workspace }} \ + --preset benchmark_${{ inputs.os }}_${{ inputs.compiler }}_${{ inputs.stdlib }} \ + -DCMAKE_CXX_COMPILER=${{ inputs.cpp_compiler }} \ + -DCMAKE_C_COMPILER=${{ inputs.c_compiler }} \ + -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} + cmake --build ${{ inputs.build-output-dir}} --target benchmark --config ${{ inputs.build_type }} --parallel + ctest --test-dir ${{ inputs.build-output-dir}} --build-config ${{ inputs.build_type }} --output-on-failure --parallel + mv ${{ inputs.build-output-dir }}/benchmark/benchmark_result.json ${{ inputs.build-output-dir }}/benchmark/benchmark_result_ref.json + + - name: Compare Benchmarks + shell: bash + run: | + python3 ${{ inputs.build-output-dir}}/_deps/benchmark-src/tools/compare.py benchmarks \ + ${{ inputs.build-output-dir }}/benchmark/benchmark_result_ref.json \ + ${{ inputs.build-output-dir }}/benchmark/benchmark_result_new.json \ + | tee ${{ inputs.build-output-dir }}/benchmark/comparison.txt - - name: Store benchmark result - uses: benchmark-action/github-action-benchmark@v1 + - name: Archive benchmark results + uses: actions/upload-artifact@v4 with: - tool: 'googlecpp' - # Where the output from the benchmark tool is stored - output-file-path: ${{ inputs.build-output-dir }}/benchmark/benchmark_result.json - # Where the previous data file is stored - external-data-json-path: ./cache/benchmark_result.json - # Workflow will fail when an alert happens - fail-on-alert: true + name: benchmark_${{ inputs.os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_json + path: | + ${{ inputs.build-output-dir }}/benchmark/benchmark_result_ref.json + ${{ inputs.build-output-dir }}/benchmark/benchmark_result_new.json + ${{ inputs.build-output-dir }}/benchmark/comparison.txt diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index b73281b4..ce816881 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -175,6 +175,11 @@ jobs: - uses: ./.github/actions/benchmark_epilogue if: matrix.preset == 'benchmark' with: + workspace: ${{ github.workspace }} + cpp_compiler: ${{ matrix.cpp_compiler }} + c_compiler: ${{ matrix.c_compiler }} + build_type: ${{ matrix.build_type }} + base_ref: ${{ github.event.pull_request.base.ref }} os: ${{ matrix.os }} compiler: ${{ matrix.compiler }} stdlib: ${{ matrix.stdlib }} diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 3a09d660..709c3b21 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -97,5 +97,5 @@ target_link_libraries(bitlib-bench PRIVATE add_test( NAME BenchmarkTest - COMMAND $ --benchmark_format=json --benchmark_out=benchmark_result.json + COMMAND $ --benchmark_format=json --benchmark_out=benchmark_result.json --benchmark_min_warmup_time=0.001 --benchmark_min_time=0.01s ) diff --git a/include/bitlib/bit-algorithms/bit_algorithm_details.hpp b/include/bitlib/bit-algorithms/bit_algorithm_details.hpp index ab30bc3f..57e2fb87 100644 --- a/include/bitlib/bit-algorithms/bit_algorithm_details.hpp +++ b/include/bitlib/bit-algorithms/bit_algorithm_details.hpp @@ -105,17 +105,17 @@ constexpr bool is_within( template T get_word(bit_iterator first, size_t len=binary_digits::value) { - using native_word_type = typename bit_iterator::word_type; - constexpr T digits = binary_digits::value; - assert(digits >= len); - using non_const_T = std::remove_cv_t; - non_const_T offset = digits - first.position(); - non_const_T ret_word = *first.base() >> first.position(); - - // We've already assigned enough bits - if (len <= offset) { - return ret_word; - } + using native_word_type = typename bit_iterator::word_type; + constexpr T digits = binary_digits::value; + assert(digits >= len); + using non_const_T = std::remove_cv_t; + non_const_T offset = digits - first.position(); + non_const_T ret_word = lsr(*first.base(), first.position()); + + // We've already assigned enough bits + if (len <= offset) { + return ret_word; + } InputIt it = std::next(first.base()); len -= offset; @@ -214,63 +214,57 @@ void write_word(src_type src, bit_iterator dst_bit_it, src_type len=binary_digits::value ) { - using dst_type = typename bit_iterator::word_type; - constexpr dst_type dst_digits = binary_digits::value; - constexpr dst_type src_digits = binary_digits::value; - - if constexpr (dst_digits >= src_digits) { - if (dst_bit_it.position() == 0 && len == dst_digits) { - *dst_bit_it.base() = src; - } - else { - *dst_bit_it.base() = _bitblend( - *dst_bit_it.base(), - src << dst_bit_it.position(), - dst_bit_it.position(), - std::min( - dst_digits - dst_bit_it.position(), - len - ) - ); - if (len > dst_digits - dst_bit_it.position()) { - OutputIt overflow_dst = std::next(dst_bit_it.base()); - *overflow_dst = _bitblend( - *overflow_dst, - src >> (dst_digits - dst_bit_it.position()), - 0, - len - (dst_digits - dst_bit_it.position()) - ); - } - } + using dst_type = typename bit_iterator::word_type; + constexpr dst_type dst_digits = binary_digits::value; + constexpr dst_type src_digits = binary_digits::value; + + if constexpr (dst_digits >= src_digits) { + if (dst_bit_it.position() == 0 && len == dst_digits) { + *dst_bit_it.base() = src; } else { - OutputIt it = dst_bit_it.base(); - if (dst_bit_it.position() != 0) { - *it = _bitblend( - *it, - static_cast(src), - static_cast(-1) << dst_bit_it.position() - ); - len -= dst_digits - dst_bit_it.position(); - // TODO would it be faster to jsut shift src every time it is - // passed as an argument and keep track of how much we need to - // shift? - src >>= dst_digits - dst_bit_it.position(); - ++it; - } - while (len >= dst_digits) { - *it = static_cast(src); - src >>= dst_digits; - len -= dst_digits; - ++it; - } - if (len > 0 ) { - *it = _bitblend( - *it, - static_cast(src), - (1 << len) - 1 - ); - } + *dst_bit_it.base() = _bitblend( + *dst_bit_it.base(), + src << dst_bit_it.position(), + dst_bit_it.position(), + std::min( + dst_digits - dst_bit_it.position(), + len)); + if (len > dst_digits - dst_bit_it.position()) { + OutputIt overflow_dst = std::next(dst_bit_it.base()); + *overflow_dst = _bitblend( + *overflow_dst, + lsr(src, (dst_digits - dst_bit_it.position())), + 0, + len - (dst_digits - dst_bit_it.position())); + } + } + } else { + OutputIt it = dst_bit_it.base(); + if (dst_bit_it.position() != 0) { + *it = _bitblend( + *it, + static_cast(src), + static_cast(-1) << dst_bit_it.position()); + len -= dst_digits - dst_bit_it.position(); + // TODO would it be faster to jsut shift src every time it is + // passed as an argument and keep track of how much we need to + // shift? + src = lsr(src, dst_digits - dst_bit_it.position()); + ++it; } + while (len >= dst_digits) { + *it = static_cast(src); + src = lsr(src, dst_digits); + len -= dst_digits; + ++it; + } + if (len > 0) { + *it = _bitblend( + *it, + static_cast(src), + _mask(len)); + } + } return; } @@ -367,56 +361,56 @@ WordType _shift_towards_msb(WordType word, std::size_t n) { * is undefined */ template +[[deprecated("Unused")]] typename bit_iterator::word_type _padded_read(bit_iterator first, - bit_iterator last, const bit::bit_value bv) { - - using word_type = typename bit_iterator::word_type; - - constexpr std::size_t num_digits = binary_digits::value; - const std::size_t first_position = first.position(); - const std::size_t last_position = last.position(); - const word_type read = *(first.base()); - constexpr word_type all_ones = _all_ones(); - - word_type mask; - - if (_is_aligned_lsb(first)) { - if (_in_same_word(first, last)) { - // Case 1 - if (bv == bit0) { - mask = _shift_towards_lsb(all_ones, num_digits - last_position); - return read & mask; - } else { - mask = _shift_towards_msb(all_ones, last_position); - return read | mask; - } - } else { - // Case 0 - return read; - } + bit_iterator last, const bit::bit_value bv) { + using word_type = typename bit_iterator::word_type; + + constexpr std::size_t num_digits = binary_digits::value; + const std::size_t first_position = first.position(); + const std::size_t last_position = last.position(); + const word_type read = *(first.base()); + constexpr word_type all_ones = _all_ones(); + + word_type mask; + + if (_is_aligned_lsb(first)) { + if (_in_same_word(first, last)) { + // Case 1 + if (bv == bit0) { + mask = _shift_towards_lsb(all_ones, num_digits - last_position); + return read & mask; + } else { + mask = _shift_towards_msb(all_ones, last_position); + return read | mask; + } + } else { + // Case 0 + return read; + } + } else { + if (!_in_same_word(first, last)) { + // Case 2 + if (bv == bit0) { + mask = _shift_towards_msb(all_ones, first_position); + return read & mask; + } else { + mask = _shift_towards_lsb(all_ones, num_digits - first_position); + return read | mask; + } } else { - if (!_in_same_word(first, last)) { - // Case 2 - if (bv == bit0) { - mask = _shift_towards_msb(all_ones, first_position); - return read & mask; - } else { - mask = _shift_towards_lsb(all_ones, num_digits - first_position); - return read | mask; - } - } else { - // Case 3 - if (bv == bit0) { - mask = _shift_towards_msb(all_ones, first_position); - mask &= _shift_towards_lsb(all_ones, num_digits - last_position); - return read & mask; - } else { - mask = _shift_towards_lsb(all_ones, num_digits - first_position); - mask |= _shift_towards_msb(all_ones, last_position); - return read | mask; - } - } + // Case 3 + if (bv == bit0) { + mask = _shift_towards_msb(all_ones, first_position); + mask &= _shift_towards_lsb(all_ones, num_digits - last_position); + return read & mask; + } else { + mask = _shift_towards_lsb(all_ones, num_digits - first_position); + mask |= _shift_towards_msb(all_ones, last_position); + return read | mask; + } } + } } // -------------------------------------------------------------------------- // diff --git a/include/bitlib/bit-algorithms/copy.hpp b/include/bitlib/bit-algorithms/copy.hpp index d4846026..9ecb336a 100644 --- a/include/bitlib/bit-algorithms/copy.hpp +++ b/include/bitlib/bit-algorithms/copy.hpp @@ -101,12 +101,10 @@ constexpr bit_iterator copy(bit_iterator first } } if (remaining_bits_to_copy > 0) { - *it = _bitblend( - *it, - get_word(first, remaining_bits_to_copy), - static_cast( - (static_cast(1) << remaining_bits_to_copy) - 1) - ); + *it = _bitblend( + *it, + get_word(first, remaining_bits_to_copy), + _mask(remaining_bits_to_copy)); } } return d_first + total_bits_to_copy; diff --git a/include/bitlib/bit-algorithms/count.hpp b/include/bitlib/bit-algorithms/count.hpp index 3f02be01..485a7750 100644 --- a/include/bitlib/bit-algorithms/count.hpp +++ b/include/bitlib/bit-algorithms/count.hpp @@ -55,7 +55,7 @@ count( iterator_type it = first.base(); if (first.position() != 0) { - word_type first_value = *first.base() >> first.position(); + word_type first_value = lsr(*first.base(), first.position()); result = _popcnt(first_value); ++it; } diff --git a/include/bitlib/bit-algorithms/equal.hpp b/include/bitlib/bit-algorithms/equal.hpp index 004c12bf..a733a625 100644 --- a/include/bitlib/bit-algorithms/equal.hpp +++ b/include/bitlib/bit-algorithms/equal.hpp @@ -56,9 +56,7 @@ constexpr bool equal( const size_type partial_bits_to_check = ::std::min( remaining_bits_to_check, digits - d_first.position()); - const word_type mask = static_cast( - (static_cast(1) << partial_bits_to_check) - 1 - ) << d_first.position(); + const word_type mask = _mask(partial_bits_to_check) << d_first.position(); const word_type comp = static_cast( get_word(first, partial_bits_to_check) << d_first.position()); @@ -88,11 +86,11 @@ constexpr bool equal( } } if (remaining_bits_to_check > 0) { - const word_type mask = static_cast( - (static_cast(1) << remaining_bits_to_check) - 1 - ); - const word_type comp = get_word(first, remaining_bits_to_check); - if ((mask & *it) != (mask & comp)) { return false; } + const word_type mask = _mask(remaining_bits_to_check); + const word_type comp = get_word(first, remaining_bits_to_check); + if ((mask & *it) != (mask & comp)) { + return false; + } } } return true; diff --git a/include/bitlib/bit-algorithms/find.hpp b/include/bitlib/bit-algorithms/find.hpp index b35583c7..0227d3d0 100644 --- a/include/bitlib/bit-algorithms/find.hpp +++ b/include/bitlib/bit-algorithms/find.hpp @@ -46,17 +46,17 @@ constexpr bit_iterator find( if (!is_first_aligned) { - word_type shifted_first = *first.base() >> first.position(); - size_type num_trailing_complementary_bits = (bv == bit0) - ? _tzcnt(static_cast(~shifted_first)) - : _tzcnt(static_cast(shifted_first)); - if (std::next(first.base(), is_last_aligned) == last.base()) { - return first + std::min(num_trailing_complementary_bits, (size_type) distance(first, last)); - } else if (num_trailing_complementary_bits + first.position() < digits) { - return first + num_trailing_complementary_bits; - } else { - first += digits - first.position(); - } + word_type shifted_first = lsr(*first.base(), first.position()); + size_type num_trailing_complementary_bits = (bv == bit0) + ? _tzcnt(static_cast(~shifted_first)) + : _tzcnt(static_cast(shifted_first)); + if (std::next(first.base(), is_last_aligned) == last.base()) { + return first + std::min(num_trailing_complementary_bits, static_cast(distance(first, last))); + } else if (num_trailing_complementary_bits + first.position() < digits) { + return first + num_trailing_complementary_bits; + } else { + first += digits - first.position(); + } } // Initialization @@ -109,7 +109,7 @@ constexpr bit_iterator find( size_type num_trailing_complementary_bits = (bv == bit0) ? _tzcnt(static_cast(~*it)) : _tzcnt(static_cast(*it)); - return bit_iterator(it, (size_type) num_trailing_complementary_bits); + return bit_iterator(it, static_cast(num_trailing_complementary_bits)); } // Deal with any unaligned boundaries @@ -117,7 +117,7 @@ constexpr bit_iterator find( size_type num_trailing_complementary_bits = (bv == bit0) ? _tzcnt(static_cast(~*it)) : _tzcnt(static_cast(*it)); - return bit_iterator(it, (size_type) std::min(num_trailing_complementary_bits, last.position())); + return bit_iterator(it, static_cast(std::min(num_trailing_complementary_bits, last.position()))); } return last; } diff --git a/include/bitlib/bit-algorithms/move.hpp b/include/bitlib/bit-algorithms/move.hpp index 0f0e09e4..aa31813d 100644 --- a/include/bitlib/bit-algorithms/move.hpp +++ b/include/bitlib/bit-algorithms/move.hpp @@ -87,12 +87,10 @@ constexpr bit_iterator move(bit_iterator first } } if (remaining_bits_to_move > 0) { - *it = _bitblend( - *it, - get_word(first, remaining_bits_to_move), - static_cast( - (static_cast(1) << remaining_bits_to_move) - 1) - ); + *it = _bitblend( + *it, + get_word(first, remaining_bits_to_move), + _mask(remaining_bits_to_move)); } } return d_first + total_bits_to_move; diff --git a/include/bitlib/bit-algorithms/reverse.hpp b/include/bitlib/bit-algorithms/reverse.hpp index 37dacb7c..582b24c0 100644 --- a/include/bitlib/bit-algorithms/reverse.hpp +++ b/include/bitlib/bit-algorithms/reverse.hpp @@ -101,12 +101,11 @@ constexpr void reverse( } // Reverse when bit iterators belong to the same underlying word } else { - *it = _bitblend( - *it, - _bitswap(*it >> first.position()) >> gap, - first.position(), - last.position() - first.position() - ); + *it = _bitblend( + *it, + lsr(_bitswap(lsr(*it, first.position())), gap), + first.position(), + last.position() - first.position()); } } diff --git a/include/bitlib/bit-algorithms/rotate.hpp b/include/bitlib/bit-algorithms/rotate.hpp index cbcb038a..1d5ecb28 100644 --- a/include/bitlib/bit-algorithms/rotate.hpp +++ b/include/bitlib/bit-algorithms/rotate.hpp @@ -208,23 +208,20 @@ bit_iterator rotate( // Within the same word if (std::next(first.base(), is_last_aligned) == last.base()) { if (is_first_aligned && is_last_aligned) { - *first.base() = - (*first.base() >> n_first.position()) - | - static_cast( - *first.base() << (digits - n_first.position()) - ); - return std::next(first, digits - n_first.position()); + *first.base() = + (lsr(*first.base(), n_first.position())) | + static_cast( + *first.base() << (digits - n_first.position())); + return std::next(first, digits - n_first.position()); } else { size_type last_pos = is_last_aligned ? digits : last.position(); size_type k = n_first.position() - first.position(); size_type p = last_pos - n_first.position(); size_type d = last_pos - first.position(); - word_type mask = ((1ULL << d) - 1) << first.position(); + word_type mask = _mask(d) << first.position(); word_type rotated = *first.base() & mask; - rotated = static_cast(rotated >> k) - | static_cast(rotated << p); + rotated = static_cast(lsr(rotated, k)) | static_cast(rotated << p); *first.base() = _bitblend( *first.base(), rotated, diff --git a/include/bitlib/bit-algorithms/shift.hpp b/include/bitlib/bit-algorithms/shift.hpp index 57c5308d..1d1bf3a6 100644 --- a/include/bitlib/bit-algorithms/shift.hpp +++ b/include/bitlib/bit-algorithms/shift.hpp @@ -73,21 +73,23 @@ bit_iterator shift_left( // Single word case // Triggered if all relevant bits are in first.base() + // clang-format off if (std::next(first.base(), is_last_aligned) == last.base()) { *first.base() = _bitblend( *first.base(), - (( + lsr( *first.base() & ( - static_cast(-1) >> ( + lsr(static_cast(-1), ( digits - (is_last_aligned ? digits : last.position()) - ) + )) ) - )) >> n, + , n), first.position(), (is_last_aligned ? digits : last.position()) - first.position() ); return first + d - n; } + // clang-format on // Triggered if all remaining bits can fit in a word if (d - n <= digits) @@ -113,7 +115,7 @@ bit_iterator shift_left( const int n2 = digits - first.position() - n1; *first.base() = _bitblend( *first.base(), - (*middle.base()) >> (middle.position() - first.position()), + lsr(*middle.base(), (middle.position() - first.position())), first.position(), n1); *first.base() = _bitblend( @@ -131,13 +133,12 @@ bit_iterator shift_left( const int bits_left = last.position() - middle.position(); if (bits_left > 0) { - *first.base() = _bitblend( - *first.base(), - *middle.base() >> middle.position(), - 0, - bits_left - ); - first += bits_left; + *first.base() = _bitblend( + *first.base(), + lsr(*middle.base(), middle.position()), + 0, + bits_left); + first += bits_left; } // https://en.cppreference.com/w/cpp/algorithm/shift // "Elements that are in the original range but not the new range @@ -205,14 +206,13 @@ bit_iterator shift_left( // If middle is now penultimate word if (std::next(middle.base()) == last.base()) { - *first.base() = _bitblend( - *first.base(), - *middle.base() >> offset, - 0, - digits - offset - ); - first += digits - offset; - middle += digits - offset; + *first.base() = _bitblend( + *first.base(), + lsr(*middle.base(), offset), + 0, + digits - offset); + first += digits - offset; + middle += digits - offset; } if (!is_last_aligned) diff --git a/include/bitlib/bit-algorithms/transform.hpp b/include/bitlib/bit-algorithms/transform.hpp index 88577882..ac3de218 100644 --- a/include/bitlib/bit-algorithms/transform.hpp +++ b/include/bitlib/bit-algorithms/transform.hpp @@ -116,12 +116,10 @@ constexpr bit_iterator transform( } } if (remaining_bits_to_op > 0) { - *it = _bitblend( - *it, - unary_op(get_word(first, remaining_bits_to_op)), - static_cast( - (static_cast(1) << remaining_bits_to_op) - 1) - ); + *it = _bitblend( + *it, + unary_op(get_word(first, remaining_bits_to_op)), + _mask(remaining_bits_to_op)); } } return d_first + total_bits_to_op; @@ -197,15 +195,12 @@ constexpr bit_iterator transform( } } if (remaining_bits_to_op > 0) { - *it = _bitblend( - *it, - binary_op( - get_word(first1, remaining_bits_to_op), - get_word(first2, remaining_bits_to_op) - ), - static_cast( - (static_cast(1) << remaining_bits_to_op) - 1) - ); + *it = _bitblend( + *it, + binary_op( + get_word(first1, remaining_bits_to_op), + get_word(first2, remaining_bits_to_op)), + _mask(remaining_bits_to_op)); } } return d_first + total_bits_to_op; diff --git a/include/bitlib/bit-algorithms/transform_compare.hpp b/include/bitlib/bit-algorithms/transform_compare.hpp new file mode 100644 index 00000000..fb0e177d --- /dev/null +++ b/include/bitlib/bit-algorithms/transform_compare.hpp @@ -0,0 +1,134 @@ +// ================================= EQUAL =================================== // +// Project: The Experimental Bit Algorithms Library +// Name: equal.hpp +// Contributor: Bryce Kille [2019] +// License: BSD 3-Clause License +// ========================================================================== // +#ifndef _EQUAL_HPP_INCLUDED +#define _EQUAL_HPP_INCLUDED +// ========================================================================== // + + + +// ================================ PREAMBLE ================================ // +// C++ standard library +#include +#include +// Project sources +#include "bitlib/bit-iterator/bit.hpp" +// Third-party libraries +// Miscellaneous +namespace bit { +// ========================================================================== // + + + +// ---------------------------- Equal Algorithms ----------------------------- // + +// Status: Does not work for Input/Output iterators due to distance call +template +constexpr bool transform_compare( + bit_iterator first, + bit_iterator last, + bit_iterator first2, + BinaryComparison binary_cmp +) +{ + // Types and constants + using dst_word_type = typename bit_iterator::word_type; + using src_word_type = typename bit_iterator::word_type; + using word_type = dst_word_type; + using size_type = typename bit_iterator::size_type; + constexpr size_type digits = binary_digits::value; + + // Assertions + _assert_range_viability(first, last); + static_assert(::std::is_same::value, "Underlying word types must be equal"); + if (first == last) return true; + + // Initialization + size_type total_bits_to_check = distance(first, last); + size_type remaining_bits_to_check = total_bits_to_check; + + if (remaining_bits_to_check < digits) { + + } + + auto it1 = first1.base() + total_bits_to_check - 1 - digits; + auto it2 = first2.base() + total_bits_to_check - 1 - digits; + const bool is_d_first_aligned = it1.position() == 0; + + + bitsof< + // d_first is not aligned. + if (!is_d_first_aligned) { + + size_type partial_bits_to_op = ::std::min( + remaining_bits_to_op, + digits - d_first.position() + ); + + if (!binary_cmp( + static_cast( + get_word(it1, partial_bits_to_op) + << static_cast(d_first.position()) + ), + static_cast( + get_word(first2, partial_bits_to_op) + << static_cast(d_first.position()) + ) + )) { return false;} + remaining_bits_to_op -= partial_bits_to_op; + advance(first1, partial_bits_to_op); + advance(first2, partial_bits_to_op); + it++; + const size_type partial_bits_to_check = ::std::min( + remaining_bits_to_check, + digits - d_first.position()); + const word_type mask = _mask(partial_bits_to_check) << d_first.position(); + const word_type comp = static_cast( + get_word(first, partial_bits_to_check) + << d_first.position()); + if ((mask & *it) != (mask & comp)) { return false; } + remaining_bits_to_check -= partial_bits_to_check; + advance(first, partial_bits_to_check); + it++; + } + + if (remaining_bits_to_check > 0) { + const bool is_first_aligned = first.position() == 0; + // d_first will be aligned at this point + if (is_first_aligned && remaining_bits_to_check >= digits) { + auto N = ::std::distance(first.base(), last.base()); + bool found_mismatch = !::std::equal(first.base(), last.base(), it); + if (found_mismatch) {return false;} + it += N; + first += digits * N; + remaining_bits_to_check -= digits * N; + } else { + // TODO benchmark if its faster to ::std::check the entire range then shift + while (remaining_bits_to_check >= digits) { + if (*it != get_word(first, digits)) {return false;} + remaining_bits_to_check -= digits; + it++; + advance(first, digits); + } + } + if (remaining_bits_to_check > 0) { + const word_type mask = _mask(remaining_bits_to_check); + const word_type comp = get_word(first, remaining_bits_to_check); + if ((mask & *it) != (mask & comp)) { + return false; + } + } + } + return true; +} +// -------------------------------------------------------------------------- // + + + +// ========================================================================== // +} // namespace bit +#endif // _EQUAL_HPP_INCLUDED +// ========================================================================== // diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index e1cc9385..7b2f79e1 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -28,6 +28,7 @@ #include #include +#include "bitlib/bit-containers/bit_bitsof.hpp" #include "bitlib/bit_concepts.hpp" // Project sources @@ -47,12 +48,11 @@ using bit_pointer = bit_iterator; // Binary digits structure definition // Implementation template: only instantiates static_asserts for non-byte types. template ::value> -struct binary_digits_impl : std::integral_constant::digits> -{ - static_assert(std::is_integral::value, "Type must be integral"); - static_assert(std::is_unsigned::value, "Type must be unsigned"); - static_assert(!std::is_same::value, "Type must not be bool"); - static_assert(!std::is_same::value, "Type must not be char"); +struct binary_digits_impl : std::integral_constant>::digits> { + static_assert(std::is_integral::value, "Type must be integral"); + //static_assert(std::is_unsigned::value, "Type must be unsigned"); + static_assert(!std::is_same::value, "Type must not be bool"); + static_assert(!std::is_same::value, "Type must not be char"); }; // Specialization for std::byte. @@ -395,440 +395,411 @@ template constexpr T _mulx(T src0, T src1, T* hi, X...) noexcept; /* ************************************************************************** */ +/* +Logical shift right +*/ +template +constexpr T lsr(const T& val, const size_type shift) { + return static_cast(static_cast>(val) >> shift); +} +enum class _mask_len { + unknown, + in_range +}; + +template +constexpr T _mask(const size_type len) { + constexpr std::make_unsigned_t one = std::make_unsigned_t(1); + if constexpr (len_in_range != _mask_len::unknown) { + return static_cast((one << len) - one); + } else { + // The digits_mask is solely here to prevent Undefined Sanitizer + // complaining about shift of len >= digits + // Note: on -O1 the (len & digits_mask) is optimized to simply (len) + constexpr std::make_unsigned_t digits_mask = bitsof() - one; + return static_cast((one << (len & digits_mask)) * (len < bitsof()) - one); + } +} // ------------- IMPLEMENTATION DETAILS: UTILITIES: ASSERTIONS -------------- // // If the range allows multipass iteration, checks if last - first >= 0 template -constexpr bool _assert_range_viability(Iterator first, Iterator last) -{ - using traits_t = std::iterator_traits; - using category_t = typename traits_t::iterator_category; - using multi_t = std::forward_iterator_tag; - constexpr bool is_multipass = std::is_base_of::value; - const bool is_viable = !is_multipass || std::distance(first, last) >= 0; - assert(is_viable); - return is_viable; +constexpr bool _assert_range_viability(Iterator first, Iterator last) { + using traits_t = std::iterator_traits; + using category_t = typename traits_t::iterator_category; + using multi_t = std::forward_iterator_tag; + constexpr bool is_multipass = std::is_base_of::value; + const bool is_viable = !is_multipass || std::distance(first, last) >= 0; + assert(is_viable); + return is_viable; } // -------------------------------------------------------------------------- // - - // --------- IMPLEMENTATION DETAILS: INSTRUCTIONS: POPULATION COUNT --------- // // Counts the number of bits set to 1 with compiler intrinsics template -constexpr T _popcnt(T src) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - if (digits <= std::numeric_limits::digits) { - src = __builtin_popcount(src); - } else if (digits <= std::numeric_limits::digits) { - src = __builtin_popcountl(src); - } else if (digits <= std::numeric_limits::digits) { - src = __builtin_popcountll(src); - } else { - src = _popcnt(src, std::ignore); - } - return src; +constexpr T _popcnt(T src) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + if (digits <= std::numeric_limits::digits) { + src = __builtin_popcount(static_cast>(src)); + } else if (digits <= std::numeric_limits::digits) { + src = __builtin_popcountl(static_cast>(src)); + } else if (digits <= std::numeric_limits::digits) { + src = __builtin_popcountll(static_cast>(src)); + } else { + src = _popcnt(src, std::ignore); + } + return src; } // Counts the number of bits set to 1 without compiler intrinsics template -constexpr T _popcnt(T src, X...) noexcept -{ - static_assert(binary_digits::value, ""); - T dst = T(); - for (dst = T(); src; src >>= 1) { - dst += src & 1; - } - return dst; +constexpr T _popcnt(T src, X...) noexcept { + static_assert(binary_digits::value, ""); + T dst = T(); + for (dst = T(); src; src = lsr(src, 1)) { + dst += src & 1; + } + return dst; } // -------------------------------------------------------------------------- // - - // ------- IMPLEMENTATION DETAILS: INSTRUCTIONS: LEADING ZEROS COUNT -------- // // Counts the number of leading zeros with compiler intrinsics template -constexpr T _lzcnt(T src) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - if (digits < std::numeric_limits::digits) { - dst = src ? __builtin_clz(src) - - (std::numeric_limits::digits - - digits) - : digits; - } else if (digits == std::numeric_limits::digits) { - dst = src ? __builtin_clz(src) : digits; - } else if (digits < std::numeric_limits::digits) { - dst = src ? __builtin_clzl(src) - - (std::numeric_limits::digits - - digits) - : digits; - } else if (digits == std::numeric_limits::digits) { - dst = src ? __builtin_clzl(src) : digits; - } else if (digits < std::numeric_limits::digits) { - dst = src ? __builtin_clzll(src) - - (std::numeric_limits::digits - - digits) - : digits; - } else if (digits == std::numeric_limits::digits) { - dst = src ? __builtin_clzll(src) : digits; - } else { - dst = _lzcnt(src, std::ignore); - } - return dst; +constexpr T _lzcnt(T src) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T dst = T(); + if (digits < std::numeric_limits::digits) { + dst = src ? __builtin_clz(src) - (std::numeric_limits::digits - digits) + : digits; + } else if (digits == std::numeric_limits::digits) { + dst = src ? __builtin_clz(src) : digits; + } else if (digits < std::numeric_limits::digits) { + dst = src ? __builtin_clzl(src) - (std::numeric_limits::digits - digits) + : digits; + } else if (digits == std::numeric_limits::digits) { + dst = src ? __builtin_clzl(src) : digits; + } else if (digits < std::numeric_limits::digits) { + dst = src ? __builtin_clzll(src) - (std::numeric_limits::digits - digits) + : digits; + } else if (digits == std::numeric_limits::digits) { + dst = src ? __builtin_clzll(src) : digits; + } else { + dst = _lzcnt(src, std::ignore); + } + return dst; } // Counts the number of leading zeros without compiler intrinsics template -constexpr T _lzcnt(T src, X...) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = src != T(); - while (src >>= 1) { - ++dst; - } - return digits - dst; +constexpr T _lzcnt(T src, X...) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T dst = src != T(); + while ((src = lsr(src, 1))) { + ++dst; + } + return digits - dst; } // -------------------------------------------------------------------------- // - - // ------- IMPLEMENTATION DETAILS: INSTRUCTIONS: TRAILING ZEROS COUNT ------- // // Counts the number of trailing zeros with compiler intrinsics template -constexpr T _tzcnt(T src) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - if (digits <= std::numeric_limits::digits) { - dst = src ? __builtin_ctz(src) : digits; - } else if (digits <= std::numeric_limits::digits) { - dst = src ? __builtin_ctzl(src) : digits; - } else if (digits <= std::numeric_limits::digits) { - dst = src ? __builtin_ctzll(src) : digits; - } else { - dst = _tzcnt(src, std::ignore); - } - return dst; +constexpr T _tzcnt(T src) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T dst = T(); + if (digits <= std::numeric_limits::digits) { + dst = src ? __builtin_ctz(src) : digits; + } else if (digits <= std::numeric_limits::digits) { + dst = src ? __builtin_ctzl(src) : digits; + } else if (digits <= std::numeric_limits::digits) { + dst = src ? __builtin_ctzll(src) : digits; + } else { + dst = _tzcnt(src, std::ignore); + } + return dst; } // Counts the number of trailing zeros without compiler intrinsics template -constexpr T _tzcnt(T src, X...) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = digits; - if (src) { - src = (src ^ (src - 1)) >> 1; - for (dst = T(); src; dst++) { - src >>= 1; - } +constexpr T _tzcnt(T src, X...) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T dst = digits; + if (src) { + src = lsr((src ^ (src - 1)), 1); + for (dst = T(); src; dst++) { + src = lsr(src, 1); } - return dst; + } + return dst; } // -------------------------------------------------------------------------- // - - // ------- IMPLEMENTATION DETAILS: INSTRUCTIONS: BIT FIELD EXTRACTION ------- // // Extacts to lsbs a field of contiguous bits with compiler intrinsics template -constexpr T _bextr(T src, T start, T len) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - if (digits <= std::numeric_limits::digits) { - dst = __builtin_ia32_bextr_u32(src, start, len); - } else if (digits <= std::numeric_limits::digits) { - dst = __builtin_ia32_bextr_u64(src, start, len); - } else { - dst = _bextr(src, start, len, std::ignore); - } - return dst; +constexpr T _bextr(T src, T start, T len) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T dst = T(); + if (digits <= std::numeric_limits::digits) { + dst = __builtin_ia32_bextr_u32(src, start, len); + } else if (digits <= std::numeric_limits::digits) { + dst = __builtin_ia32_bextr_u64(src, start, len); + } else { + dst = _bextr(src, start, len, std::ignore); + } + return dst; } // Extacts to lsbs a field of contiguous bits without compiler intrinsics template -constexpr T _bextr(T src, T start, T len, X...) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - constexpr T one = 1; - const T msk = (one << len) * (len < digits) - one; - return (src >> start) & msk * (start < digits); +constexpr T _bextr(T src, T start, T len, X...) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + constexpr T one = 1; + const T msk = (one << len) * (len < digits) - one; + return (lsr(src, start)) & msk * (start < digits); } // -------------------------------------------------------------------------- // - - // ------- IMPLEMENTATION DETAILS: INSTRUCTIONS: PARALLEL BIT DEPOSIT ------- // // Deposits bits according to a mask with compiler instrinsics template -constexpr T _pdep(T src, T msk) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - if (digits <= std::numeric_limits::digits) { - dst = _pdep_u32(src, msk); - } else if (digits <= std::numeric_limits::digits) { - dst = _pdep_u64(src, msk); - } else { - dst = _pdep(src, msk, std::ignore); - } - return dst; +constexpr T _pdep(T src, T msk) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T dst = T(); + if (digits <= std::numeric_limits::digits) { + dst = _pdep_u32(src, msk); + } else if (digits <= std::numeric_limits::digits) { + dst = _pdep_u64(src, msk); + } else { + dst = _pdep(src, msk, std::ignore); + } + return dst; } // Deposits bits according to a mask without compiler instrinsics template -constexpr T _pdep(T src, T msk, X...) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - T cnt = T(); - while (msk) { - dst >>= 1; - if (msk & 1) { - dst |= src << (digits - 1); - src >>= 1; - } - msk >>= 1; - ++cnt; +constexpr T _pdep(T src, T msk, X...) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T dst = T(); + T cnt = T(); + while (msk) { + dst = lsr(dst, 1); + if (msk & 1) { + dst |= src << (digits - 1); + src = lsr(src, 1); } - dst >>= (digits - cnt) * (cnt > 0); - return dst; + msk = lsr(msk, 1); + ++cnt; + } + dst = lsr(dst, (digits - cnt) * (cnt > 0)); + return dst; } // -------------------------------------------------------------------------- // - - // ------- IMPLEMENTATION DETAILS: INSTRUCTIONS: PARALLEL BIT EXTRACT ------- // // Extracts bits according to a mask with compiler instrinsics template -constexpr T _pext(T src, T msk) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - if (digits <= std::numeric_limits::digits) { - dst = _pext_u32(src, msk); - } else if (digits <= std::numeric_limits::digits) { - dst = _pext_u64(src, msk); - } else { - dst = _pext(src, msk, std::ignore); - } - return dst; +constexpr T _pext(T src, T msk) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T dst = T(); + if (digits <= std::numeric_limits::digits) { + dst = _pext_u32(src, msk); + } else if (digits <= std::numeric_limits::digits) { + dst = _pext_u64(src, msk); + } else { + dst = _pext(src, msk, std::ignore); + } + return dst; } // Extracts bits according to a mask without compiler instrinsics template -constexpr T _pext(T src, T msk, X...) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - T cnt = T(); - while (msk) { - if (msk & 1) { - dst >>= 1; - dst |= src << (digits - 1); - ++cnt; - } - src >>= 1; - msk >>= 1; +constexpr T _pext(T src, T msk, X...) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T dst = T(); + T cnt = T(); + while (msk) { + if (msk & 1) { + dst = lsr(dst, 1); + dst |= src << (digits - 1); + ++cnt; } - dst >>= (digits - cnt) * (cnt > 0); - return dst; + src = lsr(src, 1); + msk = lsr(msk, 1); + } + dst = lsr(dst, (digits - cnt) * (cnt > 0)); + return dst; } // -------------------------------------------------------------------------- // - - // ------------ IMPLEMENTATION DETAILS: INSTRUCTIONS: BYTE SWAP ------------- // // Reverses the order of the underlying bytes with compiler intrinsics template -constexpr T _byteswap(T src) noexcept -{ - static_assert(binary_digits::value, ""); - using byte_t = unsigned char; - constexpr T digits = sizeof(T) * std::numeric_limits::digits; - std::uint64_t tmp64 = 0; - std::uint64_t* ptr64 = nullptr; - if (std::is_same::value) { - ptr64 = reinterpret_cast(&src); - tmp64 = __builtin_bswap64(*ptr64); - *ptr64 = __builtin_bswap64(*(ptr64 + 1)); - *(ptr64 + 1) = tmp64; - } else if (digits == std::numeric_limits::digits) { - src = __builtin_bswap16(src); - } else if (digits == std::numeric_limits::digits) { - src = __builtin_bswap32(src); - } else if (digits == std::numeric_limits::digits) { - src = __builtin_bswap64(src); - } else if (digits > std::numeric_limits::digits) { - src = _byteswap(src, std::ignore); - } - return src; +constexpr T _byteswap(T src) noexcept { + static_assert(binary_digits::value, ""); + using byte_t = unsigned char; + constexpr T digits = sizeof(T) * std::numeric_limits::digits; + std::uint64_t tmp64 = 0; + std::uint64_t* ptr64 = nullptr; + if (std::is_same::value) { + ptr64 = reinterpret_cast(&src); + tmp64 = __builtin_bswap64(*ptr64); + *ptr64 = __builtin_bswap64(*(ptr64 + 1)); + *(ptr64 + 1) = tmp64; + } else if (digits == std::numeric_limits::digits) { + src = __builtin_bswap16(src); + } else if (digits == std::numeric_limits::digits) { + src = __builtin_bswap32(src); + } else if (digits == std::numeric_limits::digits) { + src = __builtin_bswap64(src); + } else if (digits > std::numeric_limits::digits) { + src = _byteswap(src, std::ignore); + } + return src; } // Reverses the order of the underlying bytes without compiler intrinsics template -constexpr T _byteswap(T src, X...) noexcept -{ - static_assert(binary_digits::value, ""); - using byte_t = unsigned char; - constexpr T half = sizeof(T) / 2; - constexpr T end = sizeof(T) - 1; - unsigned char* bytes = reinterpret_cast(&src); - unsigned char byte = 0; - for (T i = T(); i < half; ++i) { - byte = bytes[i]; - bytes[i] = bytes[end - i]; - bytes[end - i] = byte; - } - return src; +constexpr T _byteswap(T src, X...) noexcept { + static_assert(binary_digits::value, ""); + using byte_t = unsigned char; + constexpr T half = sizeof(T) / 2; + constexpr T end = sizeof(T) - 1; + unsigned char* bytes = reinterpret_cast(&src); + unsigned char byte = 0; + for (T i = T(); i < half; ++i) { + byte = bytes[i]; + bytes[i] = bytes[end - i]; + bytes[end - i] = byte; + } + return src; } // -------------------------------------------------------------------------- // - - // ------------- IMPLEMENTATION DETAILS: INSTRUCTIONS: BIT SWAP ------------- // // Reverses the order of the bits with or without of compiler intrinsics template -constexpr T _bitswap(T src) noexcept -{ - static_assert(binary_digits::value, ""); - using byte_t = unsigned char; - constexpr auto ignore = nullptr; - constexpr T digits = binary_digits::value; - constexpr unsigned long long int first = 0x80200802ULL; - constexpr unsigned long long int second = 0x0884422110ULL; - constexpr unsigned long long int third = 0x0101010101ULL; - constexpr unsigned long long int fourth = 32; - constexpr bool is_size1 = sizeof(T) == 1; - constexpr bool is_byte = digits == std::numeric_limits::digits; - constexpr bool is_octet = std::numeric_limits::digits == 8; - constexpr bool is_pow2 = _popcnt(digits, ignore) == 1; - T dst = src; - T i = digits - 1; - if (is_size1 && is_byte && is_octet) { - dst = ((src * first) & second) * third >> fourth; - } else if (is_pow2) { - dst = _bitswap(src); - } else { - for (src >>= 1; src; src >>= 1) { - dst <<= 1; - dst |= src & 1; - i--; - } - dst <<= i; +constexpr T _bitswap(T src) noexcept { + static_assert(binary_digits::value, ""); + using byte_t = unsigned char; + constexpr auto ignore = nullptr; + constexpr T digits = binary_digits::value; + constexpr unsigned long long int first = 0x80200802ULL; + constexpr unsigned long long int second = 0x0884422110ULL; + constexpr unsigned long long int third = 0x0101010101ULL; + constexpr unsigned long long int fourth = 32; + constexpr bool is_size1 = sizeof(T) == 1; + constexpr bool is_byte = digits == std::numeric_limits::digits; + constexpr bool is_octet = std::numeric_limits::digits == 8; + constexpr bool is_pow2 = _popcnt(digits, ignore) == 1; + T dst = src; + T i = digits - 1; + if (is_size1 && is_byte && is_octet) { + dst = static_cast(lsr(((static_cast>(src) * first) & second) * third, fourth)); + } else if (is_pow2) { + dst = _bitswap(src); + } else { + for (src = lsr(src, 1); src; src = lsr(src, 1)) { + dst <<= 1; + dst |= src & 1; + i--; } - return dst; + dst <<= i; + } + return dst; } // Reverses the order of the bits: recursive metafunction template -constexpr T _bitswap(T src) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T cnt = N >> 1; - constexpr T msk = _bitswap(); - src = ((src >> cnt) & msk) | ((src << cnt) & ~msk); - return cnt > 1 ? _bitswap(src) : src; +constexpr T _bitswap(T src) noexcept { + static_assert(binary_digits::value, ""); + constexpr T cnt = N >> 1; + constexpr T msk = _bitswap(); + src = ((lsr(src, cnt)) & msk) | ((src << cnt) & ~msk); + return cnt > 1 ? _bitswap(src) : src; } // Reverses the order of the bits: mask for the recursive metafunction template -constexpr T _bitswap() noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T cnt = digits; - T msk = ~T(); - while (cnt != N) { - cnt >>= 1; - msk ^= (msk << cnt); - } - return msk; +constexpr T _bitswap() noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T cnt = digits; + T msk = ~T(); + while (cnt != N) { + cnt = lsr(cnt, 1); + msk ^= (msk << cnt); + } + return msk; } // -------------------------------------------------------------------------- // - - // ------------ IMPLEMENTATION DETAILS: INSTRUCTIONS: BIT BLEND ------------- // // Replaces bits of src0 by the ones of src1 where the mask is true template -constexpr T _bitblend(T src0, T src1, T msk) noexcept -{ - static_assert(binary_digits::value, ""); - return src0 ^ ((src0 ^ src1) & msk); +constexpr T _bitblend(T src0, T src1, T msk) noexcept { + static_assert(binary_digits::value, ""); + return src0 ^ ((src0 ^ src1) & msk); } // Replaces len bits of src0 by the ones of src1 starting at start template -constexpr T _bitblend(T src0, T src1, T start, T len) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - constexpr T one = 1; - // The digits_mask is solely here to prevent Undefined Sanitizer - // complaining about shift of len >= digits - // Note: on -O1 the (len & digits_mask) is optimized to simply (len) - constexpr T digits_mask = digits - one; - const T msk = ((one << (len & digits_mask)) * (len < digits) - one) << start; - return src0 ^ ((src0 ^ src1) & msk * (start < digits)); +constexpr T _bitblend(T src0, T src1, T start, T len) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + const T msk = _mask(len) << start; + return src0 ^ ((src0 ^ src1) & msk * (start < digits)); } // -------------------------------------------------------------------------- // - - // ---------- IMPLEMENTATION DETAILS: INSTRUCTIONS: BIT EXCHANGE ------------ // // Exchanges/swaps bits of src0 by the ones of src1 where the mask is true template -constexpr void _bitexch(T& src0, T& src1, T msk) noexcept -{ - src0 = src0 ^ static_cast(src1 & msk); - src1 = src1 ^ static_cast(src0 & msk); - src0 = src0 ^ static_cast(src1 & msk); - return; +constexpr void _bitexch(T& src0, T& src1, T msk) noexcept { + src0 = src0 ^ static_cast(src1 & msk); + src1 = src1 ^ static_cast(src0 & msk); + src0 = src0 ^ static_cast(src1 & msk); + return; } // Replaces len bits of src0 by the ones of src1 starting at start template -constexpr void _bitexch(T& src0, T& src1, S start, S len) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr auto digits = binary_digits::value; - constexpr T one = 1; - const T msk = (len < digits) - ? ((one << len) - one) << start : -1; - src0 = src0 ^ static_cast(src1 & msk); - src1 = src1 ^ static_cast(src0 & msk); - src0 = src0 ^ static_cast(src1 & msk); - return; +constexpr void _bitexch(T& src0, T& src1, S start, S len) noexcept { + static_assert(binary_digits::value, ""); + constexpr auto digits = binary_digits::value; + const T msk = (len < digits) + ? _mask(len) << start + : -1; // TODO: What if start > 0 here? + src0 = src0 ^ static_cast(src1 & msk); + src1 = src1 ^ static_cast(src0 & msk); + src0 = src0 ^ static_cast(src1 & msk); + return; } // Replaces len bits of src0 by the ones of src1 starting at start0 // in src0 and start1 in src1. // len <= digits-max(start0, start1) +// clang-format off template constexpr void _bitexch(T& src0, T& src1, S start0, S start1, S len) noexcept { static_assert(binary_digits::value, ""); constexpr auto digits = binary_digits::value; - constexpr T one = 1; - const T msk = (len < digits) ? - ((one << len) - one) : -1; + const T msk = _mask(len); if (start0 >= start1) { src0 = src0 ^ ( static_cast(src1 << (start0 - start1)) @@ -836,7 +807,7 @@ constexpr void _bitexch(T& src0, T& src1, S start0, S start1, S len) noexcept static_cast(msk << start0) ); src1 = src1 ^ ( - static_cast(src0 >> (start0 - start1)) + static_cast(lsr(src0, (start0 - start1))) & static_cast(msk << start1) ); @@ -847,7 +818,7 @@ constexpr void _bitexch(T& src0, T& src1, S start0, S start1, S len) noexcept ); } else { src0 = src0 ^ ( - static_cast(src1 >> (start1 - start0)) + static_cast(lsr(src1, (start1 - start0))) & static_cast(msk << start0) ); @@ -857,173 +828,154 @@ constexpr void _bitexch(T& src0, T& src1, S start0, S start1, S len) noexcept static_cast(msk << start1) ); src0 = src0 ^ ( - static_cast(src1 >> (start1 - start0)) + static_cast(lsr(src1, (start1 - start0))) & static_cast(msk << start0) ); } return; } +// clang-format on // -------------------------------------------------------------------------- // - - // ----------- IMPLEMENTATION DETAILS: INSTRUCTIONS: BIT COMPARE ------------ // // Compares a subsequence of bits within src0 and src1 and returns 0 if equal template -constexpr T _bitcmp(T src0, T src1, T start0, T start1, T len) noexcept -{ - static_assert(binary_digits::value, ""); - return _bextr(src0, start0, len) == _bextr(src1, start1, len); +constexpr T _bitcmp(T src0, T src1, T start0, T start1, T len) noexcept { + static_assert(binary_digits::value, ""); + return _bextr(src0, start0, len) == _bextr(src1, start1, len); } // -------------------------------------------------------------------------- // - - // --- IMPLEMENTATION DETAILS: INSTRUCTIONS: DOUBLE PRECISION SHIFT LEFT ---- // // Left shifts dst by cnt bits, filling the lsbs of dst by the msbs of src template -constexpr T _shld(T dst, T src, T cnt) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - if (cnt < digits) { - dst = (dst << cnt) | (src >> (digits - cnt)); - } else { - dst = (src << (cnt - digits)) * (cnt < digits + digits); - } - return dst; +constexpr T _shld(T dst, T src, T cnt) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + if (cnt < digits) { + dst = (dst << cnt) | (lsr(src, (digits - cnt))); + } else { + dst = (src << (cnt - digits)) * (cnt < digits + digits); + } + return dst; } // -------------------------------------------------------------------------- // - - // --- IMPLEMENTATION DETAILS: INSTRUCTIONS: DOUBLE PRECISION SHIFT RIGHT --- // // Right shifts dst by cnt bits, filling the msbs of dst by the lsbs of src template -constexpr T _shrd(T dst, T src, T cnt) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - if (cnt < digits) { - dst = (dst >> cnt) | (src << (digits - cnt)); - } else { - dst = (src >> (cnt - digits)) * (cnt < digits + digits); - } - return dst; +constexpr T _shrd(T dst, T src, T cnt) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + if (cnt < digits) { + dst = (lsr(dst, cnt)) | (src << (digits - cnt)); + } else { + dst = (lsr(src, (cnt - digits))) * (cnt < digits + digits); + } + return dst; } // -------------------------------------------------------------------------- // - - // ------------ IMPLEMENTATION DETAILS: INSTRUCTIONS: ADD CARRY ------------- // // Adds src0 and src1 and returns the new carry bit with intrinsics template -constexpr C _addcarry(C carry, T src0, T src1, T* dst) noexcept -{ - static_assert(binary_digits::value, ""); - using wider_t = typename _wider_type::type; - constexpr T digits = binary_digits::value; - wider_t tmp = 0; - unsigned int udst = 0; - unsigned long long int ulldst = 0; - if (digits == std::numeric_limits::digits) { - carry = __builtin_ia32_addcarryx_u32(carry, src0, src1, &udst); - *dst = udst; - } else if (digits == std::numeric_limits::digits) { - carry = __builtin_ia32_addcarryx_u64(carry, src0, src1, &ulldst); - *dst = ulldst; - } else if (digits < binary_digits::value) { - tmp = static_cast(src0) + static_cast(src1); - tmp += static_cast(static_cast(carry)); - *dst = tmp; - carry = static_cast(tmp >> digits); - } else { - carry = _addcarry(carry, src0, src1, dst, std::ignore); - } - return carry; +constexpr C _addcarry(C carry, T src0, T src1, T* dst) noexcept { + static_assert(binary_digits::value, ""); + using wider_t = typename _wider_type::type; + constexpr T digits = binary_digits::value; + wider_t tmp = 0; + unsigned int udst = 0; + unsigned long long int ulldst = 0; + if (digits == std::numeric_limits::digits) { + carry = __builtin_ia32_addcarryx_u32(carry, src0, src1, &udst); + *dst = udst; + } else if (digits == std::numeric_limits::digits) { + carry = __builtin_ia32_addcarryx_u64(carry, src0, src1, &ulldst); + *dst = ulldst; + } else if (digits < binary_digits::value) { + tmp = static_cast(src0) + static_cast(src1); + tmp += static_cast(static_cast(carry)); + *dst = tmp; + carry = static_cast(tmp >> digits); + } else { + carry = _addcarry(carry, src0, src1, dst, std::ignore); + } + return carry; } // Adds src0 and src1 and returns the new carry bit without intrinsics template -constexpr C _addcarry(C carry, T src0, T src1, T* dst, X...) noexcept -{ - static_assert(binary_digits::value, ""); - *dst = src0 + src1 + static_cast(static_cast(carry)); - return carry ? *dst <= src0 || *dst <= src1 : *dst < src0 || *dst < src1; +constexpr C _addcarry(C carry, T src0, T src1, T* dst, X...) noexcept { + static_assert(binary_digits::value, ""); + *dst = src0 + src1 + static_cast(static_cast(carry)); + return carry ? *dst <= src0 || *dst <= src1 : *dst < src0 || *dst < src1; } // -------------------------------------------------------------------------- // - - // ------------ IMPLEMENTATION DETAILS: INSTRUCTIONS: SUB BORROW ------------ // // Subtracts src1 to src0 and returns the new borrow bit with intrinsics template -constexpr B _subborrow(B borrow, T src0, T src1, T* dst) noexcept -{ - static_assert(binary_digits::value, ""); - using wider_t = typename _wider_type::type; - constexpr T digits = binary_digits::value; - wider_t tmp = 0; - unsigned int udst = 0; - unsigned long long int ulldst = 0; - if (digits == std::numeric_limits::digits) { - borrow = __builtin_ia32_sbb_u32(borrow, src0, src1, &udst); - *dst = udst; - } else if (digits == std::numeric_limits::digits) { - borrow = __builtin_ia32_sbb_u64(borrow, src0, src1, &ulldst); - *dst = ulldst; - } else if (digits < binary_digits::value) { - tmp = static_cast(src1); - tmp += static_cast(static_cast(borrow)); - borrow = tmp > static_cast(src0); - *dst = static_cast(src0) - tmp; - } else { - borrow = _subborrow(borrow, src0, src1, dst, std::ignore); - } - return borrow; +constexpr B _subborrow(B borrow, T src0, T src1, T* dst) noexcept { + static_assert(binary_digits::value, ""); + using wider_t = typename _wider_type::type; + constexpr T digits = binary_digits::value; + wider_t tmp = 0; + unsigned int udst = 0; + unsigned long long int ulldst = 0; + if (digits == std::numeric_limits::digits) { + borrow = __builtin_ia32_sbb_u32(borrow, src0, src1, &udst); + *dst = udst; + } else if (digits == std::numeric_limits::digits) { + borrow = __builtin_ia32_sbb_u64(borrow, src0, src1, &ulldst); + *dst = ulldst; + } else if (digits < binary_digits::value) { + tmp = static_cast(src1); + tmp += static_cast(static_cast(borrow)); + borrow = tmp > static_cast(src0); + *dst = static_cast(src0) - tmp; + } else { + borrow = _subborrow(borrow, src0, src1, dst, std::ignore); + } + return borrow; } // Subtracts src1 to src0 and returns the new borrow bit with other intrinsics template -constexpr B _subborrow(const B& borrow, T src0, T src1, T* dst) noexcept -{ - static_assert(binary_digits::value, ""); - using wider_t = typename _wider_type::type; - constexpr T digits = binary_digits::value; - wider_t tmp = 0; - unsigned int udst = 0; - unsigned long long int ulldst = 0; - B flag = borrow; - if (digits == std::numeric_limits::digits) { - flag = __builtin_ia32_subborrow_u32(borrow, src0, src1, &udst); - *dst = udst; - } else if (digits == std::numeric_limits::digits) { - flag = __builtin_ia32_subborrow_u64(borrow, src0, src1, &ulldst); - *dst = ulldst; - } else if (digits < binary_digits::value) { - tmp = static_cast(src1); - tmp += static_cast(static_cast(borrow)); - flag = tmp > static_cast(src0); - *dst = static_cast(src0) - tmp; - } else { - flag = _subborrow(borrow, src0, src1, dst, std::ignore); - } - return flag; +constexpr B _subborrow(const B& borrow, T src0, T src1, T* dst) noexcept { + static_assert(binary_digits::value, ""); + using wider_t = typename _wider_type::type; + constexpr T digits = binary_digits::value; + wider_t tmp = 0; + unsigned int udst = 0; + unsigned long long int ulldst = 0; + B flag = borrow; + if (digits == std::numeric_limits::digits) { + flag = __builtin_ia32_subborrow_u32(borrow, src0, src1, &udst); + *dst = udst; + } else if (digits == std::numeric_limits::digits) { + flag = __builtin_ia32_subborrow_u64(borrow, src0, src1, &ulldst); + *dst = ulldst; + } else if (digits < binary_digits::value) { + tmp = static_cast(src1); + tmp += static_cast(static_cast(borrow)); + flag = tmp > static_cast(src0); + *dst = static_cast(src0) - tmp; + } else { + flag = _subborrow(borrow, src0, src1, dst, std::ignore); + } + return flag; } // Subtracts src1 to src0 and returns the new borrow bit without intrinsics template -constexpr B _subborrow(B borrow, T src0, T src1, T* dst, X...) noexcept -{ - static_assert(binary_digits::value, ""); - *dst = src0 - (src1 + static_cast(static_cast(borrow))); - return borrow ? src1 >= src0 : src1 > src0; +constexpr B _subborrow(B borrow, T src0, T src1, T* dst, X...) noexcept { + static_assert(binary_digits::value, ""); + *dst = src0 - (src1 + static_cast(static_cast(borrow))); + return borrow ? src1 >= src0 : src1 > src0; } // -------------------------------------------------------------------------- // - - // -------- IMPLEMENTATION DETAILS: INSTRUCTIONS: MULTIWORD MULTIPLY -------- // // Multiplies src0 and src1 and gets the full result with compiler intrinsics template @@ -1057,10 +1009,10 @@ constexpr T _mulx(T src0, T src1, T* hi, X...) noexcept constexpr T digits = binary_digits::value; constexpr T offset = digits / 2; constexpr T ones = ~static_cast(0); - const T lsbs0 = src0 & static_cast(ones >> (digits - offset)); - const T msbs0 = src0 >> offset; - const T lsbs1 = src1 & static_cast(ones >> (digits - offset)); - const T msbs1 = src1 >> offset; + const T lsbs0 = src0 & static_cast(lsr(ones, (digits - offset))); + const T msbs0 = lsr(src0, offset); + const T lsbs1 = src1 & static_cast(lsr(ones, (digits - offset))); + const T msbs1 = lsr(src1, offset); const T llsbs = lsbs0 * lsbs1; const T mlsbs = msbs0 * lsbs1; const T lmsbs = lsbs0 * msbs1; @@ -1068,7 +1020,7 @@ constexpr T _mulx(T src0, T src1, T* hi, X...) noexcept const T lo = llsbs + static_cast(mi << offset); const T lcarry = lo < llsbs || lo < static_cast(mi << offset); const T mcarry = static_cast(mi < mlsbs || mi < lmsbs) << offset; - *hi = static_cast(mi >> offset) + msbs0 * msbs1 + mcarry + lcarry; + *hi = static_cast(lsr(mi, offset)) + msbs0 * msbs1 + mcarry + lcarry; return lo; } // -------------------------------------------------------------------------- // diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index 59d7cbdc..ee4327af 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -182,7 +182,7 @@ constexpr bit_reference& bit_reference::assign(word_type val template constexpr bit_reference& bit_reference::assign(word_type val, size_type pos) const { assert(pos < binary_digits::value); - val >> pos & 1 ? set() : reset(); + ((val >> pos) & 1) ? set() : reset(); return const_cast&>(*this); } // -------------------------------------------------------------------------- // diff --git a/test/src/fixtures.hpp b/test/src/fixtures.hpp index b8cd5942..f5b672f1 100644 --- a/test/src/fixtures.hpp +++ b/test/src/fixtures.hpp @@ -1,14 +1,13 @@ // =============================== FIXTURES ================================= // // Project: The Experimental Bit Algorithms Library -// Description: Fixtures for testing -// Contributor(s): Bryce Kille +// Description: Fixtures for testing +// Contributor(s): Bryce Kille // License: BSD 3-Clause License // ========================================================================== // #ifndef _FIXTURES_HPP_INCLUDED #define _FIXTURES_HPP_INCLUDED // ========================================================================== // - // ============================== PREAMBLE ================================== // // C++ standard library #include @@ -32,51 +31,49 @@ //TODO tests need a lot of cleanup. We should only copy what we need from random_vec //and also refactor the vec generation to reduce duplication -using BaseTypes = ::testing::Types; - +using BaseTypes = ::testing::Types; -template +template class VectorTest : public testing::Test { - protected: - - using base_type = WordType; - using vec_type = bit::bit_vector; - vec_type empty_vec; - std::vector empty_vec_bool; - vec_type v2_ = vec_type(18); - vec_type v3_ = vec_type("010111111"); - - std::vector random_bitvecs; - std::vector> random_boolvecs; - std::vector random_vec; - const size_t word_size = 4; - const size_t digits = bit::binary_digits::value; - const size_t bit_size = word_size*digits; - - void SetUp() override { - empty_vec = vec_type(); - random_vec = get_random_vec(word_size); - for (size_t cont_size = 1; cont_size < bit_size; ++cont_size) { - auto bitvec = vec_type(bit_size); - std::memcpy(&(*bitvec.begin().base()), &(random_vec[0]), word_size); - bitvec.resize(cont_size); - - auto boolvec = boolvec_from_bitvec(bitvec); - random_bitvecs.push_back(bitvec); - random_boolvecs.push_back(boolvec); - } - size_t big_size = 64*64*10; - for (int i = -4; i < 4; ++i) { - size_t cont_size = big_size + i; - auto bitvec = vec_type(bit_size); - std::memcpy(&(*bitvec.begin().base()), &(random_vec[0]), word_size); - bitvec.resize(cont_size); - - auto boolvec = boolvec_from_bitvec(bitvec); - random_bitvecs.push_back(bitvec); - random_boolvecs.push_back(boolvec); - } + protected: + using base_type = WordType; + using vec_type = bit::bit_vector; + vec_type empty_vec; + std::vector empty_vec_bool; + vec_type v2_ = vec_type(18); + vec_type v3_ = vec_type("010111111"); + + std::vector random_bitvecs; + std::vector> random_boolvecs; + std::vector random_vec; + const size_t word_size = 4; + const size_t digits = bit::binary_digits::value; + const size_t bit_size = word_size * digits; + + void SetUp() override { + empty_vec = vec_type(); + random_vec = get_random_vec(word_size); + for (size_t cont_size = 1; cont_size < bit_size; ++cont_size) { + auto bitvec = vec_type(bit_size); + std::memcpy(&(*bitvec.begin().base()), &(random_vec[0]), word_size); + bitvec.resize(cont_size); + + auto boolvec = boolvec_from_bitvec(bitvec); + random_bitvecs.push_back(bitvec); + random_boolvecs.push_back(boolvec); + } + size_t big_size = 64 * 64 * 10; + for (int i = -4; i < 4; ++i) { + size_t cont_size = big_size + i; + auto bitvec = vec_type(bit_size); + std::memcpy(&(*bitvec.begin().base()), &(random_vec[0]), word_size); + bitvec.resize(cont_size); + + auto boolvec = boolvec_from_bitvec(bitvec); + random_bitvecs.push_back(bitvec); + random_boolvecs.push_back(boolvec); } + } }; TYPED_TEST_SUITE(VectorTest, BaseTypes); From a32e9aebae0b379916cf3c8addd4c2eb5c725d94 Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Sat, 24 May 2025 21:06:59 -0400 Subject: [PATCH 67/85] Bitwise operators in bit_array_base (#11) * Bitwise operators in bit_array_base * Sketching out bit_integer class * No subborrowx exists * addcarry Pointer out type is sensitive for clang * clang seems more sensitive for constructor inheritance * Remove defunct comment * Fix rebase * Remove incomplete bit_integer class --- CMakePresets.json | 20 ++ include/bitlib/bit-algorithms/transform.hpp | 249 +++++++++--------- include/bitlib/bit-containers/bit_array.hpp | 12 +- .../bitlib/bit-containers/bit_array_base.hpp | 68 ++++- .../bit_array_dynamic_extent.hpp | 4 +- .../bitlib/bit-containers/bit_array_ref.hpp | 4 +- include/bitlib/bit-iterator/bit_details.hpp | 146 ++++------ test/CMakeLists.txt | 1 + test/src/test-bitwise.cpp | 46 ++++ 9 files changed, 320 insertions(+), 230 deletions(-) create mode 100644 test/src/test-bitwise.cpp diff --git a/CMakePresets.json b/CMakePresets.json index 7417bde5..bfc042ee 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -340,6 +340,17 @@ "rhs": "Linux" } }, + { + "name": "tests_linux_clang_libstdcxx", + "configurePreset": "tests_linux_clang_libstdcxx", + "targets": "bitlib-tests", + "jobs": 0, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, { "name": "coverage_linux_clang_libcxx", "configurePreset": "coverage_linux_clang_libcxx", @@ -443,6 +454,15 @@ "rhs": "Linux" } }, + { + "name": "tests_linux_clang_libstdcxx", + "configurePreset": "tests_linux_clang_libstdcxx", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, { "name": "coverage_linux_clang_libcxx", "configurePreset": "coverage_linux_clang_libcxx", diff --git a/include/bitlib/bit-algorithms/transform.hpp b/include/bitlib/bit-algorithms/transform.hpp index ac3de218..e6f93c61 100644 --- a/include/bitlib/bit-algorithms/transform.hpp +++ b/include/bitlib/bit-algorithms/transform.hpp @@ -31,54 +31,52 @@ namespace bit { //return d_first; //} -template -constexpr bit_iterator transform( - bit_iterator first, - bit_iterator last, - bit_iterator d_first, - UnaryOperation unary_op) { - using word_type = typename bit_iterator::word_type; - using size_type = typename bit_iterator::size_type; - constexpr size_type digits = binary_digits::value; +template +constexpr bit_iterator transform( + bit_iterator first, + bit_iterator last, + bit_iterator d_first, + UnaryOperation unary_op) { + using word_type = typename bit_iterator::word_type; + using size_type = typename bit_iterator::size_type; + constexpr size_type digits = binary_digits::value; - // Assertions - _assert_range_viability(first, last); - if (first == last) return d_first; + // Assertions + _assert_range_viability(first, last); + if (first == last) { + return d_first; + } + // Initialization + const bool is_d_first_aligned = d_first.position() == 0; + size_type total_bits_to_op = distance(first, last); + size_type remaining_bits_to_op = total_bits_to_op; + auto it = d_first.base(); - // Initialization - const bool is_d_first_aligned = d_first.position() == 0; - size_type total_bits_to_op = distance(first, last); - size_type remaining_bits_to_op = total_bits_to_op; - auto it = d_first.base(); - - // d_first is not aligned. Copy partial word to align it - if (!is_d_first_aligned) { - size_type partial_bits_to_op = ::std::min( - remaining_bits_to_op, - digits - d_first.position() - ); - *it = _bitblend( - *it, - unary_op( - static_cast( - get_word(first, partial_bits_to_op) - << static_cast(d_first.position()) - ) - ), - static_cast(d_first.position()), - static_cast(partial_bits_to_op)); - remaining_bits_to_op -= partial_bits_to_op; - advance(first, partial_bits_to_op); - it++; - } - auto firstIt = first.base(); - if (remaining_bits_to_op > 0) { - const bool is_first_aligned = first.position() == 0; - //size_type words_to_op = ::std::ceil(remaining_bits_to_op / static_cast(digits)); - // d_first will be aligned at this point - if (is_first_aligned && remaining_bits_to_op > digits) { - auto N = ::std::distance(firstIt, last.base()); + // d_first is not aligned. Copy partial word to align it + if (!is_d_first_aligned) { + size_type partial_bits_to_op = ::std::min( + remaining_bits_to_op, + digits - d_first.position()); + *it = _bitblend( + *it, + unary_op( + static_cast( + get_word(first, partial_bits_to_op) + << static_cast(d_first.position()))), + static_cast(d_first.position()), + static_cast(partial_bits_to_op)); + remaining_bits_to_op -= partial_bits_to_op; + advance(first, partial_bits_to_op); + it++; + } + auto firstIt = first.base(); + if (remaining_bits_to_op > 0) { + const bool is_first_aligned = first.position() == 0; + //size_type words_to_op = ::std::ceil(remaining_bits_to_op / static_cast(digits)); + // d_first will be aligned at this point + if (is_first_aligned && remaining_bits_to_op > digits) { + auto N = ::std::distance(firstIt, last.base()); #ifdef BITLIB_HWY if constexpr (std::is_same_v>) { @@ -105,105 +103,102 @@ constexpr bit_iterator transform( size_t std_dist = ::std::distance(firstIt, last.base()); it = std::transform(firstIt, last.base(), it, unary_op); firstIt += std_dist; - first = bit_iterator(firstIt); + first = bit_iterator(firstIt); remaining_bits_to_op -= digits * N; - } else { - while (remaining_bits_to_op >= digits) { - *it = unary_op(get_word(first, digits)); - remaining_bits_to_op -= digits; - it++; - advance(first, digits); - } - } + } else { + while (remaining_bits_to_op >= digits) { + *it = unary_op(get_word(first, digits)); + remaining_bits_to_op -= digits; + it++; + advance(first, digits); + } + } if (remaining_bits_to_op > 0) { *it = _bitblend( *it, unary_op(get_word(first, remaining_bits_to_op)), _mask(remaining_bits_to_op)); } - } + } return d_first + total_bits_to_op; } -template -constexpr bit_iterator transform( - bit_iterator first1, - bit_iterator last1, - bit_iterator first2, - bit_iterator d_first, - BinaryOperation binary_op) { - using word_type = typename bit_iterator::word_type; - using size_type = typename bit_iterator::size_type; - constexpr size_type digits = binary_digits::value; - - // Assertions - _assert_range_viability(first1, last1); - if (first1 == last1) return d_first; +template +constexpr bit_iterator transform( + bit_iterator first1, + bit_iterator last1, + bit_iterator first2, + bit_iterator d_first, + BinaryOperation binary_op) { + using word_type = typename bit_iterator::word_type; + using size_type = typename bit_iterator::size_type; + constexpr size_type digits = binary_digits::value; + // Assertions + _assert_range_viability(first1, last1); + if (first1 == last1) { + return d_first; + } - // Initialization - const bool is_d_first_aligned = d_first.position() == 0; - size_type total_bits_to_op = distance(first1, last1); - size_type remaining_bits_to_op = total_bits_to_op; - auto it = d_first.base(); + // Initialization + const bool is_d_first_aligned = d_first.position() == 0; + size_type total_bits_to_op = distance(first1, last1); + size_type remaining_bits_to_op = total_bits_to_op; + auto it = d_first.base(); - // d_first is not aligned. Copy partial word to align it - if (!is_d_first_aligned) { - size_type partial_bits_to_op = ::std::min( - remaining_bits_to_op, - digits - d_first.position() - ); - *it = _bitblend( - *it, - binary_op( - static_cast( - get_word(first1, partial_bits_to_op) - << static_cast(d_first.position()) - ), - static_cast( - get_word(first2, partial_bits_to_op) - << static_cast(d_first.position()) - ) - ), - static_cast(d_first.position()), - static_cast(partial_bits_to_op)); - remaining_bits_to_op -= partial_bits_to_op; - advance(first1, partial_bits_to_op); - advance(first2, partial_bits_to_op); + // d_first is not aligned. Copy partial word to align it + if (!is_d_first_aligned) { + size_type partial_bits_to_op = ::std::min( + remaining_bits_to_op, + digits - d_first.position()); + *it = _bitblend( + *it, + binary_op( + static_cast( + get_word(first1, partial_bits_to_op) + << static_cast(d_first.position())), + static_cast( + get_word(first2, partial_bits_to_op) + << static_cast(d_first.position()))), + static_cast(d_first.position()), + static_cast(partial_bits_to_op)); + remaining_bits_to_op -= partial_bits_to_op; + advance(first1, partial_bits_to_op); + advance(first2, partial_bits_to_op); + it++; + } + if (remaining_bits_to_op > 0) { + const bool is_first1_aligned = first1.position() == 0; + const bool is_first2_aligned = first2.position() == 0; + //size_type words_to_op = ::std::ceil(remaining_bits_to_op / static_cast(digits)); + // d_first will be aligned at this point + if (is_first1_aligned && is_first2_aligned && remaining_bits_to_op > digits) { + auto N = ::std::distance(first1.base(), last1.base()); + it = std::transform(first1.base(), last1.base(), first2.base(), it, binary_op); + first1 += digits * N; + first2 += digits * N; + remaining_bits_to_op -= digits * N; + } else { + while (remaining_bits_to_op >= digits) { + *it = binary_op( + get_word(first1, digits), + get_word(first2, digits)); + remaining_bits_to_op -= digits; it++; + advance(first1, digits); + advance(first2, digits); + } } if (remaining_bits_to_op > 0) { - const bool is_first1_aligned = first1.position() == 0; - const bool is_first2_aligned = first2.position() == 0; - //size_type words_to_op = ::std::ceil(remaining_bits_to_op / static_cast(digits)); - // d_first will be aligned at this point - if (is_first1_aligned && is_first2_aligned && remaining_bits_to_op > digits) { - auto N = ::std::distance(first1.base(), last1.base()); - it = std::transform(first1.base(), last1.base(), first2.base(), it, binary_op); - first1 += digits * N; - first2 += digits * N; - remaining_bits_to_op -= digits * N; - } else { - while (remaining_bits_to_op >= digits) { - *it = binary_op( - get_word(first1, digits), - get_word(first2, digits)); - remaining_bits_to_op -= digits; - it++; - advance(first1, digits); - advance(first2, digits); - } - } - if (remaining_bits_to_op > 0) { - *it = _bitblend( - *it, - binary_op( - get_word(first1, remaining_bits_to_op), - get_word(first2, remaining_bits_to_op)), - _mask(remaining_bits_to_op)); - } + *it = _bitblend( + *it, + binary_op( + get_word(first1, remaining_bits_to_op), + get_word(first2, remaining_bits_to_op)), + _mask(remaining_bits_to_op)); } - return d_first + total_bits_to_op; + } + return d_first + total_bits_to_op; } //template diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index e1eab6e9..53e32e1f 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -52,9 +52,9 @@ template , uint8_t, T>> -class bit_array : public bit_array_base, T, W, detail::bit_array_d_it, detail::bit_array_d_cit> { +class bit_array : public bit_array_base, T, N, W, detail::bit_array_d_it, detail::bit_array_d_cit> { public: - using base = bit_array_base, T, W, detail::bit_array_d_it, detail::bit_array_d_cit>; + using base = bit_array_base, T, N, W, detail::bit_array_d_it, detail::bit_array_d_cit>; using base::end; using typename base::const_iterator; using typename base::const_pointer; @@ -86,6 +86,11 @@ class bit_array : public bit_array_base, T, W, detail::bit */ constexpr bit_array() noexcept : storage{} {} + /*This constructor is purely to simplify some corners of the API*/ + constexpr bit_array(size_type size) noexcept : storage{} { + assert(size == N); + } + constexpr bit_array(value_type bit_val) : storage{} { this->fill(bit_val); } @@ -111,7 +116,8 @@ class bit_array : public bit_array_base, T, W, detail::bit } } - constexpr bit_array(const bit_array& other) = default; + constexpr bit_array(const bit_array& other) noexcept + : storage(other.storage) {} constexpr bit_array(const bit_array&& other) noexcept : storage(other.storage) {} diff --git a/include/bitlib/bit-containers/bit_array_base.hpp b/include/bitlib/bit-containers/bit_array_base.hpp index c8fd0f3e..009a29e2 100644 --- a/include/bitlib/bit-containers/bit_array_base.hpp +++ b/include/bitlib/bit-containers/bit_array_base.hpp @@ -31,6 +31,12 @@ namespace bit { template class bit_array_ref; + +template +class bit_array; // ========================================================================== // /** @@ -45,8 +51,17 @@ class bit_array_ref; * @tparam It The iterator type for the derived class * @tparam CIt The const_iterator type for the derived class */ -template +template class bit_array_base { + protected: + constexpr Derived& derived() noexcept { + return static_cast(*this); + } + + constexpr const Derived& derived() const noexcept { + return static_cast(*this); + } + public: using word_type = W; using value_type = T; @@ -181,13 +196,52 @@ class bit_array_base { return integral; } - protected: - constexpr Derived& derived() noexcept { - return static_cast(*this); + using compatible_bitarray = bit_array; + + constexpr compatible_bitarray operator~() { + compatible_bitarray result(derived().size()); + transform(derived().begin(), derived().end(), result.begin(), [](const word_type& bits) -> word_type { return ~bits; }); + return result; } - constexpr const Derived& derived() const noexcept { - return static_cast(*this); + constexpr compatible_bitarray operator|(const bit_sized_range auto& other) const { + assert(other.size() == derived().size()); + compatible_bitarray result(derived().size()); + transform(derived().begin(), derived().end(), other.begin(), result.begin(), + [](const word_type& a, const word_type& b) -> word_type { return a | b; }); + return result; + } + constexpr Derived& operator|=(bit_sized_range auto& other) { + assert(other.size() == derived().size()); + transform(derived().begin(), derived().end(), other.begin(), derived().begin(), + [](const word_type& a, const word_type& b) -> word_type { return a | b; }); + return derived(); + } + constexpr compatible_bitarray operator&(const bit_sized_range auto& other) const { + assert(other.size() == derived().size()); + compatible_bitarray result(derived().size()); + transform(derived().begin(), derived().end(), other.begin(), result.begin(), + [](const word_type& a, const word_type& b) -> word_type { return a & b; }); + return result; + } + constexpr Derived& operator&=(bit_sized_range auto& other) { + assert(other.size() == derived().size()); + transform(derived().begin(), derived().end(), other.begin(), derived().begin(), + [](const word_type& a, const word_type& b) -> word_type { return a & b; }); + return derived(); + } + constexpr compatible_bitarray operator^(const bit_sized_range auto& other) const { + assert(other.size() == derived().size()); + compatible_bitarray result(derived().size()); + transform(derived().begin(), derived().end(), other.begin(), result.begin(), + [](const word_type& a, const word_type& b) -> word_type { return a ^ b; }); + return result; + } + constexpr Derived& operator^=(bit_sized_range auto& other) { + assert(other.size() == derived().size()); + transform(derived().begin(), derived().end(), other.begin(), derived().begin(), + [](const word_type& a, const word_type& b) -> word_type { return a ^ b; }); + return derived(); } }; constexpr bool operator==(const bit_sized_range auto& lhs, const bit_sized_range auto& rhs) { @@ -200,4 +254,4 @@ constexpr bool operator==(const bit_sized_range auto& lhs, const bit_sized_range } // namespace bit #endif // _BIT_ARRAY_BASE_HPP_INCLUDED - // ========================================================================== // \ No newline at end of file + // ========================================================================== // diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index 23c0acac..4d6a7635 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -39,9 +39,9 @@ using bit_array_cit = typename std::conditional class bit_array - : public bit_array_base, T, W, detail::bit_array_it, detail::bit_array_cit> { + : public bit_array_base, T, std::dynamic_extent, W, detail::bit_array_it, detail::bit_array_cit> { public: - using base = bit_array_base, T, W, detail::bit_array_it, detail::bit_array_cit>; + using base = bit_array_base, T, std::dynamic_extent, W, detail::bit_array_it, detail::bit_array_cit>; using base::end; using typename base::const_iterator; using typename base::const_pointer; diff --git a/include/bitlib/bit-containers/bit_array_ref.hpp b/include/bitlib/bit-containers/bit_array_ref.hpp index be86279f..a55377cc 100644 --- a/include/bitlib/bit-containers/bit_array_ref.hpp +++ b/include/bitlib/bit-containers/bit_array_ref.hpp @@ -39,9 +39,9 @@ namespace bit { */ template class bit_array_ref - : public bit_array_base, T, W, bit_iterator, bit_iterator> { + : public bit_array_base, T, std::dynamic_extent, W, bit_iterator, bit_iterator> { public: - using base = bit_array_base, T, W, bit_iterator, bit_iterator>; + using base = bit_array_base, T, std::dynamic_extent, W, bit_iterator, bit_iterator>; using base::end; using typename base::const_iterator; using typename base::const_pointer; diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index 7b2f79e1..fbef2aa8 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -15,6 +15,8 @@ // ================================ PREAMBLE ================================ // // C++ standard library +#include + #include #include #include @@ -877,103 +879,70 @@ constexpr T _shrd(T dst, T src, T cnt) noexcept { } // -------------------------------------------------------------------------- // -// ------------ IMPLEMENTATION DETAILS: INSTRUCTIONS: ADD CARRY ------------- // -// Adds src0 and src1 and returns the new carry bit with intrinsics -template -constexpr C _addcarry(C carry, T src0, T src1, T* dst) noexcept { - static_assert(binary_digits::value, ""); - using wider_t = typename _wider_type::type; - constexpr T digits = binary_digits::value; - wider_t tmp = 0; - unsigned int udst = 0; - unsigned long long int ulldst = 0; - if (digits == std::numeric_limits::digits) { - carry = __builtin_ia32_addcarryx_u32(carry, src0, src1, &udst); - *dst = udst; - } else if (digits == std::numeric_limits::digits) { - carry = __builtin_ia32_addcarryx_u64(carry, src0, src1, &ulldst); - *dst = ulldst; - } else if (digits < binary_digits::value) { - tmp = static_cast(src0) + static_cast(src1); - tmp += static_cast(static_cast(carry)); - *dst = tmp; - carry = static_cast(tmp >> digits); - } else { - carry = _addcarry(carry, src0, src1, dst, std::ignore); - } - return carry; +#if defined(__ADX__) +template +unsigned char ADDCARRYSUBBORROW32(unsigned char c, uint32_t a, uint32_t b, uint32_t* out) { + return (Add ? _addcarryx_u32(c, a, b, out) : _subborrow_u32(c, a, b, out)); } - -// Adds src0 and src1 and returns the new carry bit without intrinsics -template -constexpr C _addcarry(C carry, T src0, T src1, T* dst, X...) noexcept { - static_assert(binary_digits::value, ""); - *dst = src0 + src1 + static_cast(static_cast(carry)); - return carry ? *dst <= src0 || *dst <= src1 : *dst < src0 || *dst < src1; +template +unsigned char ADDCARRYSUBBORROW64(unsigned char c, uint64_t a, uint64_t b, uint64_t* out) { + static_assert(sizeof(uint64_t) == sizeof(unsigned long long int)); + return (Add ? _addcarryx_u64(c, a, b, reinterpret_cast(out)) : _subborrow_u64(c, a, b, reinterpret_cast(out))); } -// -------------------------------------------------------------------------- // +#else +template +unsigned char ADDCARRYSUBBORROW32(unsigned char c, uint32_t a, uint32_t b, uint32_t* out) { + return (Add ? _addcarry_u32(c, a, b, out) : _subborrow_u32(c, a, b, out)); +} +template +unsigned char ADDCARRYSUBBORROW64(unsigned char c, uint64_t a, uint64_t b, uint64_t* out) { + static_assert(sizeof(uint64_t) == sizeof(unsigned long long int)); + return (Add ? _addcarry_u64(c, a, b, reinterpret_cast(out)) : _subborrow_u64(c, a, b, reinterpret_cast(out))); +} +#endif -// ------------ IMPLEMENTATION DETAILS: INSTRUCTIONS: SUB BORROW ------------ // -// Subtracts src1 to src0 and returns the new borrow bit with intrinsics -template -constexpr B _subborrow(B borrow, T src0, T src1, T* dst) noexcept { - static_assert(binary_digits::value, ""); - using wider_t = typename _wider_type::type; - constexpr T digits = binary_digits::value; - wider_t tmp = 0; - unsigned int udst = 0; - unsigned long long int ulldst = 0; - if (digits == std::numeric_limits::digits) { - borrow = __builtin_ia32_sbb_u32(borrow, src0, src1, &udst); - *dst = udst; - } else if (digits == std::numeric_limits::digits) { - borrow = __builtin_ia32_sbb_u64(borrow, src0, src1, &ulldst); - *dst = ulldst; - } else if (digits < binary_digits::value) { - tmp = static_cast(src1); - tmp += static_cast(static_cast(borrow)); - borrow = tmp > static_cast(src0); - *dst = static_cast(src0) - tmp; +template +static inline unsigned char add_carry_sub_borrow(unsigned char c_in, U a, U b, U* out) noexcept { + if constexpr (32 > bitsof()) { + // a [aaaaaaaa111111111111111111111111111] + // b + [bbbbbbbb000000000000000000000000000] + // carry + [0000000c] + const uint8_t shift = (32 - bitsof()); + uint32_t carry_propagation = Add ? ((1 << shift) - 1) : 0; + uint32_t tmp_out; + unsigned char carry = ADDCARRYSUBBORROW32( + c_in, + (static_cast(a) << shift) | carry_propagation, + (static_cast(b) << shift), + &tmp_out); + *out = static_cast(tmp_out >> shift); + return carry; + } else if constexpr (32 == bitsof()) { + return ADDCARRYSUBBORROW32(c_in, static_cast(a), static_cast(b), reinterpret_cast(out)); + } else if constexpr (64 == bitsof()) { + return ADDCARRYSUBBORROW64(c_in, static_cast(a), static_cast(b), reinterpret_cast(out)); + } else if constexpr (0 == (bitsof() % 64)) { + using t64 = std::conditional, int64_t, uint64_t>; + unsigned char carry; + for (int i = 0; i < (bitsof() / 64); i++) { + carry = ADDCARRYSUBBORROW64(c_in, static_cast(a >> (i * 64)), static_cast(b >> (i * 64)), reinterpret_cast(out) + i); + } + return carry; } else { - borrow = _subborrow(borrow, src0, src1, dst, std::ignore); + assert(((void)"add carry intrinsics support only support powers of 2 bits", false)); } - return borrow; } -// Subtracts src1 to src0 and returns the new borrow bit with other intrinsics -template -constexpr B _subborrow(const B& borrow, T src0, T src1, T* dst) noexcept { - static_assert(binary_digits::value, ""); - using wider_t = typename _wider_type::type; - constexpr T digits = binary_digits::value; - wider_t tmp = 0; - unsigned int udst = 0; - unsigned long long int ulldst = 0; - B flag = borrow; - if (digits == std::numeric_limits::digits) { - flag = __builtin_ia32_subborrow_u32(borrow, src0, src1, &udst); - *dst = udst; - } else if (digits == std::numeric_limits::digits) { - flag = __builtin_ia32_subborrow_u64(borrow, src0, src1, &ulldst); - *dst = ulldst; - } else if (digits < binary_digits::value) { - tmp = static_cast(src1); - tmp += static_cast(static_cast(borrow)); - flag = tmp > static_cast(src0); - *dst = static_cast(src0) - tmp; - } else { - flag = _subborrow(borrow, src0, src1, dst, std::ignore); - } - return flag; +template +static inline unsigned char add_carry(unsigned char c_in, U a, U b, U* out) noexcept { + return add_carry_sub_borrow(c_in, a, b, out); } -// Subtracts src1 to src0 and returns the new borrow bit without intrinsics -template -constexpr B _subborrow(B borrow, T src0, T src1, T* dst, X...) noexcept { - static_assert(binary_digits::value, ""); - *dst = src0 - (src1 + static_cast(static_cast(borrow))); - return borrow ? src1 >= src0 : src1 > src0; +template +static inline unsigned char sub_borrow(unsigned char c_in, U a, U b, U* out) noexcept { + return add_carry_sub_borrow(c_in, a, b, out); } + // -------------------------------------------------------------------------- // // -------- IMPLEMENTATION DETAILS: INSTRUCTIONS: MULTIWORD MULTIPLY -------- // @@ -1028,7 +997,6 @@ constexpr T _mulx(T src0, T src1, T* hi, X...) noexcept // ========================================================================== // -} // namespace bit +} // namespace bit #endif // _BIT_DETAILS_HPP_INCLUDED // ========================================================================== // - diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d8c29d29..d08332da 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -25,6 +25,7 @@ add_executable(bitlib-tests) target_sources(bitlib-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/fixtures.hpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-array.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-bitwise.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-copy_backward.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-copy.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-count.cpp diff --git a/test/src/test-bitwise.cpp b/test/src/test-bitwise.cpp new file mode 100644 index 00000000..98a66b09 --- /dev/null +++ b/test/src/test-bitwise.cpp @@ -0,0 +1,46 @@ +#include +#include +#include + +#include "bitlib/bit-containers/bit_array.hpp" +#include "bitlib/bit-containers/bit_array_dynamic_extent.hpp" +#include "fixtures.hpp" + +// Third-party libraries +#include "gtest/gtest.h" + +TEST(Bitwise, UnaryNot) { + auto num = 13'8191_b; + EXPECT_EQ(~num, 13'0_b); + EXPECT_EQ(~(num(0, 7)), 7'0_b); +} + +TEST(Bitwise, Or) { + auto num = 0x20'DEAD0000_b; + auto num2 = 0x20'0000BEEF_b; + auto ord = num | num2; + EXPECT_EQ(ord, 0x20'DEADBEEF_b); + EXPECT_EQ(ord.size(), 0x20); + num |= num2; + EXPECT_EQ(ord, num); +} + +TEST(Bitwise, And) { + auto num = 0x20'DEAD0000_b; + auto num2 = 0x20'0000BEEF_b; + auto anded = num & num2; + EXPECT_EQ(anded, 0x20'00000000_b); + EXPECT_EQ(anded.size(), 0x20); + num &= num2; + EXPECT_EQ(anded, num); +} + +TEST(Bitwise, Xor) { + auto num = 0x20'DEAD0000_b; + auto num2 = 0x20'0000BEEF_b; + auto xord = num ^ num2; + EXPECT_EQ(xord, 0x20'DEADBEEF_b); + EXPECT_EQ(xord.size(), 0x20); + num ^= num2; + EXPECT_EQ(xord, num); +} From 0f0b787a1df2aec35376556ac24fb198317cd035 Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Thu, 12 Jun 2025 21:54:41 -0400 Subject: [PATCH 68/85] Small buffer optimization (#16) * Remove alignment. Expose algorithm issues with differing types * Add iterator adapter class (big to small) * Use new iterator adapter. Set more default words to uintptr_t * Add dispatch to workflow trigger options * Dummy change * Try rerranging * Remove distinct iterator adapter test target * Try GLIBCXX_RELEASE macro * Fix type for test * bit_reference now templated on ref type to allow for it to wrap proxy references * Now with proxy reference and proxy pointer * Fix some ops * Remove wider_t narrower_t * Small buffer optimized dynamic bit array * Clean up more of bit_details * small improvement to bitsof * Don't measure coverage on tests (too error prone) * Adapter prelude wip * Add test for mixed type equals * Adapted copy * Test adapted copy * Fix conversion from adapter back to iterator * Remove unnecessary return type template parameter --- .codecov.yml | 2 +- .github/workflows/cmake-multi-platform.yml | 2 + CMakeLists.txt | 16 + .../bit-algorithms/bit_algorithm_details.hpp | 8 +- include/bitlib/bit-algorithms/copy.hpp | 74 ++-- .../bitlib/bit-algorithms/copy_backward.hpp | 28 +- include/bitlib/bit-algorithms/equal.hpp | 105 +++--- include/bitlib/bit-algorithms/shift.hpp | 27 +- include/bitlib/bit-algorithms/type_traits.hpp | 5 +- include/bitlib/bit-containers/bit_array.hpp | 24 +- .../bitlib/bit-containers/bit_array_base.hpp | 7 +- .../bit_array_dynamic_extent.hpp | 222 +++++++----- .../bitlib/bit-containers/bit_array_ref.hpp | 2 +- include/bitlib/bit-containers/bit_bitsof.hpp | 9 +- .../bit-containers/bit_mdspan_accessor.hpp | 2 +- include/bitlib/bit-containers/bit_span.hpp | 4 +- include/bitlib/bit-containers/bit_vector.hpp | 2 +- include/bitlib/bit-iterator/bit.hpp | 3 + include/bitlib/bit-iterator/bit_details.hpp | 171 +++++++-- include/bitlib/bit-iterator/bit_iterator.hpp | 22 +- include/bitlib/bit-iterator/bit_reference.hpp | 135 ++++--- include/bitlib/bit-iterator/bit_value.hpp | 16 +- .../bit-iterator/bit_word_pointer_adapter.hpp | 213 +++++++++++ .../bit_word_reference_adapter.hpp | 119 +++++++ include/bitlib/bit_concepts.hpp | 2 +- test/CMakeLists.txt | 1 + test/src/fixtures.hpp | 102 +++++- test/src/test-array.cpp | 58 ++- test/src/test-array_ref.cpp | 4 +- test/src/test-copy.cpp | 98 ++++-- test/src/test-equal.cpp | 111 ++++-- test/src/test-iterator_adapter.cpp | 333 ++++++++++++++++++ test/src/test-mdspan.cpp | 2 +- 33 files changed, 1489 insertions(+), 440 deletions(-) create mode 100644 include/bitlib/bit-iterator/bit_word_pointer_adapter.hpp create mode 100644 include/bitlib/bit-iterator/bit_word_reference_adapter.hpp create mode 100644 test/src/test-iterator_adapter.cpp diff --git a/.codecov.yml b/.codecov.yml index 464e2d45..fd894e6f 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,5 +1,5 @@ ignore: - "**/libpopcnt.h" - - "test/inc/*" + - "test/**" - "benchmark/**" - "example/**" \ No newline at end of file diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index ce816881..046edde5 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -3,6 +3,7 @@ name: CMake on multiple platforms on: + workflow_dispatch: push: branches: [ "master" ] paths-ignore: @@ -12,6 +13,7 @@ on: paths-ignore: - '**/*.md' + jobs: build: runs-on: ${{ matrix.distro }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 061f11a9..32f3b4a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,9 +57,25 @@ target_sources(bitlib INTERFACE ) target_compile_features(bitlib INTERFACE cxx_std_23) +include(CheckCXXSourceCompiles) if (BITLIB_MDSPAN) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(USING_LIBSTDCXX 1) + endif() + if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(CODE " + #include + #ifdef __GLIBCXX__ + int main() { return 0; } + #else + #error Not using libstdc++ + #endif + ") + + check_cxx_source_compiles("${CODE}" USING_LIBSTDCXX) + endif() + if (USING_LIBSTDCXX) if (NOT TARGET std::mdspan) find_package(mdspan CONFIG) if (NOT mdspan_DIR OR "${mdspan_DIR}" STREQUAL "mdspan-NOTFOUND") diff --git a/include/bitlib/bit-algorithms/bit_algorithm_details.hpp b/include/bitlib/bit-algorithms/bit_algorithm_details.hpp index 57e2fb87..3be853b6 100644 --- a/include/bitlib/bit-algorithms/bit_algorithm_details.hpp +++ b/include/bitlib/bit-algorithms/bit_algorithm_details.hpp @@ -4,6 +4,7 @@ // Description: A set of utilities to assist in writing algorithms // Contributor(s): Vincent Reverdy [2019] // Bryce Kille [2019] +// Peter McLean [2025] // License: BSD 3-Clause License // ========================================================================== // #ifndef _BIT_ALGORITHM_DETAILS_HPP_INCLUDED @@ -103,12 +104,11 @@ constexpr bool is_within( // Get next len bits beginning at start and store them in a word of type T template -T get_word(bit_iterator first, size_t len=binary_digits::value) -{ +T get_word(const bit_iterator& first, size_t len = binary_digits::value) { using native_word_type = typename bit_iterator::word_type; constexpr T digits = binary_digits::value; assert(digits >= len); - using non_const_T = std::remove_cv_t; + using non_const_T = std::remove_cvref_t; non_const_T offset = digits - first.position(); non_const_T ret_word = lsr(*first.base(), first.position()); @@ -224,7 +224,7 @@ void write_word(src_type src, bit_iterator dst_bit_it, } else { *dst_bit_it.base() = _bitblend( *dst_bit_it.base(), - src << dst_bit_it.position(), + static_cast(src << dst_bit_it.position()), dst_bit_it.position(), std::min( dst_digits - dst_bit_it.position(), diff --git a/include/bitlib/bit-algorithms/copy.hpp b/include/bitlib/bit-algorithms/copy.hpp index 9ecb336a..00913d6f 100644 --- a/include/bitlib/bit-algorithms/copy.hpp +++ b/include/bitlib/bit-algorithms/copy.hpp @@ -22,38 +22,41 @@ namespace bit { // ========================================================================== // +struct copy_impl; +// Status: Does not work for Input/Output iterators due to distance call +template +constexpr bit_iterator copy( + const bit_iterator& first, + const bit_iterator& last, + const bit_iterator& d_first) { + return with_bit_iterator_adapter(first, last, d_first); +} // ---------------------------- Copy Algorithms ----------------------------- // - -// Status: Does not work for Input/Output iterators due to distance call -template -constexpr bit_iterator copy(bit_iterator first, - bit_iterator last, - bit_iterator d_first -) -{ +struct copy_impl { + // Status: Does not work for Input/Output iterators due to distance call + template + constexpr bit_iterator operator()( + bit_iterator first, + bit_iterator last, + bit_iterator d_first) { // Types and constants using dst_word_type = typename bit_iterator::word_type; using src_word_type = typename bit_iterator::word_type; // This checks for differing word types and uses an unoptimized copy in that event - if (!::std::is_same::value) { - while (first != last) { - *d_first = *first; - first++; - d_first++; - } - return d_first; - } + static_assert(std::is_same::value, "Both types must be the same"); + using word_type = dst_word_type; using size_type = typename bit_iterator::size_type; constexpr size_type digits = binary_digits::value; // Assertions _assert_range_viability(first, last); - if (first == last) return d_first; - + if (first == last) { + return d_first; + } // Initialization const bool is_d_first_aligned = d_first.position() == 0; @@ -61,25 +64,21 @@ constexpr bit_iterator copy(bit_iterator first size_type remaining_bits_to_copy = total_bits_to_copy; auto it = d_first.base(); - // d_first is not aligned. Copy partial word to align it if (!is_d_first_aligned) { - size_type partial_bits_to_copy = ::std::min( - remaining_bits_to_copy, - digits - d_first.position() - ); - *it = _bitblend( - *it, - static_cast( - get_word(first, partial_bits_to_copy) - << static_cast(d_first.position()) - ), - static_cast(d_first.position()), - static_cast(partial_bits_to_copy) - ); - remaining_bits_to_copy -= partial_bits_to_copy; - advance(first, partial_bits_to_copy); - it++; + size_type partial_bits_to_copy = ::std::min( + remaining_bits_to_copy, + digits - d_first.position()); + *it = _bitblend( + *it, + static_cast( + get_word(first, partial_bits_to_copy) + << static_cast(d_first.position())), + static_cast(d_first.position()), + static_cast(partial_bits_to_copy)); + remaining_bits_to_copy -= partial_bits_to_copy; + advance(first, partial_bits_to_copy); + it++; } if (remaining_bits_to_copy > 0) { @@ -108,12 +107,13 @@ constexpr bit_iterator copy(bit_iterator first } } return d_first + total_bits_to_copy; -} + } +}; // -------------------------------------------------------------------------- // // ========================================================================== // -} // namespace bit +} // namespace bit #endif // _COPY_HPP_INCLUDED // ========================================================================== // diff --git a/include/bitlib/bit-algorithms/copy_backward.hpp b/include/bitlib/bit-algorithms/copy_backward.hpp index 67c071f0..365ade22 100644 --- a/include/bitlib/bit-algorithms/copy_backward.hpp +++ b/include/bitlib/bit-algorithms/copy_backward.hpp @@ -56,13 +56,12 @@ constexpr bit_iterator copy_backward(bit_iterator( - *it, - static_cast( - get_word(last - partial_bits_to_copy, partial_bits_to_copy) - ) << (d_last.position() - partial_bits_to_copy), - d_last.position() - partial_bits_to_copy, - static_cast(partial_bits_to_copy) - ); + *it, + static_cast( + get_word(last - partial_bits_to_copy, partial_bits_to_copy) + << (d_last.position() - partial_bits_to_copy)), + d_last.position() - partial_bits_to_copy, + static_cast(partial_bits_to_copy)); remaining_bits_to_copy -= partial_bits_to_copy; advance(last, -partial_bits_to_copy); } @@ -87,14 +86,13 @@ constexpr bit_iterator copy_backward(bit_iterator 0) { - *it = _bitblend( - *it, - get_word(last - remaining_bits_to_copy, remaining_bits_to_copy) - << (digits - remaining_bits_to_copy), - digits - remaining_bits_to_copy, - remaining_bits_to_copy - ); - remaining_bits_to_copy = 0; + *it = _bitblend( + *it, + static_cast(get_word(last - remaining_bits_to_copy, remaining_bits_to_copy) + << (digits - remaining_bits_to_copy)), + digits - remaining_bits_to_copy, + remaining_bits_to_copy); + remaining_bits_to_copy = 0; } } return d_last - total_bits_to_copy; diff --git a/include/bitlib/bit-algorithms/equal.hpp b/include/bitlib/bit-algorithms/equal.hpp index a733a625..60642d8e 100644 --- a/include/bitlib/bit-algorithms/equal.hpp +++ b/include/bitlib/bit-algorithms/equal.hpp @@ -2,14 +2,13 @@ // Project: The Experimental Bit Algorithms Library // Name: equal.hpp // Contributor: Bryce Kille [2019] +// Peter McLean [2025] // License: BSD 3-Clause License // ========================================================================== // #ifndef _EQUAL_HPP_INCLUDED #define _EQUAL_HPP_INCLUDED // ========================================================================== // - - // ================================ PREAMBLE ================================ // // C++ standard library #include @@ -20,30 +19,39 @@ // Miscellaneous namespace bit { // ========================================================================== // +struct equal_impl; - +// Status: Does not work for Input/Output iterators due to distance call +template +constexpr bool equal( + const bit_iterator& first, + const bit_iterator& last, + const bit_iterator& d_first) { + return with_bit_iterator_adapter(first, last, d_first); +} // ---------------------------- Equal Algorithms ----------------------------- // // Status: Does not work for Input/Output iterators due to distance call -template -constexpr bool equal( - bit_iterator first, - bit_iterator last, - bit_iterator d_first -) -{ +struct equal_impl { + template + constexpr bool operator()( + bit_iterator first, + bit_iterator last, + bit_iterator d_first) { // Types and constants using dst_word_type = typename bit_iterator::word_type; using src_word_type = typename bit_iterator::word_type; + static_assert(::std::is_same::value, "Underlying word types must be equal"); using word_type = dst_word_type; using size_type = typename bit_iterator::size_type; constexpr size_type digits = binary_digits::value; // Assertions _assert_range_viability(first, last); - static_assert(::std::is_same::value, "Underlying word types must be equal"); - if (first == last) return true; + if (first == last) { + return true; + } // Initialization const bool is_d_first_aligned = d_first.position() == 0; @@ -53,48 +61,55 @@ constexpr bool equal( // d_first is not aligned. if (!is_d_first_aligned) { - const size_type partial_bits_to_check = ::std::min( - remaining_bits_to_check, - digits - d_first.position()); - const word_type mask = _mask(partial_bits_to_check) << d_first.position(); - const word_type comp = static_cast( - get_word(first, partial_bits_to_check) - << d_first.position()); - if ((mask & *it) != (mask & comp)) { return false; } - remaining_bits_to_check -= partial_bits_to_check; - advance(first, partial_bits_to_check); - it++; + const size_type partial_bits_to_check = ::std::min( + remaining_bits_to_check, + digits - d_first.position()); + const word_type mask = _mask(partial_bits_to_check) << d_first.position(); + const word_type comp = static_cast( + get_word(first, partial_bits_to_check) + << d_first.position()); + if ((mask & *it) != (mask & comp)) { + return false; + } + remaining_bits_to_check -= partial_bits_to_check; + advance(first, partial_bits_to_check); + it++; } if (remaining_bits_to_check > 0) { - const bool is_first_aligned = first.position() == 0; - // d_first will be aligned at this point - if (is_first_aligned && remaining_bits_to_check >= digits) { - auto N = ::std::distance(first.base(), last.base()); - bool found_mismatch = !::std::equal(first.base(), last.base(), it); - if (found_mismatch) {return false;} - it += N; - first += digits * N; - remaining_bits_to_check -= digits * N; - } else { - // TODO benchmark if its faster to ::std::check the entire range then shift - while (remaining_bits_to_check >= digits) { - if (*it != get_word(first, digits)) {return false;} - remaining_bits_to_check -= digits; - it++; - advance(first, digits); - } + const bool is_first_aligned = first.position() == 0; + // d_first will be aligned at this point + if (is_first_aligned && remaining_bits_to_check >= digits) { + auto N = ::std::distance(first.base(), last.base()); + bool found_mismatch = !::std::equal(first.base(), last.base(), it); + if (found_mismatch) { + return false; } - if (remaining_bits_to_check > 0) { - const word_type mask = _mask(remaining_bits_to_check); - const word_type comp = get_word(first, remaining_bits_to_check); - if ((mask & *it) != (mask & comp)) { + it += N; + first += digits * N; + remaining_bits_to_check -= digits * N; + } else { + // TODO benchmark if its faster to ::std::check the entire range then shift + while (remaining_bits_to_check >= digits) { + if (*it != get_word(first, digits)) { return false; } + remaining_bits_to_check -= digits; + it++; + advance(first, digits); + } + } + if (remaining_bits_to_check > 0) { + const word_type mask = _mask(remaining_bits_to_check); + const word_type comp = get_word(first, remaining_bits_to_check); + if ((mask & *it) != (mask & comp)) { + return false; } + } } return true; -} + } +}; // -------------------------------------------------------------------------- // diff --git a/include/bitlib/bit-algorithms/shift.hpp b/include/bitlib/bit-algorithms/shift.hpp index 1d1bf3a6..279877bc 100644 --- a/include/bitlib/bit-algorithms/shift.hpp +++ b/include/bitlib/bit-algorithms/shift.hpp @@ -77,13 +77,13 @@ bit_iterator shift_left( if (std::next(first.base(), is_last_aligned) == last.base()) { *first.base() = _bitblend( *first.base(), - lsr( + static_cast(lsr( *first.base() & ( lsr(static_cast(-1), ( digits - (is_last_aligned ? digits : last.position()) )) ) - , n), + , n)), first.position(), (is_last_aligned ? digits : last.position()) - first.position() ); @@ -107,7 +107,7 @@ bit_iterator shift_left( if (first.position() >= middle.position()) { *first.base() = _bitblend( *first.base(), - (*middle.base()) << (first.position() - middle.position()), + static_cast((*middle.base()) << (first.position() - middle.position())), first.position(), digits - first.position()); } else { @@ -120,7 +120,7 @@ bit_iterator shift_left( n1); *first.base() = _bitblend( *first.base(), - (*std::next(middle.base())) << (digits - n2), + static_cast((*std::next(middle.base())) << (digits - n2)), first.position() + n1, n2); } @@ -249,17 +249,14 @@ bit_iterator shift_right( // Single word case if (std::next(first.base(), is_last_aligned) == last.base()) { - *first.base() = _bitblend( - *first.base(), - ( - *first.base() & ( - static_cast(-1) << first.position() - ) - ) << n, - first.position(), - (is_last_aligned ? digits : last.position()) - first.position() - ); - return first + n; + *first.base() = _bitblend( + *first.base(), + static_cast( + (*first.base() & (static_cast(-1) << first.position())) + << n), + static_cast(first.position()), + static_cast((is_last_aligned ? digits : last.position()) - first.position())); + return first + n; } // Align last diff --git a/include/bitlib/bit-algorithms/type_traits.hpp b/include/bitlib/bit-algorithms/type_traits.hpp index 0cd1fb20..56b76d29 100644 --- a/include/bitlib/bit-algorithms/type_traits.hpp +++ b/include/bitlib/bit-algorithms/type_traits.hpp @@ -55,9 +55,8 @@ struct is_bit // Is bit structure specialization: bit reference template -struct is_bit> -: std::true_type -{ +struct is_bit> + : std::true_type { }; // Is bit value template definition diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index 53e32e1f..e531ca2f 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -50,11 +50,10 @@ using bit_array_d_cit = typename std::conditional, uint8_t, T>> -class bit_array : public bit_array_base, T, N, W, detail::bit_array_d_it, detail::bit_array_d_cit> { + typename W = std::conditional_t<(N == std::dynamic_extent), std::uintptr_t, ceil_integral>> +class bit_array : public bit_array_base, T, N, W, detail::bit_array_d_it, detail::bit_array_d_cit> { public: - using base = bit_array_base, T, N, W, detail::bit_array_d_it, detail::bit_array_d_cit>; + using base = bit_array_base, T, N, W, detail::bit_array_d_it, detail::bit_array_d_cit>; using base::end; using typename base::const_iterator; using typename base::const_pointer; @@ -75,10 +74,7 @@ class bit_array : public bit_array_base, T, N, W, detail:: } private: - static constexpr std::size_t Words_ = Words(N); - static constexpr std::size_t AlignedWords = (((Words_ * sizeof(word_type) + static_cast(V) - 1) & ~(static_cast(V) - 1)) + sizeof(word_type) - 1) / sizeof(word_type); - - alignas(static_cast(V)) std::array storage; + std::array storage; public: /* @@ -116,10 +112,10 @@ class bit_array : public bit_array_base, T, N, W, detail:: } } - constexpr bit_array(const bit_array& other) noexcept + constexpr bit_array(const bit_array& other) noexcept : storage(other.storage) {} - constexpr bit_array(const bit_array&& other) noexcept + constexpr bit_array(const bit_array&& other) noexcept : storage(other.storage) {} constexpr bit_array(const bit_sized_range auto& other) { @@ -148,7 +144,7 @@ class bit_array : public bit_array_base, T, N, W, detail:: constexpr bit_array(const std::initializer_list init) : storage{} { // Make sure we handle the case where init.size() != Words auto it = init.begin(); - for (size_type i = 0; i < std::min(AlignedWords, init.size()); ++i, ++it) { + for (size_type i = 0; i < std::min(Words(N), init.size()); ++i, ++it) { storage[i] = *it; } } @@ -173,7 +169,7 @@ class bit_array : public bit_array_base, T, N, W, detail:: /* * Assignment */ - constexpr bit_array& operator=(const bit_array& other) = default; + constexpr bit_array& operator=(const bit_array& other) = default; constexpr bit_array& operator=(const bit_sized_range auto& other) { if (other.size() != this->size()) [[unlikely]] { @@ -183,7 +179,7 @@ class bit_array : public bit_array_base, T, N, W, detail:: return *this; }; - constexpr bit_array& operator=(bit_array&& other) noexcept { + constexpr bit_array& operator=(bit_array&& other) noexcept { std::copy(other.storage.begin(), other.storage.end(), storage.begin()); return *this; } @@ -217,7 +213,7 @@ class bit_array : public bit_array_base, T, N, W, detail:: /* * Operations */ - constexpr void swap(bit_array& other) noexcept { + constexpr void swap(bit_array& other) noexcept { std::swap(this->storage, other.storage); } }; diff --git a/include/bitlib/bit-containers/bit_array_base.hpp b/include/bitlib/bit-containers/bit_array_base.hpp index 009a29e2..92414d4e 100644 --- a/include/bitlib/bit-containers/bit_array_base.hpp +++ b/include/bitlib/bit-containers/bit_array_base.hpp @@ -34,7 +34,6 @@ class bit_array_ref; template class bit_array; // ========================================================================== // @@ -67,8 +66,8 @@ class bit_array_base { using value_type = T; using size_type = std::size_t; using difference_type = std::ptrdiff_t; - using reference = typename std::conditional, bit_reference, T&>::type; - using const_reference = typename std::conditional, const bit_reference, const T&>::type; + using reference = typename std::conditional, bit_reference, T&>::type; + using const_reference = typename std::conditional, const bit_reference, const T&>::type; using pointer = typename std::conditional, bit_pointer, T&>::type; using const_pointer = const pointer; using iterator = It; @@ -196,7 +195,7 @@ class bit_array_base { return integral; } - using compatible_bitarray = bit_array; + using compatible_bitarray = bit_array; constexpr compatible_bitarray operator~() { compatible_bitarray result(derived().size()); diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index 4d6a7635..a446d831 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -37,11 +37,11 @@ using bit_array_cit = typename std::conditional, const word_type*>::type; } // namespace detail -template -class bit_array - : public bit_array_base, T, std::dynamic_extent, W, detail::bit_array_it, detail::bit_array_cit> { +template +class bit_array + : public bit_array_base, T, std::dynamic_extent, W, detail::bit_array_it, detail::bit_array_cit> { public: - using base = bit_array_base, T, std::dynamic_extent, W, detail::bit_array_it, detail::bit_array_cit>; + using base = bit_array_base, T, std::dynamic_extent, W, detail::bit_array_it, detail::bit_array_cit>; using base::end; using typename base::const_iterator; using typename base::const_pointer; @@ -55,51 +55,98 @@ class bit_array using typename base::word_type; private: - struct deleter { - size_type words; - void operator()(word_type* const p) const { - for (size_type i = 0; i < words; ++i) { - (p + i)->~word_type(); + using word_type_ptr = std::unique_ptr; + static const size_type FixedWords = sizeof(word_type_ptr) / sizeof(word_type); + static const size_type FixedBits = FixedWords * bitsof(); + + const size_type m_size; + + union Storage { + word_type_ptr pointer; + word_type fixed[FixedWords]; + + Storage(size_type words) { + if (words > FixedWords) { + new (&pointer) word_type_ptr(new word_type[words]); + } else { + for (size_type i = 0; i < words; ++i) { + new (&fixed[i]) word_type(); + } } - ::operator delete(p, V); } - }; - const size_type m_size; - const std::unique_ptr storage; + Storage(size_type words, Storage&& other) { + if (words > FixedWords) { + new (&pointer) word_type_ptr(std::move(other.pointer)); + other.pointer = nullptr; // Prevent double deletion + } else { + for (size_type i = 0; i < words; ++i) { + new (&fixed[i]) word_type(std::move(other.fixed[i])); + } + } + } + + Storage(size_type words, const Storage& other) { + if (words > FixedWords) { + new (&pointer) word_type_ptr(new word_type[words]); + std::memcpy(pointer.get(), other.pointer.get(), words * sizeof(word_type)); + } else { + for (size_type i = 0; i < words; ++i) { + new (&fixed[i]) word_type(other.fixed[i]); + } + } + } + template + Storage(size_type words, const U& val) { + if (words > FixedWords) { + new (&pointer) word_type_ptr(new word_type[words](val)); + } else { + for (size_type i = 0; i < words; ++i) { + new (&fixed[i]) word_type(val); + } + } + } + Storage() = delete; + ~Storage() {} + } storage; static constexpr size_type Words(size_type N) { return (N * bitsof() + bitsof() - 1) / bitsof(); }; - - static constexpr size_t AlignedBytes(size_t N) { - return (Words(N) * sizeof(word_type) + static_cast(V) - 1) & ~(static_cast(V) - 1); - }; + /* + constexpr size_type Words() const { + return Words(m_size); + }*/ public: + ~bit_array() { + if (size() > FixedBits) { + storage.pointer.~unique_ptr(); + } else { + for (size_type i = 0; i < Words(size()); ++i) { + storage.fixed[i].~word_type(); + } + } + } + /* * Constructors, copies and moves... */ bit_array() = delete; constexpr bit_array(const size_type size) - : m_size(size), - storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { - //std::uninitialized_fill_n(this->begin(), Words(m_size), word_type()); - for (size_type i = 0; i < Words(m_size); ++i) { - new (storage.get() + i) word_type(); - } + : m_size(size), storage(Words(size)) { } template constexpr bit_array(const size_type size, const U& integral) - : m_size(size), - storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { + : bit_array(size) { assert(bitsof() <= size); - for (size_type i = 0; i < Words(m_size); ++i) { - new (storage.get() + i) word_type(); + if (size() > FixedBits) { + std::memcpy(storage.pointer, &integral, sizeof(integral)); + } else { + std::memcpy(storage.fixed, &integral, sizeof(integral)); } - std::memcpy(storage.get(), &integral, sizeof(integral)); bool sign_extend = false; if constexpr (std::is_signed_v) { sign_extend = (integral & (1 << (bitsof() - 1))) ? true : false; @@ -115,50 +162,32 @@ class bit_array } } + constexpr bit_array(const size_type size, const word_type val) + : m_size(size), storage(Words(size), val) { + } + constexpr bit_array(const size_type size, const value_type bit_val) - : m_size(size), - storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { - if constexpr (std::is_same::value) { - for (size_type i = 0; i < Words(m_size); ++i) { - new (storage.get() + i) word_type(bit_val); - } - } else { - for (size_type i = 0; i < Words(m_size); ++i) { - new (storage.get() + i) word_type(); - } - this->fill(bit_val); - } + requires(!std::is_same::value) + : m_size(size), storage(Words(size)) { + this->fill(bit_val); } - constexpr bit_array(const bit_array& other) - : m_size(other.size()), - storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { - for (size_type i = 0; i < Words(m_size); ++i) { - new (storage.get() + i) word_type(*(other.storage.get() + i)); - } + constexpr bit_array(const bit_array& other) + : m_size(other.size()), storage(Words(size()), other.storage) { } constexpr bit_array(const bit_sized_range auto& other) - : m_size(other.size()), - storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { + : bit_array(other.size()) { ::bit::copy(other.begin(), other.end(), this->begin()); } - constexpr bit_array(const bit_array&& other) - : m_size(other.size()), - storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { - for (size_type i = 0; i < Words(m_size); ++i) { - new (storage.get() + i) word_type(*(other.storage.get() + i)); - } + constexpr bit_array(bit_array&& other) + : m_size(other.size()), storage(Words(size()), std::move(other.storage)) { } constexpr bit_array(const std::initializer_list init) requires(!std::is_same_v) - : m_size(init.size()), - storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { - for (size_type i = 0; i < Words(m_size); ++i) { - new (storage.get() + i) word_type(); - } + : bit_array(init.size()) { std::copy(init.begin(), init.end(), this->begin()); } @@ -172,23 +201,15 @@ class bit_array } #endif - constexpr bit_array(const std::initializer_list init) - : m_size(bitsof() * init.size()), - storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { - size_type i = 0; - auto it = init.begin(); - for (; i < Words(m_size) && it != init.end(); ++i, ++it) { - new (storage.get() + i) word_type(*it); - } + template + constexpr bit_array(const std::initializer_list init) + : bit_array(bitsof() * init.size()) { + std::copy(init.begin(), init.end(), data()); } constexpr bit_array(const std::string_view s) requires(std::is_same_v) - : m_size((std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))), - storage(static_cast(::operator new(AlignedBytes(m_size), V)), deleter{Words(m_size)}) { - for (size_type i = 0; i < Words(m_size); ++i) { - new (storage.get() + i) word_type(); - } + : bit_array((std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))) { size_type i = 0; for (char c : s) { if (c == '0') { @@ -199,20 +220,21 @@ class bit_array } } - ~bit_array() = default; - /* * Assignment */ - constexpr bit_array& operator=(const bit_array& other) { - if (nullptr == storage.get() || m_size != other.m_size) { + constexpr bit_array& operator=(const bit_array& other) { + if (nullptr == data() || size() != other.size()) { throw std::invalid_argument("Cannot reassign bit_array size"); } + if (this == &other) [[unlikely]] { + return *this; + } std::copy(other.begin(), other.end(), this->begin()); return *this; } - constexpr bit_array& operator=(const bit_sized_range auto& other) { + constexpr bit_array& operator=(const bit_sized_range auto& other) { if (other.size() != this->size()) [[unlikely]] { throw std::invalid_argument("other bit_range contains an invalid number of bits for bit_array."); } @@ -220,11 +242,12 @@ class bit_array return *this; }; - constexpr bit_array& operator=(bit_array&& other) { - if (nullptr == storage.get() || m_size != other.m_size) { + constexpr bit_array& operator=(bit_array&& other) { + if (nullptr == data() || size() != other.size()) { throw std::invalid_argument("Cannot reassign bit_array size"); } - std::copy(other.begin(), other.end(), this->begin()); + bit_array temp(std::move(other)); + swap(temp); return *this; } @@ -232,22 +255,38 @@ class bit_array * Element Access */ constexpr word_type* data() noexcept { - return size() ? storage.get() : nullptr; + if (size() > FixedBits) { + return storage.pointer.get(); + } else { + return storage.fixed; + } } constexpr const word_type* data() const noexcept { - return size() ? storage.get() : nullptr; + if (size() > FixedBits) { + return storage.pointer.get(); + } else { + return storage.fixed; + } } /* * Iterators */ constexpr iterator begin() noexcept { - return iterator(storage.get()); + if (size() > FixedBits) { + return iterator(storage.pointer.get()); + } else { + return iterator(storage.fixed); + } } constexpr const_iterator begin() const noexcept { - return const_iterator(storage.get()); + if (size() > FixedBits) { + return const_iterator(storage.pointer.get()); + } else { + return const_iterator(storage.fixed); + } } /* @@ -260,13 +299,14 @@ class bit_array /* * Operations */ - constexpr void swap(bit_array& other) noexcept { - assert(this->m_size == other.m_size); - // Cannot just swap storage as it is const. - W* it1 = this->storage.get(); - W* it2 = other.storage.get(); - for (size_type i = 0; i < Words(this->m_size); i++, it1++, it2++) { - std::swap(*it1, *it2); + constexpr void swap(bit_array& other) noexcept { + assert(size() == other.size()); + if (size() > FixedBits) { + std::swap(this->storage.pointer, other.storage.pointer); + } else { + for (size_type i = 0; i < Words(size()); ++i) { + std::swap(this->storage.fixed[i], other.storage.fixed[i]); + } } } }; diff --git a/include/bitlib/bit-containers/bit_array_ref.hpp b/include/bitlib/bit-containers/bit_array_ref.hpp index a55377cc..038c9bc9 100644 --- a/include/bitlib/bit-containers/bit_array_ref.hpp +++ b/include/bitlib/bit-containers/bit_array_ref.hpp @@ -37,7 +37,7 @@ namespace bit { * @tparam T The value type (typically bit_value) * @tparam W The word type used for storage */ -template +template class bit_array_ref : public bit_array_base, T, std::dynamic_extent, W, bit_iterator, bit_iterator> { public: diff --git a/include/bitlib/bit-containers/bit_bitsof.hpp b/include/bitlib/bit-containers/bit_bitsof.hpp index 6eafe2c0..ca369407 100644 --- a/include/bitlib/bit-containers/bit_bitsof.hpp +++ b/include/bitlib/bit-containers/bit_bitsof.hpp @@ -23,11 +23,18 @@ concept HasBits = requires { }; // General case: for types that do not have a bits member. -template requires (!HasBits) +template + requires(!HasBits && !std::is_integral_v) constexpr std::size_t bitsof() noexcept { return 8u * sizeof(T); } +template + requires(!HasBits && std::is_integral_v) +constexpr std::size_t bitsof() noexcept { + return std::integral_constant>::digits>::value; +} + // Overload for types with a bits member. template requires HasBits constexpr std::size_t bitsof() noexcept { diff --git a/include/bitlib/bit-containers/bit_mdspan_accessor.hpp b/include/bitlib/bit-containers/bit_mdspan_accessor.hpp index 68b6febe..a948a117 100644 --- a/include/bitlib/bit-containers/bit_mdspan_accessor.hpp +++ b/include/bitlib/bit-containers/bit_mdspan_accessor.hpp @@ -14,7 +14,7 @@ #include "bitlib/bit-iterator/bit_value.hpp" namespace bit { -template +template struct bit_default_accessor { using element_type = bit_value; using data_handle_type = bit_pointer; diff --git a/include/bitlib/bit-containers/bit_span.hpp b/include/bitlib/bit-containers/bit_span.hpp index 74c8ed2f..a1bd52a5 100644 --- a/include/bitlib/bit-containers/bit_span.hpp +++ b/include/bitlib/bit-containers/bit_span.hpp @@ -41,8 +41,8 @@ class bit_span : private bit_span_storage { using word_type = WordType; using size_type = std::size_t; using difference_type = std::ptrdiff_t; - using reference = bit_reference; - using const_reference = const bit_reference; + using reference = bit_reference; + using const_reference = const bit_reference; using pointer = bit_pointer; using const_pointer = const pointer; using iterator = bit_iterator; diff --git a/include/bitlib/bit-containers/bit_vector.hpp b/include/bitlib/bit-containers/bit_vector.hpp index cb0b3ff2..a3fa9285 100644 --- a/include/bitlib/bit-containers/bit_vector.hpp +++ b/include/bitlib/bit-containers/bit_vector.hpp @@ -76,7 +76,7 @@ class bit_vector { using allocator_type = Allocator; using size_type = std::size_t; using difference_type = std::ptrdiff_t; - using reference = bit_reference; + using reference = bit_reference; // typename std::vector::reference>; using const_reference = const reference; using pointer = bit_pointer; using iterator = bit_iterator::iterator>; diff --git a/include/bitlib/bit-iterator/bit.hpp b/include/bitlib/bit-iterator/bit.hpp index 8446eae2..d0b5f70d 100644 --- a/include/bitlib/bit-iterator/bit.hpp +++ b/include/bitlib/bit-iterator/bit.hpp @@ -23,6 +23,9 @@ #include "bit_iterator.hpp" #include "bit_reference.hpp" #include "bit_value.hpp" +#include "bit_word_pointer_adapter.hpp" +#include "bit_word_reference_adapter.hpp" + // Third-party libraries // Miscellaneous // ========================================================================== // diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index fbef2aa8..c59b97dd 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -40,9 +40,12 @@ namespace bit { class bit_value; template class bit_reference; -template class bit_iterator; +template +class bit_iterator; template using bit_pointer = bit_iterator; +template +class bit_word_pointer_adapter; // ========================================================================== // @@ -68,24 +71,22 @@ struct binary_digits : binary_digits_impl> {}; // Binary digits value template constexpr std::size_t binary_digits_v = binary_digits::value; -/* ************************************************************************** */ +/*************************************************************************** */ -#if 0 -template -using smallest_integral = std::conditional_t< - (sizeof(T) <= sizeof(std::uint8_t)), +template +using ceil_integral = std::conditional_t< + (N <= bitsof()), std::uint8_t, std::conditional_t< - (sizeof(T) <= sizeof(std::uint16_t)), + (N <= bitsof()), std::uint16_t, std::conditional_t< - (sizeof(T) <= sizeof(std::uint32_t)), + (N <= bitsof()), std::uint32_t, std::conditional_t< - (sizeof(T) <= sizeof(std::uint64_t)), + (N <= bitsof()), std::uint64_t, - T>>>>; -#endif + std::uint64_t>>>>; /* *************** IMPLEMENTATION DETAILS: CV ITERATOR TRAITS *************** */ // Cv iterator traits structure definition @@ -106,8 +107,9 @@ struct _cv_iterator_traits using _raw_pointer_t = typename std::remove_cv<_no_pointer_t>::type; using _raw_reference_t = typename std::remove_cv<_no_reference_t>::type; using _cv_value_t = _no_reference_t; - static_assert(std::is_same<_raw_pointer_t, _raw_value_t>::value, ""); - static_assert(std::is_same<_raw_reference_t, _raw_value_t>::value, ""); + + // static_assert(std::is_same<_raw_pointer_t, _raw_value_t>::value, ""); + // static_assert(std::is_same<_raw_reference_t, _raw_value_t>::value, ""); // Types public: @@ -119,8 +121,7 @@ struct _cv_iterator_traits }; /* ************************************************************************** */ - - +#if 0 /* *********** IMPLEMENTATION DETAILS: NARROWEST AND WIDEST TYPES *********** */ // Narrowest type structure declaration template @@ -282,8 +283,53 @@ struct _wider_type template using _wider_type_t = typename _wider_type::type; /* ************************************************************************** */ +#endif + +/* +exact_ceil_integral is used to determine the exact integral type that a proxy reference +can be implicitly converted to. +If the proxy reference supports multiple types, it will pick the largest, preferring unsigned. +*/ +template +struct exact_ceil_integral { + private: + using U = std::remove_cvref_t; + + template + static constexpr bool is_exactly_convertible() { + if constexpr (!std::is_convertible_v) { + return false; + } else { + // Try brace-initialization to detect narrowing + return requires(From f) { + To{f}; // will fail if narrowing + }; + } + } + public: + using type = std::conditional_t< + is_exactly_convertible(), uint64_t, + std::conditional_t< + is_exactly_convertible(), int64_t, + std::conditional_t< + is_exactly_convertible(), uint32_t, + std::conditional_t< + is_exactly_convertible(), int32_t, + std::conditional_t< + is_exactly_convertible(), uint16_t, + std::conditional_t< + is_exactly_convertible(), int16_t, + std::conditional_t< + is_exactly_convertible(), uint8_t, + std::conditional_t< + is_exactly_convertible(), int8_t, + void>>>>>>>>; +}; +// Helper alias +template +using exact_ceil_integral_t = typename exact_ceil_integral::type; /* ******************* IMPLEMENTATION DETAILS: UTILITIES ******************** */ // Assertions @@ -318,6 +364,7 @@ constexpr T _bextr(T src, T start, T len) noexcept; template constexpr T _bextr(T src, T start, T len, X...) noexcept; +#if 0 // Parallel bits deposit template constexpr T _pdep(T src, T msk) noexcept; @@ -336,6 +383,8 @@ constexpr T _byteswap(T src) noexcept; template constexpr T _byteswap(T src, X...) noexcept; +#endif + // Bit swap template constexpr T _bitswap(T src) noexcept; @@ -344,12 +393,6 @@ constexpr T _bitswap(T src) noexcept; template constexpr T _bitswap() noexcept; -// Bit blend -template -constexpr T _bitblend(T src0, T src1, T msk) noexcept; -template -constexpr T _bitblend(T src0, T src1, T start, T len) noexcept; - // Bit exchange template constexpr void _bitexch(T& src0, T& src1, T msk) noexcept; @@ -401,10 +444,15 @@ constexpr T _mulx(T src0, T src1, T* hi, X...) noexcept; Logical shift right */ template -constexpr T lsr(const T& val, const size_type shift) { +constexpr T lsr(const T val, const size_type shift) { return static_cast(static_cast>(val) >> shift); } +template +constexpr exact_ceil_integral_t lsr(const T val, const size_type shift) { + return static_cast>(static_cast>>(val) >> shift); +} + enum class _mask_len { unknown, in_range @@ -572,6 +620,7 @@ constexpr T _bextr(T src, T start, T len, X...) noexcept { } // -------------------------------------------------------------------------- // +#if 0 // ------- IMPLEMENTATION DETAILS: INSTRUCTIONS: PARALLEL BIT DEPOSIT ------- // // Deposits bits according to a mask with compiler instrinsics template @@ -691,6 +740,7 @@ constexpr T _byteswap(T src, X...) noexcept { return src; } // -------------------------------------------------------------------------- // +#endif // ------------- IMPLEMENTATION DETAILS: INSTRUCTIONS: BIT SWAP ------------- // // Reverses the order of the bits with or without of compiler intrinsics @@ -752,18 +802,34 @@ constexpr T _bitswap() noexcept { // ------------ IMPLEMENTATION DETAILS: INSTRUCTIONS: BIT BLEND ------------- // // Replaces bits of src0 by the ones of src1 where the mask is true -template -constexpr T _bitblend(T src0, T src1, T msk) noexcept { - static_assert(binary_digits::value, ""); + +template +constexpr exact_ceil_integral_t _bitblend( + const T src0_, + const U src1_, + const exact_ceil_integral_t msk) noexcept + requires(std::is_same_v, exact_ceil_integral_t>) +{ + static_assert(binary_digits>::value, ""); + const exact_ceil_integral_t src0 = static_cast>(src0_); + const exact_ceil_integral_t src1 = static_cast>(src1_); return src0 ^ ((src0 ^ src1) & msk); } // Replaces len bits of src0 by the ones of src1 starting at start -template -constexpr T _bitblend(T src0, T src1, T start, T len) noexcept { - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - const T msk = _mask(len) << start; +template +constexpr exact_ceil_integral_t _bitblend( + const T src0_, + const U src1_, + const exact_ceil_integral_t start, + const exact_ceil_integral_t len) noexcept + requires(std::is_same_v, exact_ceil_integral_t>) +{ + static_assert(binary_digits>::value, ""); + constexpr exact_ceil_integral_t digits = bitsof>(); + const exact_ceil_integral_t src0 = static_cast>(src0_); + const exact_ceil_integral_t src1 = static_cast>(src1_); + const exact_ceil_integral_t msk = _mask, _mask_len::unknown>(len) << start; return src0 ^ ((src0 ^ src1) & msk * (start < digits)); } // -------------------------------------------------------------------------- // @@ -951,7 +1017,7 @@ template constexpr T _mulx(T src0, T src1, T* hi) noexcept { static_assert(binary_digits::value, ""); - using wider_t = typename _wider_type::type; + using wider_t = ceil_integral() + bitsof()>; constexpr T digits = binary_digits::value; wider_t tmp = 0; T128 tmp128 = 0; @@ -994,7 +1060,50 @@ constexpr T _mulx(T src0, T src1, T* hi, X...) noexcept } // -------------------------------------------------------------------------- // +template +constexpr auto with_bit_iterator_adapter( + bit_iterator first, + bit_iterator last, + bit_iterator d_first) { + using dst_word_type = typename bit_iterator::word_type; + using src_word_type = typename bit_iterator::word_type; + if constexpr (!std::is_same_v && bitsof() != bitsof()) { + if constexpr (bitsof() > bitsof()) { + bit_iterator> adapted_first( + bit_word_pointer_adapter(first.base(), first.position() / bitsof()), first.position() % bitsof()); + bit_iterator> adapted_last( + bit_word_pointer_adapter(last.base(), last.position() / bitsof()), last.position() % bitsof()); + return AlgoFunc{}(adapted_first, adapted_last, d_first); + } else { + bit_iterator> adapted_d_first( + bit_word_pointer_adapter(d_first.base(), d_first.position() / bitsof()), d_first.position() % bitsof()); + return AlgoFunc{}(first, last, adapted_d_first); + } + } else { + return AlgoFunc{}(first, last, d_first); + } +} +template +constexpr auto with_bit_iterator_adapter( + bit_iterator first, + bit_iterator last) { + using dst_word_type = typename bit_iterator::word_type; + using src_word_type = typename bit_iterator::word_type; + if constexpr (!std::is_same_v && bitsof() != bitsof()) { + if constexpr (bitsof() > bitsof()) { + bit_iterator> adapted_first( + bit_word_pointer_adapter(first.base(), first.position() / bitsof()), first.position() % bitsof()); + return AlgoFunc{}(adapted_first, last); + } else { + bit_iterator> adapted_last( + bit_word_pointer_adapter(last.base(), last.position() / bitsof()), last.position() % bitsof()); + return AlgoFunc{}(first, adapted_last); + } + } else { + return AlgoFunc{}(first, last); + } +} // ========================================================================== // } // namespace bit diff --git a/include/bitlib/bit-iterator/bit_iterator.hpp b/include/bitlib/bit-iterator/bit_iterator.hpp index f26fc8f2..458a4471 100644 --- a/include/bitlib/bit-iterator/bit_iterator.hpp +++ b/include/bitlib/bit-iterator/bit_iterator.hpp @@ -30,6 +30,10 @@ namespace bit { /* ****************************** BIT ITERATOR ****************************** */ + +template +class bit_word_pointer_adapter; + // Bit iterator class definition template class bit_iterator @@ -40,17 +44,17 @@ class bit_iterator // Assertions private: using _traits_t = _cv_iterator_traits; - static_assert(binary_digits::value, ""); + static_assert(binary_digits>::value, ""); // Types public: using iterator_type = Iterator; - using word_type = typename _traits_t::value_type; + using word_type = std::iter_value_t; using iterator_category = typename _traits_t::iterator_category; using value_type = bit_value; using difference_type = std::ptrdiff_t; using pointer = bit_pointer; - using reference = bit_reference; + using reference = bit_reference; // typename _traits_t::reference; using size_type = std::size_t; using mask_type = std::make_unsigned_t>; @@ -66,6 +70,9 @@ class bit_iterator explicit constexpr bit_iterator(const pointer& ptr) requires std::constructible_from; + template + constexpr bit_iterator(const bit_iterator>& other); + // Assignment public: constexpr bit_iterator& operator=(const bit_iterator& other); @@ -160,6 +167,13 @@ constexpr bit_iterator::bit_iterator(const pointer& ptr) : _current(ptr._current), _position(ptr.position()) { assert(_position < bitsof()); } + +template +template +constexpr bit_iterator::bit_iterator(const bit_iterator>& other) + : _current(other.base().base()), _position(other.base().index() * bitsof::value_type>() + other.position()) { + assert(_position < bitsof()); +} // -------------------------------------------------------------------------- // @@ -366,7 +380,7 @@ operator-(const bit_iterator& lhs, const bit_iterator& rhs) { } static_assert(bit_iterator_c>, "bit_iterator does not satisfy bit_iterator_c concept!"); -static_assert(bit_pointer_c, bit_reference>, "bit_pointer does not satisfy bit_pointer_c concept!"); +static_assert(bit_pointer_c, bit_reference>, "bit_pointer does not satisfy bit_pointer_c concept!"); static_assert(bit_iterator_c>, "bit_pointer does not satisfy bit_iterator_c concept!"); // ========================================================================== // diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index ee4327af..55fc4bf7 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -28,8 +28,12 @@ namespace bit { /* ***************************** BIT REFERENCE ****************************** */ // Bit reference class definition -template +template class bit_reference { + public: + using WordType = std::remove_reference_t; + + private: // Assertions static_assert(binary_digits::value, ""); @@ -46,11 +50,13 @@ class bit_reference { // Lifecycle public: template - requires (std::is_const_v == std::is_const_v) - constexpr bit_reference(const bit_reference& other) noexcept; + requires(std::is_const_v> == std::is_const_v>) + constexpr bit_reference(const bit_reference& other) noexcept + : _ref(other._ref), _mask(other._mask) { + } constexpr bit_reference(const bit_reference& other) noexcept; - explicit constexpr bit_reference(word_type& ref) noexcept; - constexpr bit_reference(word_type& ref, size_type pos); + explicit constexpr bit_reference(WordRef ref) noexcept; + constexpr bit_reference(WordRef ref, size_type pos); // Assignment public: @@ -96,7 +102,7 @@ class bit_reference { // Implementation details: data members private: - word_type& _ref; + WordRef _ref; const mask_type _mask; }; static_assert(bit_reference_c>, "bit_reference does not satisfy bit_reference_c concept!"); @@ -116,31 +122,22 @@ template std::basic_ostream& operator<<(std::basic_ostream& os, bit_reference x); /* ************************************************************************** */ - - // ------------------------ BIT REFERENCE: LIFECYCLE ------------------------ // -// Implicitly constructs a bit reference from another bit reference -template -template -requires (std::is_const_v == std::is_const_v) -constexpr bit_reference::bit_reference(const bit_reference& other) noexcept - : _ref(other._ref), _mask(other._mask) { -} -template -constexpr bit_reference::bit_reference(const bit_reference& other) noexcept +template +constexpr bit_reference::bit_reference(const bit_reference& other) noexcept : _ref(other._ref), _mask(other._mask) { } // Explicitly constructs an aligned bit reference -template -constexpr bit_reference::bit_reference(word_type& ref) noexcept +template +constexpr bit_reference::bit_reference(WordRef ref) noexcept : _ref(ref), _mask(1) { } // Explicitly constructs an unaligned bit reference -template -constexpr bit_reference::bit_reference(word_type& ref, size_type pos) +template +constexpr bit_reference::bit_reference(WordRef ref, size_type pos) : _ref(ref), _mask(static_cast(1) << pos) { assert(pos < binary_digits::value); } @@ -150,40 +147,40 @@ constexpr bit_reference::bit_reference(word_type& ref, size_type pos) // ----------------------- BIT REFERENCE: ASSIGNMENT ------------------------ // // Copies a bit reference to the bit reference -template -constexpr bit_reference& bit_reference::operator=(const bit_reference& other) const noexcept { +template +constexpr bit_reference& bit_reference::operator=(const bit_reference& other) const noexcept { other ? set() : reset(); - return const_cast&>(*this); + return const_cast&>(*this); } // Assigns a bit reference to the bit reference -template +template template -constexpr bit_reference& bit_reference::operator=(const bit_reference& other) const noexcept { +constexpr bit_reference& bit_reference::operator=(const bit_reference& other) const noexcept { other ? set() : reset(); - return const_cast&>(*this); + return const_cast&>(*this); } // Assigns a bit value to the bit reference -template -constexpr bit_reference& bit_reference::operator=(const bit_value val) const noexcept { +template +constexpr bit_reference& bit_reference::operator=(const bit_value val) const noexcept { val ? set() : reset(); - return const_cast&>(*this); + return const_cast&>(*this); } // Assigns the aligned bit of a value to the bit reference -template -constexpr bit_reference& bit_reference::assign(word_type val) const noexcept { +template +constexpr bit_reference& bit_reference::assign(word_type val) const noexcept { val & 1 ? set() : reset(); - return const_cast&>(*this); + return const_cast&>(*this); } // Assigns an unaligned bit of a value to the bit reference -template -constexpr bit_reference& bit_reference::assign(word_type val, size_type pos) const { +template +constexpr bit_reference& bit_reference::assign(word_type val, size_type pos) const { assert(pos < binary_digits::value); ((val >> pos) & 1) ? set() : reset(); - return const_cast&>(*this); + return const_cast&>(*this); } // -------------------------------------------------------------------------- // @@ -191,24 +188,24 @@ constexpr bit_reference& bit_reference::assign(word_type val // -------------- BIT REFERENCE: BITWISE ASSIGNMENT OPERATORS --------------- // // Assigns the value of the referenced bit through a bitwise and operation -template -constexpr bit_reference& bit_reference::operator&=(bit_value other) const noexcept { +template +constexpr bit_reference& bit_reference::operator&=(bit_value other) const noexcept { _ref &= ~(_mask * static_cast(!other._value)); - return const_cast&>(*this); + return const_cast&>(*this); } // Assigns the value of the referenced bit through a bitwise or operation -template -constexpr bit_reference& bit_reference::operator|=(bit_value other) const noexcept { +template +constexpr bit_reference& bit_reference::operator|=(bit_value other) const noexcept { _ref |= _mask * static_cast(other._value); - return const_cast&>(*this); + return const_cast&>(*this); } // Assigns the value of the referenced bit through a bitwise xor operation -template -constexpr bit_reference& bit_reference::operator^=(bit_value other) const noexcept { +template +constexpr bit_reference& bit_reference::operator^=(bit_value other) const noexcept { _ref ^= _mask * static_cast(other._value); - return const_cast&>(*this); + return const_cast&>(*this); } // -------------------------------------------------------------------------- // @@ -216,8 +213,8 @@ constexpr bit_reference& bit_reference::operator^=(bit_value // ----------------------- BIT REFERENCE: CONVERSION ------------------------ // // Explicitly converts the bit reference to a boolean value -template -constexpr bit_reference::operator bool() const noexcept { +template +constexpr bit_reference::operator bool() const noexcept { return _ref & _mask; } // -------------------------------------------------------------------------- // @@ -226,8 +223,8 @@ constexpr bit_reference::operator bool() const noexcept { // ------------------------- BIT REFERENCE: ACCESS -------------------------- // // Gets a bit pointer from the bit reference -template -constexpr bit_pointer bit_reference::operator&() const noexcept { +template +constexpr bit_pointer> bit_reference::operator&() const noexcept { return bit_pointer(&_ref, _tzcnt(_mask)); } // -------------------------------------------------------------------------- // @@ -236,9 +233,9 @@ constexpr bit_pointer bit_reference::operator&() const noexc // ---------------------- BIT REFERENCE: SWAP MEMBERS ----------------------- // // Swaps the value of the referenced bit with another bit reference -template +template template -void bit_reference::swap(bit_reference other) const { +void bit_reference::swap(bit_reference other) const { if (other != *this) { flip(); other.flip(); @@ -246,8 +243,8 @@ void bit_reference::swap(bit_reference other) const { } // Swaps the value of the referenced bit with a bit value -template -void bit_reference::swap(bit_value& other) const { +template +void bit_reference::swap(bit_value& other) const { if (other != *this) { flip(); other.flip(); @@ -259,32 +256,32 @@ void bit_reference::swap(bit_value& other) const { // -------------------- BIT REFERENCE: BIT MANIPULATION --------------------- // // Sets the value of the referenced bit to the provided boolean value -template -constexpr bit_reference& bit_reference::set( +template +constexpr bit_reference& bit_reference::set( bool b) const noexcept { b ? set() : reset(); - return const_cast&>(*this); + return const_cast&>(*this); } // Sets the value of the referenced bit to 1 -template -constexpr bit_reference& bit_reference::set() const noexcept { +template +constexpr bit_reference& bit_reference::set() const noexcept { _ref |= _mask; - return const_cast&>(*this); + return const_cast&>(*this); } // Resets the value of the referenced bit to 0 -template -constexpr bit_reference& bit_reference::reset() const noexcept { +template +constexpr bit_reference& bit_reference::reset() const noexcept { _ref &= ~_mask; - return const_cast&>(*this); + return const_cast&>(*this); } // Flips the value of the referenced bit -template -constexpr bit_reference& bit_reference::flip() const noexcept { +template +constexpr bit_reference& bit_reference::flip() const noexcept { _ref ^= _mask; - return const_cast&>(*this); + return const_cast&>(*this); } // -------------------------------------------------------------------------- // @@ -379,14 +376,14 @@ std::basic_ostream& operator<<(std::basic_ostream& // -------- BIT REFERENCE: IMPLEMENTATION DETAILS: FUNCTION MEMBERS --------- // // Privately explicitly constructs an aligned bit reference from a pointer -template -constexpr bit_reference::bit_reference(word_type* ptr) noexcept +template +constexpr bit_reference::bit_reference(word_type* ptr) noexcept : _ref(*ptr), _mask(1) { } // Privately explicitly constructs an unaligned bit reference from a pointer -template -constexpr bit_reference::bit_reference(word_type* ptr, size_type pos) +template +constexpr bit_reference::bit_reference(word_type* ptr, size_type pos) : _ref(*ptr), _mask(static_cast(1) << pos) { assert(pos < binary_digits::value); } diff --git a/include/bitlib/bit-iterator/bit_value.hpp b/include/bitlib/bit-iterator/bit_value.hpp index f1c1ca11..02f7c9ba 100644 --- a/include/bitlib/bit-iterator/bit_value.hpp +++ b/include/bitlib/bit-iterator/bit_value.hpp @@ -48,14 +48,14 @@ class bit_value // Assignment public: - template - constexpr bit_value& operator=(bit_reference ref) noexcept; - template - constexpr bit_value& assign(WordType val) noexcept; - template - constexpr bit_value& assign(WordType val, size_type pos); - - // Bitwise assignment operators + template + constexpr bit_value& operator=(bit_reference ref) noexcept; + template + constexpr bit_value& assign(WordType val) noexcept; + template + constexpr bit_value& assign(WordType val, size_type pos); + + // Bitwise assignment operators public: constexpr bit_value& operator&=(bit_value other) noexcept; constexpr bit_value& operator|=(bit_value other) noexcept; diff --git a/include/bitlib/bit-iterator/bit_word_pointer_adapter.hpp b/include/bitlib/bit-iterator/bit_word_pointer_adapter.hpp new file mode 100644 index 00000000..797fcd1e --- /dev/null +++ b/include/bitlib/bit-iterator/bit_word_pointer_adapter.hpp @@ -0,0 +1,213 @@ +#ifndef _BIT_WORD_POINTER_ADAPTER_HPP_ +#define _BIT_WORD_POINTER_ADAPTER_HPP_ + +#include "bit_details.hpp" + +namespace bit { + +template +class bit_word_reference_adapter; + +template +class bit_word_pointer_adapter { + private: + // Assertions + using _traits_t = _cv_iterator_traits; + static_assert(binary_digits::value, ""); + + public: + using target_word = std::remove_const_t; + using target_word_ref = _traits_t::reference; + using source_word = typename _cv_iterator_traits::value_type; + using source_word_ref = std::add_lvalue_reference_t; + + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = bit_word_reference_adapter; + using pointer = bit_word_pointer_adapter; + + using iterator_type = target_word_ptr; + using iterator_category = typename _cv_iterator_traits::iterator_category; + using value_type = target_word; + + private: + static_assert(std::is_integral_v, "source_word_ptr must be a pointer to a integral type"); + static_assert(std::is_integral_v, "target_word must be an integral type"); + + static constexpr bool is_small_to_big = sizeof(target_word) > sizeof(source_word); + static constexpr bool is_big_to_small = sizeof(target_word) < sizeof(source_word); + static constexpr size_t ratio = is_small_to_big ? sizeof(target_word) / sizeof(source_word) + : sizeof(source_word) / sizeof(target_word); + + source_word_ptr _source; + size_type _index; + + public: + explicit constexpr bit_word_pointer_adapter(source_word_ptr source) + requires(is_small_to_big) + : _source(source) { + } + explicit constexpr bit_word_pointer_adapter(source_word_ptr source, const size_type index = 0) + requires(is_big_to_small) + : _source(source), _index(index) { + } + + constexpr bit_word_pointer_adapter(const bit_word_pointer_adapter&) = default; + constexpr bit_word_pointer_adapter& operator=(const bit_word_pointer_adapter&) = default; + constexpr bit_word_pointer_adapter(bit_word_pointer_adapter&&) = default; + constexpr bit_word_pointer_adapter& operator=(bit_word_pointer_adapter&&) = default; + constexpr ~bit_word_pointer_adapter() = default; + + constexpr reference operator*() const noexcept { + if constexpr (is_small_to_big) { + return reference(*_source); + } else { + return reference(*_source, _index); + } + } + + constexpr bit_word_pointer_adapter operator->() const noexcept { + return bit_word_pointer_adapter(&*_source, _index); + } + + constexpr reference operator[](difference_type n) const noexcept { + if constexpr (is_small_to_big) { + return reference(*std::next(_source, n*ratio)); + } else { + const difference_type sum = _index + n; + difference_type src_diff = sum / static_cast(ratio); + const size_type new_index = sum % ratio; + if (n < 0 && new_index > _index) { + --src_diff; + } + return reference(*std::next(_source, src_diff), new_index); + } + } + + constexpr bit_word_pointer_adapter& operator++() noexcept { + if constexpr (is_small_to_big) { + _source = std::next(_source, ratio); + } else { + ++_index; + if (_index >= ratio) { + _source = std::next(_source); + _index -= ratio; + } + } + return *this; + } + constexpr bit_word_pointer_adapter operator++(int) noexcept { + bit_word_pointer_adapter old = *this; + ++(*this); + return old; + } + constexpr bit_word_pointer_adapter& operator--() noexcept { + if constexpr (is_small_to_big) { + _source = std::next(_source, -ratio); + } else { + if ((_index--) == 0) { + _source = std::prev(_source); + _index += ratio; + } + } + return *this; + } + constexpr bit_word_pointer_adapter operator--(int) noexcept { + bit_word_pointer_adapter old = *this; + --(*this); + return old; + } + constexpr bit_word_pointer_adapter operator+(difference_type n) const noexcept { + if constexpr (is_small_to_big) { + return bit_word_pointer_adapter(std::next(_source, n*ratio)); + } else { + const difference_type sum = _index + n; + difference_type src_diff = sum / static_cast(ratio); + const size_type new_index = sum % ratio; + if (n < 0 && new_index > _index) { + --src_diff; + } + return bit_word_pointer_adapter(std::next(_source, src_diff), new_index); + } + } + constexpr bit_word_pointer_adapter operator-(difference_type n) const noexcept { + if constexpr (is_small_to_big) { + return bit_word_pointer_adapter(std::next(_source, -n*ratio)); + } else { + const difference_type sum = _index - n; + difference_type src_diff = sum / static_cast(ratio); + const size_type new_index = sum % ratio; + if (n > 0 && new_index > _index) { + --src_diff; + } + return bit_word_pointer_adapter(std::next(_source, src_diff), new_index); + } + } + constexpr bit_word_pointer_adapter& operator+=(difference_type n) noexcept { + if constexpr (is_small_to_big) { + _source = std::next(_source, n * ratio); + } else { + const difference_type sum = _index + n; + difference_type src_diff = sum / static_cast(ratio); + const size_type new_index = sum % ratio; + if (n < 0 && new_index > _index) { + --src_diff; + } + _source = std::next(_source, src_diff); + _index = new_index; + } + return *this; + } + constexpr bit_word_pointer_adapter& operator-=(difference_type n) noexcept { + if constexpr (is_small_to_big) { + _source = std::next(_source, -n * ratio); + } else { + const difference_type sum = _index - n; + difference_type src_diff = sum / static_cast(ratio); + const size_type new_index = sum % ratio; + if (n > 0 && new_index > _index) { + --src_diff; + } + _source = std::next(_source, src_diff); + _index = new_index; + } + return *this; + } + constexpr bool operator==(const bit_word_pointer_adapter&) const = default; + constexpr auto operator<=>(const bit_word_pointer_adapter&) const = default; + + constexpr size_type index() const noexcept { + return _index; + } + constexpr source_word_ptr base() const noexcept { + return _source; + } + template + friend constexpr auto operator-( + const bit_word_pointer_adapter& lhs, + const bit_word_pointer_adapter& rhs); +}; + +template +constexpr auto operator-( + const bit_word_pointer_adapter& lhs, + const bit_word_pointer_adapter& rhs) { + using lhs_type = typename bit_word_pointer_adapter::difference_type; + using rhs_type = typename bit_word_pointer_adapter::difference_type; + static_assert( + bit_word_pointer_adapter::ratio == + bit_word_pointer_adapter::ratio, + "Cannot subtract iterators with different ratios"); + if constexpr (bit_word_pointer_adapter::is_big_to_small) { + auto main = (lhs._source - rhs._source); + return main * static_cast>(bit_word_pointer_adapter::ratio) + (static_cast(lhs._index) - static_cast(rhs._index)); + } else { + // ā€œsmall→largeā€ mode: each base‐step is 1 small word, but difference is in big words: + auto small_diff = (lhs._source - rhs._source); + return small_diff / static_cast>(bit_word_pointer_adapter::ratio); + } +} + +} // namespace bit + +#endif // _BIT_WORD_POINTER_ADAPTER_HPP_ diff --git a/include/bitlib/bit-iterator/bit_word_reference_adapter.hpp b/include/bitlib/bit-iterator/bit_word_reference_adapter.hpp new file mode 100644 index 00000000..a768d270 --- /dev/null +++ b/include/bitlib/bit-iterator/bit_word_reference_adapter.hpp @@ -0,0 +1,119 @@ +#ifndef _BIT_WORD_REFERENCE_ADAPTER_HPP_ +#define _BIT_WORD_REFERENCE_ADAPTER_HPP_ + +namespace bit { + +template +class bit_word_pointer_adapter; + +template +class bit_word_reference_adapter { + private: + // Assertions + static_assert(std::is_reference_v, "source_word_ref must be a reference type"); + + using source_word = std::remove_reference_t; + using source_word_ptr = std::add_pointer_t; + using target_word = std::remove_cvref_t; + using target_word_ptr = std::add_pointer_t; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = bit_word_pointer_adapter; + + static_assert(std::is_integral_v, "source_word_ref must reference to a integral type"); + static_assert(std::is_integral_v, "target_word must be an integral type"); + + static constexpr bool is_small_to_big = sizeof(target_word) > sizeof(source_word); + static constexpr bool is_big_to_small = sizeof(target_word) < sizeof(source_word); + static constexpr size_t ratio = is_small_to_big ? sizeof(target_word) / sizeof(source_word) + : sizeof(source_word) / sizeof(target_word); + + source_word_ref _source; + size_type _index; + + public: + // Constructor from source + explicit constexpr bit_word_reference_adapter(source_word_ref source) + requires(is_small_to_big) + : _source(source), _index(0) { + } + constexpr bit_word_reference_adapter(source_word_ref source, const size_type index) + requires(is_big_to_small) + : _source(source), _index(index) { + } + explicit constexpr bit_word_reference_adapter(const bit_word_reference_adapter& other) { + if constexpr (is_small_to_big) { + *reinterpret_cast(&_source) = *reinterpret_cast(&_source); + } else { + *(reinterpret_cast(&_source) + _index) = *(reinterpret_cast(&_source) + other._index); + } + } + constexpr bit_word_reference_adapter& operator=(const bit_word_reference_adapter& other) { + if constexpr (is_small_to_big) { + *reinterpret_cast(&_source) = *reinterpret_cast(&_source); + } else { + *(reinterpret_cast(&_source) + _index) = *(reinterpret_cast(&_source) + other._index); + } + return *this; + } + + constexpr ~bit_word_reference_adapter() = default; + + constexpr pointer operator&() const noexcept { + if constexpr (is_small_to_big) { + return pointer(&_source); + } else { + return pointer(&_source, _index); + } + } + + // Assignment from value type. + // NOT safe for big-endian systems + constexpr bit_word_reference_adapter& operator=(const target_word& value) noexcept { + if constexpr (is_small_to_big) { + *reinterpret_cast(&_source) = value; + } else { + *(reinterpret_cast(&_source) + _index) = value; + } + return *this; + } + + // Implicit conversion to value type + // NOT safe for big-endian systems + constexpr operator target_word() const noexcept { + if constexpr (is_small_to_big) { + return *reinterpret_cast(&_source); + } else { + return *(reinterpret_cast(&_source) + _index); + } + } + +#if 0 + // Compound assignment (+=, -=, etc.) + bit_word_reference_adapter& operator+=(const target_word&); + bit_word_reference_adapter& operator-=(const target_word&); + // ... others like *=, /=, etc. + + // Increment/decrement + bit_word_reference_adapter& operator++(); // Prefix ++ + target_word operator++(int); // Postfix ++ + bit_word_reference_adapter& operator--(); // Prefix -- + target_word operator--(int); // Postfix -- +#endif + + // Swap support + + void swap(bit_word_reference_adapter& other) noexcept { + std::swap(_source, other._source); + std::swap(_index, other._index); + } + + friend void swap(bit_word_reference_adapter a, bit_word_reference_adapter b) { + std::swap(a._source, b._source); + std::swap(a._index, b._index); + } +}; + +} // namespace bit + +#endif // _BIT_WORD_REFERENCE_ADAPTER_HPP_ diff --git a/include/bitlib/bit_concepts.hpp b/include/bitlib/bit_concepts.hpp index 959b86f7..01f3c14d 100644 --- a/include/bitlib/bit_concepts.hpp +++ b/include/bitlib/bit_concepts.hpp @@ -5,7 +5,7 @@ #include #include -#if defined(__GNUC__) && (__GNUC__ < 14) && !defined(__clang__) +#if (defined(__GLIBCXX__) && (_GLIBCXX_RELEASE < 14)) namespace std { struct from_range_t { explicit from_range_t() = default; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d08332da..eda549e9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -32,6 +32,7 @@ target_sources(bitlib-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/test-equal.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-fill.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-find.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-iterator_adapter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-literal.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-move.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-reverse.cpp diff --git a/test/src/fixtures.hpp b/test/src/fixtures.hpp index f5b672f1..099d038f 100644 --- a/test/src/fixtures.hpp +++ b/test/src/fixtures.hpp @@ -19,15 +19,15 @@ #include #include // Project sources -#include "bitlib/bit-iterator/bit_iterator.hpp" #include "bitlib/bit-containers/bit-containers.hpp" +#include "bitlib/bit-containers/bit_bitsof.hpp" +#include "bitlib/bit-iterator/bit_iterator.hpp" #include "test_utils.hpp" // Third-party libraries #include "gtest/gtest.h" // Miscellaneous // ========================================================================== // - //TODO tests need a lot of cleanup. We should only copy what we need from random_vec //and also refactor the vec generation to reduce duplication @@ -193,6 +193,104 @@ class DoubleRangeTest : public testing::Test { } }; TYPED_TEST_SUITE(DoubleRangeTest, BaseTypes); +template +class MixedDoubleRangeTest : public testing::Test { + protected: + using FromWordType = typename FromToPair::first_type; + using ToWordType = typename FromToPair::second_type; + + static constexpr size_t digits = bit::binary_digits::value; + static constexpr size_t word_size = 4; + static constexpr size_t bit_size = word_size * digits; + static constexpr size_t big_size = 64 * 64 * 2; + + // test data + std::vector> random_bitvecs1; + std::vector> random_bitvecs2; + std::vector> random_boolvecs1, random_boolvecs2; + + void SetUp() override { + // templated lambda to fill any bit-vector / bool-vector pair + auto fill = [&]( + std::vector>& bitvecs, + std::vector>& boolvecs, + std::vector const& small_words, + std::vector const& big_words) { + const size_t digits = bit::bitsof(); + const size_t num_small_words = ((word_size * std::max(sizeof(FromWordType), sizeof(ToWordType))) / sizeof(W)); + const size_t num_big_words = ((big_size * std::max(sizeof(FromWordType), sizeof(ToWordType))) / sizeof(W)); + const size_t small_bit_size = num_small_words * digits; + const size_t big_bit_size = num_big_words * digits; + assert(small_words.size() == num_small_words); + assert(big_words.size() == num_big_words); + // small-size runs + for (size_t cont_size = 1; cont_size < small_bit_size; ++cont_size) { + bit::bit_vector bv(small_bit_size); + std::copy(small_words.begin(), small_words.end(), bv.begin().base()); + bv.resize(cont_size); + bitvecs.push_back(bv); + boolvecs.push_back(boolvec_from_bitvec(bv)); + } + // ā€œbigā€ runs around (big_size-1)*digits +/-4 + const auto max_digits = std::max(bit::bitsof(), bit::bitsof()); + for (int i = -4; i < 4; ++i) { + size_t cont_size = big_bit_size - max_digits + i; + bit::bit_vector bv(big_bit_size); + std::copy(big_words.begin(), big_words.end(), + bv.begin().base()); + bv.resize(cont_size); + bitvecs.push_back(bv); + boolvecs.push_back(boolvec_from_bitvec(bv)); + } + }; + + // — fill for FromWordType — + const auto small_words1 = (word_size * std::max(sizeof(FromWordType), sizeof(ToWordType))) / sizeof(FromWordType); + const auto big_words1 = (big_size * std::max(sizeof(FromWordType), sizeof(ToWordType))) / sizeof(FromWordType); + auto small1 = get_random_vec(small_words1); + auto big1 = get_random_vec(big_words1); + fill.template operator()( + random_bitvecs1, + random_boolvecs1, + small1, big1); + + // — fill for ToWordType — + const auto small_words2 = (word_size * std::max(sizeof(FromWordType), sizeof(ToWordType))) / sizeof(ToWordType); + const auto big_words2 = (big_size * std::max(sizeof(FromWordType), sizeof(ToWordType))) / sizeof(ToWordType); + auto small2 = get_random_vec(small_words2); + auto big2 = get_random_vec(big_words2); + fill.template operator()( + random_bitvecs2, + random_boolvecs2, + small2, big2); + + // now random_bitvecs1.size() == random_bitvecs2.size() + } +}; + +// ----------------------------------------------------------------------------- +// Instantiate for the pairs you care about +// ----------------------------------------------------------------------------- +using TypePairs = ::testing::Types< + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair, + std::pair>; + +TYPED_TEST_SUITE(MixedDoubleRangeTest, TypePairs); + // ========================================================================== // #endif // _FIXTURES_HPP_INCLUDED // ========================================================================== // diff --git a/test/src/test-array.cpp b/test/src/test-array.cpp index 578ce026..d7b60752 100644 --- a/test/src/test-array.cpp +++ b/test/src/test-array.cpp @@ -37,24 +37,24 @@ TEST(ArrayTest, BitsOf) { } TEST(ArrayTest, BasicIteration) { - // <-- LSB, apparently šŸ™„ - bit::bit_array barr("0110_0101_110"); - int i = 0; - for (const auto& bbit : barr) { - switch (10 - i++) { - case 0: EXPECT_EQ(bit::bit0, bbit); break; - case 1: EXPECT_EQ(bit::bit1, bbit); break; - case 2: EXPECT_EQ(bit::bit1, bbit); break; - case 3: EXPECT_EQ(bit::bit1, bbit); break; - case 4: EXPECT_EQ(bit::bit0, bbit); break; - case 5: EXPECT_EQ(bit::bit1, bbit); break; - case 6: EXPECT_EQ(bit::bit0, bbit); break; - case 7: EXPECT_EQ(bit::bit0, bbit); break; - case 8: EXPECT_EQ(bit::bit1, bbit); break; - case 9: EXPECT_EQ(bit::bit1, bbit); break; - case 10: EXPECT_EQ(bit::bit0, bbit); break; - } - } + // <-- LSB, apparently šŸ™„ + bit::bit_array barr("0110_0101_110"); + int i = 0; + for (const auto& bbit : barr) { + switch (10 - i++) { + case 0: EXPECT_EQ(bit::bit0, bbit); break; + case 1: EXPECT_EQ(bit::bit1, bbit); break; + case 2: EXPECT_EQ(bit::bit1, bbit); break; + case 3: EXPECT_EQ(bit::bit1, bbit); break; + case 4: EXPECT_EQ(bit::bit0, bbit); break; + case 5: EXPECT_EQ(bit::bit1, bbit); break; + case 6: EXPECT_EQ(bit::bit0, bbit); break; + case 7: EXPECT_EQ(bit::bit0, bbit); break; + case 8: EXPECT_EQ(bit::bit1, bbit); break; + case 9: EXPECT_EQ(bit::bit1, bbit); break; + case 10: EXPECT_EQ(bit::bit0, bbit); break; + } + } } TEST(ArrayTest, ZeroSize) { @@ -314,11 +314,11 @@ TEST(BitArrayDynamicTest, InitializerListWordTypeConstructorWorks) { // For this test, we assume that the initializer list for WordType initializes the underlying storage. // Here we use two bytes as an example. std::initializer_list init = {0b10101010, 0b01010101}; - bit::bit_array<> arr(init); + bit::bit_array arr(init); // Assuming each std::uint8_t provides 8 bits, we expect the size to be the number of initializer elements * 8. EXPECT_EQ(arr.size(), init.size() * 8u); // Check that the underlying storage matches the initializer values. - const std::uint8_t* data = arr.data(); + const std::uint8_t* data = reinterpret_cast(arr.data()); auto wordIt = init.begin(); for (std::size_t i = 0; i < init.size(); ++i) { EXPECT_EQ(data[i], *(wordIt++)); @@ -443,7 +443,7 @@ TEST(BitArrayDynamicTest, StructOfBitArray) { } TEST(BitArrayDynamicTest, StringConstructor) { - bit::bit_array<> arr{"01001101"}; + bit::bit_array<> arr("01001101"); EXPECT_EQ(arr.size(), 8); @@ -457,22 +457,6 @@ TEST(BitArrayDynamicTest, StringConstructor) { EXPECT_EQ(arr[0], bit::bit0); } -TEST(BitArrayDynamicTest, TwoDBitArraySizeValueConstructor) { - bit::bit_array> arr( - 16, - bit::bit_array<>(4, bit::bit1)); - EXPECT_EQ(arr.size(), 16); - EXPECT_EQ(arr[0].size(), 4); -} - -TEST(BitArrayDynamicTest, TwoDBitArraySizeConstructor) { - bit::bit_array> arr( - 16, - bit::bit_array<>(4)); - EXPECT_EQ(arr.size(), 16); - EXPECT_EQ(arr[0].size(), 4); -} - // Test comparison operators (== and !=). TEST(BitArrayDynamicTest, ComparisonOperators) { bit::bit_array ba1{bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; diff --git a/test/src/test-array_ref.cpp b/test/src/test-array_ref.cpp index 4db45b17..fb100303 100644 --- a/test/src/test-array_ref.cpp +++ b/test/src/test-array_ref.cpp @@ -156,7 +156,7 @@ TEST(BitArrayRef, BitPointerConstructor) { original[63] = bit::bit1; // Create a bit_pointer to original's storage - bit::bit_pointer bit_ptr(original.data()); + bit::bit_pointer bit_ptr(original.data()); // Create a bit_array_ref using the bit_pointer bit::bit_array_ref<> ref(bit_ptr, original.size()); @@ -195,7 +195,7 @@ TEST(BitArrayRef, OffsetBitPointerConstructor) { original[13] = bit::bit1; // Create an offset bit_pointer (starting from the 3rd bit) - bit::bit_pointer bit_ptr(original.data(), 3); + bit::bit_pointer bit_ptr(original.data(), 3); // Create a bit_array_ref using the offset bit_pointer bit::bit_array_ref<> ref(bit_ptr, 32); diff --git a/test/src/test-copy.cpp b/test/src/test-copy.cpp index 225a0b4b..f1b3187b 100644 --- a/test/src/test-copy.cpp +++ b/test/src/test-copy.cpp @@ -1,11 +1,10 @@ // ============================= COPY TESTS =============================== // // Project: The Experimental Bit Algorithms Library -// Description: Tests for copy algorithms +// Description: Tests for copy algorithms // Contributor(s): Bryce Kille // License: BSD 3-Clause License // ========================================================================== // - // ============================== PREAMBLE ================================== // // C++ standard library #include @@ -28,44 +27,44 @@ TYPED_TEST(DoubleRangeTest, Copy) { std::vector& boolvec1 = this->random_boolvecs1[idx]; std::vector& boolvec2 = this->random_boolvecs2[idx]; long long start1 = generate_random_number( - 0, - std::min(bitvec1.size() - 1, digits + 1)); + 0, + std::min(bitvec1.size() - 1, digits + 1)); long long start2 = generate_random_number( - 0, - std::min(bitvec2.size() - 1, digits + 1)); + 0, + std::min(bitvec2.size() - 1, digits + 1)); const auto min_range = (start2 > start1) ? start2 - start1 : 0; const auto max_range = std::max( - min_range, - std::min(digits, bitvec1.size() - start1)); + min_range, + std::min(digits, bitvec1.size() - start1)); long long end1 = generate_random_number(min_range, max_range); auto bitret = bit::copy( - bitvec1.begin() + start1, - bitvec1.end() - end1, - bitvec2.begin() + start2); + bitvec1.begin() + start1, + bitvec1.end() - end1, + bitvec2.begin() + start2); auto boolret = std::copy( - boolvec1.begin() + start1, - boolvec1.end() - end1, - boolvec2.begin() + start2); + boolvec1.begin() + start1, + boolvec1.end() - end1, + boolvec2.begin() + start2); EXPECT_EQ( - bit::distance(bitvec2.begin(), bitret), - std::distance(boolvec2.begin(), boolret)); + bit::distance(bitvec2.begin(), bitret), + std::distance(boolvec2.begin(), boolret)); EXPECT_TRUE(std::equal( bitvec2.begin(), bitvec2.end(), boolvec2.begin(), boolvec2.end(), comparator) ); start2 = generate_random_number(0, start1); bitret = bit::copy( - bitvec1.begin() + start1, - bitvec1.end() - end1, - bitvec1.begin() + start2); + bitvec1.begin() + start1, + bitvec1.end() - end1, + bitvec1.begin() + start2); boolret = std::copy( - boolvec1.begin() + start1, - boolvec1.end() - end1, - boolvec1.begin() + start2); + boolvec1.begin() + start1, + boolvec1.end() - end1, + boolvec1.begin() + start2); EXPECT_EQ( - bit::distance(bitvec1.begin(), bitret), - std::distance(boolvec1.begin(), boolret)); + bit::distance(bitvec1.begin(), bitret), + std::distance(boolvec1.begin(), boolret)); EXPECT_TRUE(std::equal( bitvec1.begin(), bitvec1.end(), boolvec1.begin(), boolvec1.end(), comparator) @@ -73,4 +72,55 @@ TYPED_TEST(DoubleRangeTest, Copy) { } } +TYPED_TEST(MixedDoubleRangeTest, Copy) { + for (size_t idx = 0; idx < this->random_bitvecs1.size(); ++idx) { + using FromWordType = typename TestFixture::FromWordType; + using ToWordType = typename TestFixture::ToWordType; + bit::bit_vector& bitvec1 = this->random_bitvecs1[idx]; + bit::bit_vector& bitvec2 = this->random_bitvecs2[idx]; + constexpr auto min_digits = std::min(bit::bitsof(), bit::bitsof()); + std::vector& boolvec1 = this->random_boolvecs1[idx]; + std::vector& boolvec2 = this->random_boolvecs2[idx]; + long long start1 = generate_random_number( + 0, + std::min(bitvec1.size() - 1, min_digits + 1)); + long long start2 = generate_random_number( + 0, + std::min(bitvec2.size() - 1, min_digits + 1)); + const auto min_range = (start2 > start1) ? start2 - start1 : 0; + const auto max_range = std::max( + min_range, + std::min(min_digits, bitvec1.size() - start1)); + long long end1 = generate_random_number(min_range, max_range); + auto bitret2 = bit::copy( + bitvec1.begin() + start1, + bitvec1.end() - end1, + bitvec2.begin() + start2); + auto boolret = std::copy( + boolvec1.begin() + start1, + boolvec1.end() - end1, + boolvec2.begin() + start2); + EXPECT_EQ( + bit::distance(bitvec2.begin(), bitret2), + std::distance(boolvec2.begin(), boolret)); + EXPECT_TRUE(std::equal( + bitvec2.begin(), bitvec2.end(), + boolvec2.begin(), boolvec2.end(), comparator)); + start2 = generate_random_number(0, start1); + auto bitret1 = bit::copy( + bitvec1.begin() + start1, + bitvec1.end() - end1, + bitvec1.begin() + start2); + boolret = std::copy( + boolvec1.begin() + start1, + boolvec1.end() - end1, + boolvec1.begin() + start2); + EXPECT_EQ( + bit::distance(bitvec1.begin(), bitret1), + std::distance(boolvec1.begin(), boolret)); + EXPECT_TRUE(std::equal( + bitvec1.begin(), bitvec1.end(), + boolvec1.begin(), boolvec1.end(), comparator)); + } +} diff --git a/test/src/test-equal.cpp b/test/src/test-equal.cpp index f8b09383..2a842aa5 100644 --- a/test/src/test-equal.cpp +++ b/test/src/test-equal.cpp @@ -1,11 +1,10 @@ // ============================= EQUAL TESTS =============================== // // Project: The Experimental Bit Algorithms Library -// Description: Tests for equal algorithms +// Description: Tests for equal algorithms // Contributor(s): Bryce Kille // License: BSD 3-Clause License // ========================================================================== // - // ============================== PREAMBLE ================================== // // C++ standard library #include @@ -28,46 +27,46 @@ TYPED_TEST(DoubleRangeTest, Equal) { std::vector& boolvec1 = this->random_boolvecs1[idx]; std::vector& boolvec2 = this->random_boolvecs2[idx]; long long start1 = generate_random_number( - 0, - std::min(bitvec1.size() - 1, digits + 1)); + 0, + std::min(bitvec1.size() - 1, digits + 1)); long long start2 = generate_random_number( - 0, - std::min(bitvec2.size() - 1, digits + 1)); + 0, + std::min(bitvec2.size() - 1, digits + 1)); const auto min_range = (start2 > start1) ? start2 - start1 : 0; const auto max_range = std::max( - min_range, - std::min(digits, bitvec1.size() - start1)); + min_range, + std::min(digits, bitvec1.size() - start1)); long long end1 = generate_random_number(min_range, max_range); auto bitret = bit::equal( - bitvec1.begin() + start1, - bitvec1.end() - end1, - bitvec2.begin() + start2); + bitvec1.begin() + start1, + bitvec1.end() - end1, + bitvec2.begin() + start2); auto boolret = std::equal( - boolvec1.begin() + start1, - boolvec1.end() - end1, - boolvec2.begin() + start2); + boolvec1.begin() + start1, + boolvec1.end() - end1, + boolvec2.begin() + start2); EXPECT_EQ(boolret, bitret); EXPECT_TRUE(std::equal( bitvec2.begin(), bitvec2.end(), boolvec2.begin(), boolvec2.end(), comparator) ); bit::copy( - bitvec1.begin() + start1, - bitvec1.end() - end1, - bitvec2.begin() + start2); + bitvec1.begin() + start1, + bitvec1.end() - end1, + bitvec2.begin() + start2); std::copy( - boolvec1.begin() + start1, - boolvec1.end() - end1, - boolvec2.begin() + start2); + boolvec1.begin() + start1, + boolvec1.end() - end1, + boolvec2.begin() + start2); bitret = bit::equal( - bitvec1.begin() + start1, - bitvec1.end() - end1, - bitvec2.begin() + start2); + bitvec1.begin() + start1, + bitvec1.end() - end1, + bitvec2.begin() + start2); boolret = std::equal( - boolvec1.begin() + start1, - boolvec1.end() - end1, - boolvec2.begin() + start2); + boolvec1.begin() + start1, + boolvec1.end() - end1, + boolvec2.begin() + start2); EXPECT_EQ(boolret, bitret); EXPECT_TRUE(std::equal( bitvec1.begin(), bitvec1.end(), @@ -76,4 +75,64 @@ TYPED_TEST(DoubleRangeTest, Equal) { } } +TYPED_TEST(MixedDoubleRangeTest, Equal) { + for (size_t idx = 0; idx < this->random_bitvecs1.size(); ++idx) { + using FromWordType = typename TestFixture::FromWordType; + using ToWordType = typename TestFixture::ToWordType; + bit::bit_vector& bitvec1 = this->random_bitvecs1[idx]; + bit::bit_vector& bitvec2 = this->random_bitvecs2[idx]; + constexpr auto min_digits = std::min(bit::bitsof(), bit::bitsof()); + std::vector& boolvec1 = this->random_boolvecs1[idx]; + std::vector& boolvec2 = this->random_boolvecs2[idx]; + long long start1 = generate_random_number( + 0, + std::min(bitvec1.size() - 1, min_digits + 1)); + long long start2 = generate_random_number( + 0, + std::min(bitvec2.size() - 1, min_digits + 1)); + const auto min_range = (start2 > start1) ? start2 - start1 : 0; + const auto max_range = std::max( + min_range, + std::min(min_digits, bitvec1.size() - start1)); + long long end1 = generate_random_number(min_range, max_range); + auto bitret = bit::equal( + bitvec1.begin() + start1, + bitvec1.end() - end1, + bitvec2.begin() + start2); + auto boolret = std::equal( + boolvec1.begin() + start1, + boolvec1.end() - end1, + boolvec2.begin() + start2); + EXPECT_EQ(boolret, bitret); + EXPECT_TRUE(std::equal( + bitvec1.begin(), bitvec1.end(), + boolvec1.begin(), boolvec1.end(), comparator)); + EXPECT_TRUE(std::equal( + bitvec2.begin(), bitvec2.end(), + boolvec2.begin(), boolvec2.end(), comparator)); + bit::copy( + bitvec1.begin() + start1, + bitvec1.end() - end1, + bitvec2.begin() + start2); + std::copy( + boolvec1.begin() + start1, + boolvec1.end() - end1, + boolvec2.begin() + start2); + EXPECT_TRUE(std::equal( + bitvec2.begin(), bitvec2.end(), + boolvec2.begin(), boolvec2.end(), comparator)); + bitret = bit::equal( + bitvec1.begin() + start1, + bitvec1.end() - end1, + bitvec2.begin() + start2); + boolret = std::equal( + boolvec1.begin() + start1, + boolvec1.end() - end1, + boolvec2.begin() + start2); + EXPECT_EQ(boolret, bitret); + EXPECT_TRUE(std::equal( + bitvec1.begin(), bitvec1.end(), + boolvec1.begin(), boolvec1.end(), comparator)); + } +} diff --git a/test/src/test-iterator_adapter.cpp b/test/src/test-iterator_adapter.cpp new file mode 100644 index 00000000..f8be23d5 --- /dev/null +++ b/test/src/test-iterator_adapter.cpp @@ -0,0 +1,333 @@ +// ============================= TRANSFORM TESTS =============================== // +// Project: The Experimental Bit Algorithms Library +// Description: Tests for transform algorithms +// Contributor(s): Bryce Kille +// License: BSD 3-Clause License +// ========================================================================== // + + +// ============================== PREAMBLE ================================== // +// C++ standard library +#include +#include +#include +// Project sources +#include "bitlib/bit-algorithms/bit_algorithm.hpp" +#include "bitlib/bit-containers/bit-containers.hpp" +#include "fixtures.hpp" +// Third-party libraries +#include "gtest/gtest.h" +// Miscellaneous +// ========================================================================== // +#include "bitlib/bit-iterator/bit_word_pointer_adapter.hpp" + +// +// GoogleTest suite for bit::bit_word_pointer_adapter. +// Covers both ā€œbig‐to‐smallā€ and ā€œsmall‐to‐largeā€ modes. +// + +// Assumes a little‐endian platform (so that reinterpret_cast<…> of bytes +// within larger words behaves predictably). If you compile on a big‐endian +// machine, these tests will fail. +// +// Verify that we are on a little‐endian machine: +static_assert(std::endian::native == std::endian::little, + "These tests assume little‐endian byte order"); + +TEST(IteratorAdapter, Basic) { + std::vector base{0x1234, 0x2345, 0x3456, 0x4567}; + bit::bit_word_pointer_adapter::iterator> adapter(base.begin()); + EXPECT_EQ(*adapter, 0x34); + EXPECT_EQ(*(++adapter), 0x12); + EXPECT_EQ(*(adapter++), 0x12); + EXPECT_EQ(*adapter, 0x45); + adapter += 2; + EXPECT_EQ(*adapter, 0x56); +} + +// ----------------------------------------------------------------------------- +// Helper: Convert a 32‐bit word into its 4 individual bytes (little‐endian) +// ----------------------------------------------------------------------------- +static void split_u32_le(uint32_t value, uint8_t out_bytes[4]) { + out_bytes[0] = static_cast(value & 0xFFu); + out_bytes[1] = static_cast((value >> 8) & 0xFFu); + out_bytes[2] = static_cast((value >> 16) & 0xFFu); + out_bytes[3] = static_cast((value >> 24) & 0xFFu); +} + +// ----------------------------------------------------------------------------- +// TEST SUITE: Big-to-Small Mode (BaseIterator.value_type > Iterator.value_type) +// ----------------------------------------------------------------------------- +TEST(BitIteratorAdapter_BigToSmall, DereferenceAndIncrement) { + // Base‐words are 32-bit => 4 Ɨ 8-bit subwords + constexpr size_t N = 2; + uint32_t base_arr[N] = {0x04030201u, 0x08070605u}; + // Bytes in memory (little‐endian): + // base_arr[0]: {0x01,0x02,0x03,0x04} + // base_arr[1]: {0x05,0x06,0x07,0x08} + + // Iterator type: iterate in 8-bit steps over a 32-bit array + using Iter8 = uint8_t*; + using Base32 = uint32_t*; + using Adapter8_32 = bit::bit_word_pointer_adapter; + + Adapter8_32 it(base_arr, /*pos=*/0); + + // Check that *it sees the first byte = 0x01 + EXPECT_EQ(*it, 0x01u); + + // Advance 1: pos=1, still in base_arr[0] + ++it; + EXPECT_EQ(it.index(), 1u); + EXPECT_EQ(*it, 0x02u); + + // Advance to pos=3 + it += 2; + EXPECT_EQ(it.index(), 3u); + EXPECT_EQ(*it, 0x04u); + + // Next increment should roll over to base_arr[1], pos=0 + ++it; + EXPECT_EQ(it.base(), base_arr + 1); + EXPECT_EQ(it.index(), 0u); + EXPECT_EQ(*it, 0x05u); + + // Further increments + ++it; // pos=1 + EXPECT_EQ(it.index(), 1u); + EXPECT_EQ(*it, 0x06u); + + ++it; // pos=2 + EXPECT_EQ(it.index(), 2u); + EXPECT_EQ(*it, 0x07u); + + ++it; // pos=3 + EXPECT_EQ(it.index(), 3u); + EXPECT_EQ(*it, 0x08u); +} + +TEST(BitIteratorAdapter_BigToSmall, OperatorStarAndArrow) { + // Use a single base word = 0xAABBCCDD + uint32_t base_val = 0xAABBCCDDu; + // Memory bytes: { DD, CC, BB, AA } little-endian + using Iter8 = uint8_t*; + using Base32 = uint32_t*; + using Adapter8_32 = bit::bit_word_pointer_adapter; + + Adapter8_32 it(&base_val, /*pos=*/0); + + EXPECT_EQ(*it, 0xDDu); + // operator->() should return a pointer to the byte + auto p = *it; + EXPECT_EQ(p, 0xDDu); + + // Move to pos=2 + it += 2; + EXPECT_EQ(*it, 0xBBu); + EXPECT_EQ(it.index(), 2u); + + // operator->() at pos=2 + p = *it; + EXPECT_EQ(p, 0xBBu); +} + +TEST(BitIteratorAdapter_BigToSmall, OperatorPlusMinusAndDistance) { + // Fill two 32-bit words + uint32_t base_arr[2] = {0x11223344u, 0x55667788u}; + // Bytes: {44,33,22,11, 88,77,66,55} + + using Iter8 = uint8_t*; + using Base32 = uint32_t*; + using Adapter8_32 = bit::bit_word_pointer_adapter; + + Adapter8_32 it(base_arr, /*pos=*/0); // points at first byte (0x44) + + // it + 3 => should point to 4th byte of base_arr[0] = 0x11 + auto it3 = it + 3; + EXPECT_EQ(it3.base(), base_arr); + EXPECT_EQ(it3.index(), 3u); + EXPECT_EQ(*it3, 0x11u); + + // it + 4 => moves into base_arr[1], pos=0 => should be 0x88 + auto it4 = it + 4; + EXPECT_EQ(it4.base(), base_arr + 1); + EXPECT_EQ(it4.index(), 0u); + EXPECT_EQ(*it4, 0x88u); + + // Now distance between it and it+7 = 7 + auto it7 = it + 7; + EXPECT_EQ(it7.base(), base_arr + 1); + EXPECT_EQ(it7.index(), 3u); + EXPECT_EQ(*it7, 0x55u); + + EXPECT_EQ(it7 - it, 7); + EXPECT_EQ(it - it, 0); + + // Test operator- (iterator difference) with reversed operands + EXPECT_EQ((it - it7), -7); + + // Advance it4 by -2 => (it4 - 2) = it + 2 => base_arr[0], pos=2 => 0x22 + auto it2 = it4 - 2; + EXPECT_EQ(it2.base(), base_arr); + EXPECT_EQ(it2.index(), 2u); + EXPECT_EQ(*it2, 0x22u); +} + +TEST(BitIteratorAdapter_BigToSmall, OperatorSubscript) { + // 1 uint32_t: 0xDEADBEEFu => bytes { EF, BE, AD, DE } + uint32_t base_val = 0xDEADBEEFu; + using Iter8 = uint8_t*; + using Base32 = uint32_t*; + using Adapter8_32 = bit::bit_word_pointer_adapter; + + Adapter8_32 it(&base_val, /*pos=*/0); + // it[0] == 0xEF, it[1] == 0xBE, it[2] == 0xAD, it[3] == 0xDE + EXPECT_EQ(it[0], static_cast(0xEFu)); + EXPECT_EQ(it[1], static_cast(0xBEu)); + EXPECT_EQ(it[2], static_cast(0xADu)); + EXPECT_EQ(it[3], static_cast(0xDEu)); + + // Negative index: it[ -1 ] => moves to previous base word; + // but since there's no previous word, behavior is UB. We skip testing negative here. + + // If we do it + 4, then subscript( it + 4 )[0] should be same as it[4] + auto it_shift = it + 4; // rolls into next base (out-of-bounds, but for test allocate two words) + // For safety, create two‐element array: + uint32_t arr2[2] = {0x01020304u, 0x05060708u}; + Adapter8_32 it2(arr2, 0); + EXPECT_EQ(it2[0], 0x04u); + EXPECT_EQ(it2[1], 0x03u); + EXPECT_EQ(it2[2], 0x02u); + EXPECT_EQ(it2[3], 0x01u); + EXPECT_EQ(it2[4], 0x08u); + EXPECT_EQ(it2[5], 0x07u); + EXPECT_EQ(it2[6], 0x06u); + EXPECT_EQ(it2[7], 0x05u); +} + +// ----------------------------------------------------------------------------- +// TEST SUITE: Small-to-Large Mode (BaseIterator.value_type < Iterator.value_type) +// ----------------------------------------------------------------------------- +TEST(BitIteratorAdapter_SmallToLarge, DereferenceAndIncrement) { + // Base: array of uint8_t; Iterator’s value_type = uint16_t => ratio = 2 + uint8_t base_arr[] = { + 0x11, 0x22, // first 16-bit chunk => 0x2211 + 0x33, 0x44, // second 16-bit chunk => 0x4433 + 0x55, 0x66 // third 16-bit chunk => 0x6655 + }; + using Iter16 = uint16_t*; + using Base8 = uint8_t*; + using Adapter16_8 = bit::bit_word_pointer_adapter; + + Adapter16_8 it(base_arr); + + // *it: combine base[0] + (base[1] << 8) => 0x2211 + uint16_t first = *it; + EXPECT_EQ(first, static_cast(0x2211u)); + + // ++it => skip 2 bytes: now base points to base_arr+2 + ++it; + uint16_t second = *it; + EXPECT_EQ(second, static_cast(0x4433u)); + + // ++it again + ++it; + uint16_t third = *it; + EXPECT_EQ(third, static_cast(0x6655u)); +} + +TEST(BitIteratorAdapter_SmallToLarge, OperatorPlusMinusDistance) { + // Base: eight uint8_t => four uint16_t logical elements + uint8_t base_arr[8] = { + 0x10, 0x01, // 0x0110 + 0x20, 0x02, // 0x0220 + 0x30, 0x03, // 0x0330 + 0x40, 0x04 // 0x0440 + }; + using Iter16 = uint16_t*; + using Base8 = uint8_t*; + using Adapter16_8 = bit::bit_word_pointer_adapter; + + Adapter16_8 it(base_arr); + + // it + 0 => *it == 0x0110 + EXPECT_EQ(*it, static_cast(0x0110u)); + + // it + 1 => combine bytes at indices [2,3] => 0x0220 + auto it1 = it + 1; + EXPECT_EQ(*it1, static_cast(0x0220u)); + + // it + 2 => 0x0330 + auto it2 = it + 2; + EXPECT_EQ(*it2, static_cast(0x0330u)); + + // it + 3 => 0x0440 + auto it3 = it + 3; + EXPECT_EQ(*it3, static_cast(0x0440u)); + + // Distance: it3 - it == 3 + EXPECT_EQ(it3 - it, 3); + EXPECT_EQ(it - it, 0); + + // it2 - it1 == 1 + EXPECT_EQ(it2 - it1, 1); + + // Subtraction reversed: + EXPECT_EQ(it - it3, -3); +} + +TEST(BitIteratorAdapter_SmallToLarge, OperatorStarAndArrow) { + uint8_t base_arr[4] = {0xAA, 0xBB, 0xCC, 0xDD}; + using Iter16 = uint16_t*; + using Base8 = uint8_t*; + using Adapter16_8 = bit::bit_word_pointer_adapter; + + Adapter16_8 it(base_arr); + // *it => (0xBB << 8) | 0xAA = 0xBBAA + EXPECT_EQ(*it, static_cast(0xBBAAu)); +} + +TEST(BitIteratorAdapter_SmallToLarge, OperatorPlusEqualsAndMinusEquals) { + // Base: six uint8_t => three 16-bit words + uint8_t base_arr[6] = { + 0x01, 0xA0, // 0xA001 + 0x02, 0xB0, // 0xB002 + 0x03, 0xC0 // 0xC003 + }; + using Iter16 = uint16_t*; + using Base8 = uint8_t*; + using Adapter16_8 = bit::bit_word_pointer_adapter; + + Adapter16_8 it(base_arr); + + // it += 1 => now at second 16-bit chunk: 0xB002 + it += 1; + EXPECT_EQ(*it, static_cast(0xB002u)); + + // it += 1 => now at third: 0xC003 + it += 1; + EXPECT_EQ(*it, static_cast(0xC003u)); + + // it -= 2 => go back to first chunk: 0xA001 + it -= 2; + EXPECT_EQ(*it, static_cast(0xA001u)); +} + +TEST(BitIteratorAdapter_SmallToLarge, OperatorMinusBetweenDifferentIterators) { + // Base: eight uint8_t => four 16-bit words + uint8_t base_arr[8] = { + 0x01, 0x10, // 0x1001 + 0x02, 0x20, // 0x2002 + 0x03, 0x30, // 0x3003 + 0x04, 0x40 // 0x4004 + }; + using Iter16 = uint16_t*; + using Base8 = uint8_t*; + using Adapter16_8 = bit::bit_word_pointer_adapter; + + Adapter16_8 it0(base_arr); + Adapter16_8 it2(base_arr + 4); // same as (it0 + 2) + + EXPECT_EQ(it2 - it0, 2); + EXPECT_EQ(it0 - it2, -2); +} diff --git a/test/src/test-mdspan.cpp b/test/src/test-mdspan.cpp index 6e95ab7d..78729830 100644 --- a/test/src/test-mdspan.cpp +++ b/test/src/test-mdspan.cpp @@ -26,7 +26,7 @@ TEST(MdSpanTest, BitDefaultLayout) { bit::bit_value, std::dextents, std::layout_right, - bit::bit_default_accessor > + bit::bit_default_accessor > myspan(&dynarr[0], 5, 6, 7); for (size_t i = 0; i < myspan.extent(0); i++) { From e50fa034f695d88bcd41e539637d099f360bf12a Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Tue, 17 Jun 2025 02:58:27 +0000 Subject: [PATCH 69/85] Truncation policy (#17) * Add draft policies for integer conversion * Add allocator to Policy. Use policy to construct from integral * Add convenience conversion pathway for literal -> bit_value * Add algorithm accumulate and utility to_string * template keyword only necessary if actually passing template args * Better UX for accumulate policy * small tweaks to to_string * Add static_assert error to bit_literal. * Consolidate from_integral code * Fix accumulate, get_word doesn't mask --- include/bitlib/bit-algorithms/accumulate.hpp | 278 ++++++++++++++++++ .../bitlib/bit-algorithms/bit_algorithm.hpp | 3 +- .../bit-algorithms/bit_algorithm_details.hpp | 5 + include/bitlib/bit-algorithms/count.hpp | 21 +- include/bitlib/bit-algorithms/to_string.hpp | 73 +++++ include/bitlib/bit-containers/bit_array.hpp | 63 ++-- .../bitlib/bit-containers/bit_array_base.hpp | 76 +++-- .../bit_array_dynamic_extent.hpp | 147 ++++----- .../bitlib/bit-containers/bit_array_ref.hpp | 16 +- include/bitlib/bit-containers/bit_literal.hpp | 26 +- include/bitlib/bit-containers/bit_policy.hpp | 108 +++++++ include/bitlib/bit-containers/bit_span.hpp | 168 ++++++----- include/bitlib/bit-iterator/bit_details.hpp | 9 + test/CMakeLists.txt | 2 + test/src/test-count_leading.cpp | 18 ++ test/src/test-literal.cpp | 17 +- test/src/test-to_string.cpp | 28 ++ 17 files changed, 835 insertions(+), 223 deletions(-) create mode 100644 include/bitlib/bit-algorithms/accumulate.hpp create mode 100644 include/bitlib/bit-algorithms/to_string.hpp create mode 100644 include/bitlib/bit-containers/bit_policy.hpp create mode 100644 test/src/test-count_leading.cpp create mode 100644 test/src/test-to_string.cpp diff --git a/include/bitlib/bit-algorithms/accumulate.hpp b/include/bitlib/bit-algorithms/accumulate.hpp new file mode 100644 index 00000000..1e5e46cc --- /dev/null +++ b/include/bitlib/bit-algorithms/accumulate.hpp @@ -0,0 +1,278 @@ +// ================================= BIT_ARRAY_REF =================================== // +// Project: The Experimental Bit Algorithms Library +// \file accumulate.hpp +// Description: Implementation of accumulate +// Creator: Vincent Reverdy +// Contributor: Peter McLean [2025] +// License: BSD 3-Clause License +// ========================================================================== // + +#ifndef _BIT_ACCUMULATE_HPP_INCLUDED +#define _BIT_ACCUMULATE_HPP_INCLUDED + +#include "bitlib/bit-iterator/bit_iterator.hpp" +#include "bitlib/bit-iterator/bit_details.hpp" + +namespace bit { + +namespace policy { +struct AccumulateInitialSubword { + static constexpr bool initial_sub_word = true; +}; + +struct AccumulateNoInitialSubword { + static constexpr bool initial_sub_word = false; +}; +} // namespace policy + +template +constexpr auto accumulate( + bit_iterator first, + bit_iterator last, + T acc, + BinaryOperation binary_op, + BinaryOperationSubword binary_op_subword) { + using word_type = typename bit_iterator::word_type; + using size_type = typename bit_iterator::size_type; + constexpr size_type digits = bitsof(); + + + size_type total_bits_to_op = distance(first, last); + bool keep_going = true;; + if constexpr (initial_sub_word) { + size_type sub_digits; + if constexpr (forward) { + sub_digits = std::min(digits - first.position(), total_bits_to_op); + if (sub_digits != 0) { + acc = binary_op_subword(std::move(acc), get_masked_word(first, sub_digits), sub_digits); + advance(first, digits - first.position()); + } + } else { + sub_digits = std::min(last.position(), total_bits_to_op); + if (sub_digits != 0) { + advance(last, -sub_digits); + acc = binary_op_subword(std::move(acc), get_masked_word(last, sub_digits), sub_digits); + } + } + total_bits_to_op -= sub_digits; + } + + const size_type whole_words_to_op = total_bits_to_op / digits; + const size_type remaining_bits_to_op = total_bits_to_op % digits; + + for (size_t i = 0; i < whole_words_to_op; i ++) { + if constexpr (forward) { + acc = binary_op(std::move(acc), get_word(first)); + advance(first, digits); + } else { + advance(last, -digits); + acc = binary_op(std::move(acc), get_word(last)); + } + } + if (remaining_bits_to_op > 0) { + if constexpr (forward) { + acc = binary_op_subword(std::move(acc), get_masked_word(first, remaining_bits_to_op), remaining_bits_to_op); + } else { + advance(last, -remaining_bits_to_op); + acc = binary_op_subword(std::move(acc), get_masked_word(last, remaining_bits_to_op), remaining_bits_to_op); + } + } + + return acc; +} + +template +constexpr auto accumulate_while( + bit_iterator first, + bit_iterator last, + T acc, + BinaryOperation binary_op, + BinaryOperationSubword binary_op_subword) { + using word_type = typename bit_iterator::word_type; + using size_type = typename bit_iterator::size_type; + using difference_type = typename bit_iterator::difference_type; + constexpr size_type digits = bitsof(); + + size_type total_bits_to_op = distance(first, last); + size_type whole_words_to_op = total_bits_to_op / digits; + + bool keep_going = true;; + if constexpr (initial_sub_word) { + if (whole_words_to_op > 1) { + size_type sub_digits; + if constexpr (forward) { + sub_digits = std::min(digits - first.position(), total_bits_to_op); + if (sub_digits != 0) { + std::tie(keep_going, acc) = binary_op_subword(std::move(acc), get_masked_word(first, sub_digits), sub_digits); + advance(first, digits - first.position()); + } + } else { + sub_digits = std::min(last.position(), total_bits_to_op); + if (sub_digits != 0) { + advance(last, -sub_digits); + std::tie(keep_going, acc) = binary_op_subword(std::move(acc), get_masked_word(first, sub_digits), sub_digits); + } + } + total_bits_to_op -= sub_digits; + if (!keep_going) { + return acc; // Stop early if the operation indicates to stop + } + } + } + + whole_words_to_op = total_bits_to_op / digits; + const size_type remaining_bits_to_op = total_bits_to_op % digits; + + for (size_type i = 0; i < whole_words_to_op; i ++) { + if constexpr (forward) { + std::tie(keep_going, acc) = binary_op(std::move(acc), get_word(first)); + advance(first, digits); + } else { + advance(last, -digits); + std::tie(keep_going, acc) = binary_op(std::move(acc), get_word(last)); + } + if (!keep_going) { + return acc; // Stop early if the operation indicates to stop + } + } + if (remaining_bits_to_op > 0) { + if constexpr (forward) { + std::tie(std::ignore, acc) = binary_op_subword(std::move(acc), get_masked_word(first, remaining_bits_to_op), remaining_bits_to_op); + } else { + advance(last, -remaining_bits_to_op); + std::tie(std::ignore, acc) = binary_op_subword(std::move(acc), get_masked_word(last, remaining_bits_to_op), remaining_bits_to_op); + } + } + + return acc; +} + +// Requires BinaryOperation to have a third but defaulted argument +template +constexpr auto accumulate( + const bit_iterator& first, + const bit_iterator& last, + const T& acc, + BinaryOperation binary_op) { + return accumulate(first, last, acc, binary_op, binary_op); +} + +template +constexpr auto accumulate( + const bit_iterator& first, + const bit_iterator& last, + const T& acc, + BinaryOperation binary_op, + BinaryOperationSubword binary_op_subword) { + return accumulate(first, last, acc, binary_op, binary_op_subword); +} + +// Requires BinaryOperation to have a third but defaulted argument +template +constexpr auto accumulate_while( + const bit_iterator& first, + const bit_iterator& last, + const T& acc, + BinaryOperation binary_op) { + return accumulate_while(first, last, acc, binary_op, binary_op); +} + +template +constexpr auto accumulate_while( + const bit_iterator& first, + const bit_iterator& last, + const T& acc, + BinaryOperation binary_op, + BinaryOperationSubword binary_op_subword) { + return accumulate_while(first, last, acc, binary_op, binary_op_subword); +} + +// Requires BinaryOperation to have a third but defaulted argument +template +constexpr auto accumulate_backward_while( + const bit_iterator& first, + const bit_iterator& last, + const T& acc, + BinaryOperation binary_op) { + return accumulate_while(first, last, acc, binary_op, binary_op); +} + +template +constexpr auto accumulate_backward_while( + const bit_iterator& first, + const bit_iterator& last, + const T& acc, + BinaryOperation binary_op, + BinaryOperationSubword binary_op_subword) { + return accumulate_while(first, last, acc, binary_op, binary_op_subword); +} + +// Requires BinaryOperation to have a third but defaulted argument +template +constexpr auto accumulate( + Policy, + const bit_iterator& first, + const bit_iterator& last, + const T& acc, + BinaryOperation binary_op) { + return accumulate(first, last, acc, binary_op, binary_op); +} + +template +constexpr auto accumulate( + Policy, + const bit_iterator& first, + const bit_iterator& last, + const T& acc, + BinaryOperation binary_op, + BinaryOperationSubword binary_op_subword) { + return accumulate(first, last, acc, binary_op, binary_op_subword); +} + +// Requires BinaryOperation to have a third but defaulted argument +template +constexpr auto accumulate_while( + Policy, + const bit_iterator& first, + const bit_iterator& last, + const T& acc, + BinaryOperation binary_op) { + return accumulate_while(first, last, acc, binary_op, binary_op); +} + +template +constexpr auto accumulate_while( + Policy, + const bit_iterator& first, + const bit_iterator& last, + const T& acc, + BinaryOperation binary_op, + BinaryOperationSubword binary_op_subword) { + return accumulate_while(first, last, acc, binary_op, binary_op_subword); +} + +// Requires BinaryOperation to have a third but defaulted argument +template +constexpr auto accumulate_backward_while( + Policy, + const bit_iterator& first, + const bit_iterator& last, + const T& acc, + BinaryOperation binary_op) { + return accumulate_while(first, last, acc, binary_op, binary_op); +} + +template +constexpr auto accumulate_backward_while( + Policy, + const bit_iterator& first, + const bit_iterator& last, + const T& acc, + BinaryOperation binary_op, + BinaryOperationSubword binary_op_subword) { + return accumulate_while(first, last, acc, binary_op, binary_op_subword); +} + +} // namespace bit + +#endif // _BIT_ACCUMULATE_HPP_INCLUDED diff --git a/include/bitlib/bit-algorithms/bit_algorithm.hpp b/include/bitlib/bit-algorithms/bit_algorithm.hpp index 51cc6615..6266e640 100644 --- a/include/bitlib/bit-algorithms/bit_algorithm.hpp +++ b/include/bitlib/bit-algorithms/bit_algorithm.hpp @@ -21,8 +21,8 @@ #include "bit_algorithm_details.hpp" // overloads #include "bit_algorithm_details.hpp" -#include "copy_backward.hpp" #include "copy.hpp" +#include "copy_backward.hpp" #include "count.hpp" #include "debug_utils.hpp" #include "equal.hpp" @@ -33,6 +33,7 @@ #include "rotate.hpp" #include "shift.hpp" #include "swap_ranges.hpp" +#include "to_string.hpp" #include "transform.hpp" #include "type_traits.hpp" // ========================================================================== // diff --git a/include/bitlib/bit-algorithms/bit_algorithm_details.hpp b/include/bitlib/bit-algorithms/bit_algorithm_details.hpp index 3be853b6..a0466141 100644 --- a/include/bitlib/bit-algorithms/bit_algorithm_details.hpp +++ b/include/bitlib/bit-algorithms/bit_algorithm_details.hpp @@ -142,6 +142,11 @@ T get_word(const bit_iterator& first, size_t len = binary_digits::va return ret_word; } +template +T get_masked_word(const bit_iterator& first, size_t len = binary_digits::value) { + return get_word(first, len) & _mask(len); +} + // Get next len bits beginning at start and store them in a word of type T // If we reach `last` before we get len bits, break and return the current word // bits_read will store the number of bits that we read. diff --git a/include/bitlib/bit-algorithms/count.hpp b/include/bitlib/bit-algorithms/count.hpp index 485a7750..321edec8 100644 --- a/include/bitlib/bit-algorithms/count.hpp +++ b/include/bitlib/bit-algorithms/count.hpp @@ -12,8 +12,9 @@ #include #include // Project sources +#include "bitlib/bit-algorithms/accumulate.hpp" +#include "bitlib/bit-algorithms/libpopcnt.h" #include "bitlib/bit-iterator/bit.hpp" -#include "bitlib/bit-algorithms//libpopcnt.h" // Third-party libraries #ifdef BITLIB_HWY #include "hwy/highway.h" @@ -117,6 +118,24 @@ count( return result; } +template +constexpr int count_msb( + bit_iterator first, + bit_iterator last, + bit_value value) { + if (value) { + return bit::accumulate_backward_while( + first, last, 0, + [](int acc, auto word) { return std::make_pair((word == 0), acc + std::countl_one(word)); }, + [](int acc, auto word, auto bits) { return std::make_pair((word == 0), acc + std::countl_one(word) - (bit::bitsof(word) - bits)); }); + } else { + return bit::accumulate_backward_while( + first, last, 0, + [](int acc, auto word) { return std::make_pair((word == 0), acc + std::countl_zero(word)); }, + [](int acc, auto word, auto bits) { return std::make_pair((word == 0), acc + std::countl_zero(word) - (bit::bitsof(word) - bits)); }); + } +} + } // namespace bit #ifdef BITLIB_HWY HWY_AFTER_NAMESPACE(); diff --git a/include/bitlib/bit-algorithms/to_string.hpp b/include/bitlib/bit-algorithms/to_string.hpp new file mode 100644 index 00000000..cb31c8d7 --- /dev/null +++ b/include/bitlib/bit-algorithms/to_string.hpp @@ -0,0 +1,73 @@ +// ================================= BIT_ARRAY_REF =================================== // +// Project: The Experimental Bit Algorithms Library +// \file to_string.hpp +// Description: Implementation of bit_array_ref +// Creator: Vincent Reverdy +// Contributor: Peter McLean [2025] +// License: BSD 3-Clause License +// ========================================================================== // + +#ifndef _BIT_TO_STRING_HPP_INCLUDED +#define _BIT_TO_STRING_HPP_INCLUDED + +#include + +#include "bitlib/bit-algorithms/accumulate.hpp" +#include "bitlib/bit-algorithms/count.hpp" +#include "bitlib/bit_concepts.hpp" + +namespace bit { + +namespace detail { + +template +constexpr auto make_digit_map() { + static_assert((Base >= 2) && ((Base & (Base - 1)) == 0), "Base must be power of 2 >= 2"); + static_assert(Base <= 64, "Base too large for simple char mapping"); + + std::array map = {}; + for (std::size_t i = 0; i < Base; ++i) { + map[i] = (i < 10) ? ('0' + i) : ('A' + (i - 10)); + } + return map; +} + +} // namespace detail + +template +constexpr std::string to_string(const bit_iterator& first, const bit_iterator& last, std::string prefix = "") { + if constexpr (std::has_single_bit(base)) { + constexpr const auto base_bits = std::bit_width(base - 1); + + int skip_leading_bits = prepend_zeros ? 0 : count_msb(first, last, bit0); + + int str_len = (distance(first, last) - skip_leading_bits); + str_len += (0 != (str_len % base_bits)); + std::string& str = prefix; + str.resize(str.length() + str_len); + + static constexpr auto base_digits = detail::make_digit_map(); + + return accumulate( + policy::AccumulateNoInitialSubword{}, + first, last - skip_leading_bits, (str.data() + str_len), + [](char* acc, auto word, const size_t bits = bitsof()) { + const int characters = ((bits + base_bits - 1) / base_bits); + acc -= characters; + for (int i = characters - 1; i >= 0; i--) { + acc[i] = base_digits[word & (base - 1)]; + word >>= base_bits; + } + return acc; + }); + } +} + +template +constexpr std::string to_string(const bit_sized_range auto& bits, std::string prefix = "") { + return to_string(bits.begin(), bits.end(), prefix); +} + +} // namespace bit + +#endif // _BIT_TO_STRING_HPP_INCLUDED \ No newline at end of file diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index e531ca2f..a46a3c0b 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -15,7 +15,6 @@ #include #include #include -#include // memcpy #include #include #include @@ -25,6 +24,7 @@ #include "bitlib/bit-algorithms/bit_algorithm.hpp" #include "bitlib/bit-containers/bit_array_base.hpp" #include "bitlib/bit-containers/bit_bitsof.hpp" +#include "bitlib/bit-containers/bit_policy.hpp" #include "bitlib/bit-containers/bit_span.hpp" #include "bitlib/bit-iterator/bit.hpp" #include "bitlib/bit_concepts.hpp" @@ -38,22 +38,24 @@ template constexpr size_t Words() { return (N * bitsof() + bitsof() - 1) / bitsof(); } template -using bit_array_d_it = typename std::conditional, - bit_iterator()>::iterator>, - typename std::array()>::iterator>::type; - -template -using bit_array_d_cit = typename std::conditional, - bit_iterator()>::const_iterator>, - typename std::array()>::const_iterator>::type; +struct bit_array_iterator_types { + using iterator = typename std::conditional, + bit_iterator()>::iterator>, + typename std::array()>::iterator>::type; + + using const_iterator = typename std::conditional, + bit_iterator()>::const_iterator>, + typename std::array()>::const_iterator>::type; +}; } // namespace detail template >> -class bit_array : public bit_array_base, T, N, W, detail::bit_array_d_it, detail::bit_array_d_cit> { + typename W = std::conditional_t<(N == std::dynamic_extent), std::uintptr_t, ceil_integral>, + typename Policy = policy::typical> +class bit_array : public bit_array_base, T, N, W, Policy, detail::bit_array_iterator_types> { public: - using base = bit_array_base, T, N, W, detail::bit_array_d_it, detail::bit_array_d_cit>; + using base = bit_array_base, T, N, W, Policy, detail::bit_array_iterator_types>; using base::end; using typename base::const_iterator; using typename base::const_pointer; @@ -82,34 +84,13 @@ class bit_array : public bit_array_base, T, N, W, detail::bit */ constexpr bit_array() noexcept : storage{} {} - /*This constructor is purely to simplify some corners of the API*/ - constexpr bit_array(size_type size) noexcept : storage{} { - assert(size == N); - } - - constexpr bit_array(value_type bit_val) : storage{} { + constexpr bit_array(value_type bit_val) { this->fill(bit_val); } template - constexpr bit_array(const U& integral) - requires(bitsof() <= bits) - { - std::memcpy(&storage[0], &integral, sizeof(integral)); - - bool sign_extend = false; - if constexpr (std::is_signed_v) { - sign_extend = (integral & (1 << (bitsof() - 1))) ? true : false; - } - if (sign_extend) { - for (auto it = begin() + bitsof(); it != end(); ++it) { - *it = bit1; - } - } else { - for (auto it = begin() + bitsof(); it != end(); ++it) { - *it = bit0; - } - } + constexpr bit_array(const U& integral) { + this->from_integral(integral); } constexpr bit_array(const bit_array& other) noexcept @@ -169,7 +150,7 @@ class bit_array : public bit_array_base, T, N, W, detail::bit /* * Assignment */ - constexpr bit_array& operator=(const bit_array& other) = default; + constexpr bit_array& operator=(const bit_array& other) = default; constexpr bit_array& operator=(const bit_sized_range auto& other) { if (other.size() != this->size()) [[unlikely]] { @@ -179,7 +160,7 @@ class bit_array : public bit_array_base, T, N, W, detail::bit return *this; }; - constexpr bit_array& operator=(bit_array&& other) noexcept { + constexpr bit_array& operator=(bit_array&& other) noexcept { std::copy(other.storage.begin(), other.storage.end(), storage.begin()); return *this; } @@ -216,6 +197,12 @@ class bit_array : public bit_array_base, T, N, W, detail::bit constexpr void swap(bit_array& other) noexcept { std::swap(this->storage, other.storage); } + + constexpr operator bit_value() const noexcept + requires(std::is_same_v && N == 1) + { + return this->begin()[0]; + } }; static_assert(bit_range>, "bit_array does not satisfy bit_range concept!"); diff --git a/include/bitlib/bit-containers/bit_array_base.hpp b/include/bitlib/bit-containers/bit_array_base.hpp index 92414d4e..ed3f432b 100644 --- a/include/bitlib/bit-containers/bit_array_base.hpp +++ b/include/bitlib/bit-containers/bit_array_base.hpp @@ -15,7 +15,6 @@ #include #include #include -#include // memcpy #include #include #include @@ -24,19 +23,20 @@ // Project sources #include "bitlib/bit-algorithms/bit_algorithm.hpp" #include "bitlib/bit-containers/bit_bitsof.hpp" +#include "bitlib/bit-containers/bit_policy.hpp" #include "bitlib/bit-containers/bit_span.hpp" #include "bitlib/bit-iterator/bit.hpp" namespace bit { -template +template class bit_array_ref; template + typename W, + typename Policy> class bit_array; -// ========================================================================== // /** * @brief Base class template for bit_array implementations @@ -47,10 +47,11 @@ class bit_array; * @tparam Derived The derived class (CRTP pattern) * @tparam T The value type (typically bit_value) * @tparam W The word type used for storage - * @tparam It The iterator type for the derived class - * @tparam CIt The const_iterator type for the derived class + * @tparam N The size of the bit array, or std::dynamic_extent for dynamic size + * @tparam Policy The policy for integral conversion (default is typical) + * @tparam Iterators A struct that provides iterator and const_iterator types */ -template +template class bit_array_base { protected: constexpr Derived& derived() noexcept { @@ -70,8 +71,8 @@ class bit_array_base { using const_reference = typename std::conditional, const bit_reference, const T&>::type; using pointer = typename std::conditional, bit_pointer, T&>::type; using const_pointer = const pointer; - using iterator = It; - using const_iterator = CIt; + using iterator = Iterators::iterator; + using const_iterator = Iterators::const_iterator; // Element access constexpr reference operator[](size_type pos) { @@ -163,14 +164,14 @@ class bit_array_base { * @brief Slice operations - returns a bit_array_ref */ constexpr auto operator()(size_type offset, size_type right) const noexcept { - return bit_array_ref(&this->at(offset), right - offset); + return bit_array_ref(&this->at(offset), right - offset); } /** * @brief Slice operations - returns a bit_array_ref */ constexpr auto operator()(size_type offset, size_type right) noexcept { - return bit_array_ref(&this->at(offset), right - offset); + return bit_array_ref(&this->at(offset), right - offset); } // Common operations @@ -185,17 +186,31 @@ class bit_array_base { explicit constexpr operator U() const noexcept { assert(derived().size() <= bitsof()); U integral; - bit_span()> integral_ref(reinterpret_cast(&integral)); - copy(derived().begin(), derived().begin() + bitsof(), integral_ref.begin()); - if constexpr (std::is_signed_v) { - ::bit::fill(integral_ref.begin() + derived().size(), integral_ref.end(), integral_ref[bitsof() - 1]); + + if constexpr (N == std::dynamic_extent) { + if (derived().size() > bitsof()) { + Policy::truncation::template to_integral(derived(), integral); + } else { + ::bit::copy(derived().begin(), end(), bit_pointer(&integral)); + } + if (derived().size() < bitsof()) { + Policy::extension::template to_integral(derived(), integral); + } } else { - ::bit::fill(integral_ref.begin() + derived().size(), integral_ref.end(), bit0); + if constexpr (N > bitsof()) { + Policy::truncation::template to_integral(derived(), integral); + } else { + ::bit::copy(derived().begin(), end(), bit_pointer(&integral)); + } + if constexpr (N < bitsof()) { + Policy::extension::template to_integral(derived(), integral); + } } + return integral; } - using compatible_bitarray = bit_array; + using compatible_bitarray = bit_array; constexpr compatible_bitarray operator~() { compatible_bitarray result(derived().size()); @@ -242,7 +257,34 @@ class bit_array_base { [](const word_type& a, const word_type& b) -> word_type { return a ^ b; }); return derived(); } + + protected: + template + constexpr void from_integral(const U& integral) { + if constexpr (N == std::dynamic_extent) { + if ((derived().size() * bitsof()) < bitsof()) { + Policy::truncation::template from_integral(derived(), integral); + } else { + bit_pointer integral_ptr(&integral); + ::bit::copy(integral_ptr, integral_ptr + bitsof(), derived().begin()); + } + if (bitsof() < (derived().size() * bitsof())) { + Policy::extension::template from_integral(derived(), integral, detail::uninitialized); + } + } else { + if constexpr ((N * bitsof()) < bitsof()) { + Policy::truncation::template from_integral(derived(), integral); + } else { + bit_pointer integral_ptr(&integral); + ::bit::copy(integral_ptr, integral_ptr + bitsof(), derived().begin()); + } + if constexpr (bitsof() < (N * bitsof())) { + Policy::extension::template from_integral(derived(), integral, detail::uninitialized); + } + } + } }; + constexpr bool operator==(const bit_sized_range auto& lhs, const bit_sized_range auto& rhs) { if (lhs.size() != rhs.size()) { return false; diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index a446d831..53544db2 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -11,7 +11,6 @@ #include #include -#include // memcpy #include #include #include @@ -22,26 +21,29 @@ #include "bitlib/bit-containers/bit_array.hpp" #include "bitlib/bit-containers/bit_array_base.hpp" #include "bitlib/bit-containers/bit_bitsof.hpp" +#include "bitlib/bit-containers/bit_policy.hpp" #include "bitlib/bit-containers/bit_span.hpp" #include "bitlib/bit-iterator/bit.hpp" #include "bitlib/bit_concepts.hpp" namespace bit { namespace detail { + template -using bit_array_it = typename std::conditional, - bit_iterator, - word_type*>::type; -template -using bit_array_cit = typename std::conditional, - bit_iterator, - const word_type*>::type; +struct bit_array_dextent_iterator_types { + using iterator = typename std::conditional, + bit_iterator, + word_type*>::type; + using const_iterator = typename std::conditional, + bit_iterator, + const word_type*>::type; +}; } // namespace detail -template -class bit_array - : public bit_array_base, T, std::dynamic_extent, W, detail::bit_array_it, detail::bit_array_cit> { +template +class bit_array + : public bit_array_base, T, std::dynamic_extent, W, Policy, detail::bit_array_dextent_iterator_types> { public: - using base = bit_array_base, T, std::dynamic_extent, W, detail::bit_array_it, detail::bit_array_cit>; + using base = bit_array_base, T, std::dynamic_extent, W, Policy, detail::bit_array_dextent_iterator_types>; using base::end; using typename base::const_iterator; using typename base::const_pointer; @@ -53,21 +55,34 @@ class bit_array using typename base::size_type; using typename base::value_type; using typename base::word_type; + using Allocator = typename Policy::allocator; private: - using word_type_ptr = std::unique_ptr; + using word_type_ptr = word_type*; static const size_type FixedWords = sizeof(word_type_ptr) / sizeof(word_type); static const size_type FixedBits = FixedWords * bitsof(); const size_type m_size; - union Storage { - word_type_ptr pointer; - word_type fixed[FixedWords]; + struct Storage { + union { + word_type_ptr pointer; + word_type fixed[FixedWords]; + }; + Allocator m_allocator; + + Storage(size_type words, const Allocator& allocator, detail::uninitialized_t) : m_allocator(allocator) { + if (words > FixedWords) { + new (&pointer) word_type_ptr(m_allocator.allocate(words)); + } + } - Storage(size_type words) { + Storage(size_type words, const Allocator& allocator = Allocator()) : m_allocator(allocator) { if (words > FixedWords) { - new (&pointer) word_type_ptr(new word_type[words]); + new (&pointer) word_type_ptr(m_allocator.allocate(words)); + for (size_type i = 0; i < words; ++i) { + new (pointer + i) word_type(); + } } else { for (size_type i = 0; i < words; ++i) { new (&fixed[i]) word_type(); @@ -86,10 +101,14 @@ class bit_array } } - Storage(size_type words, const Storage& other) { + Storage(size_type words, const Storage& other, const Allocator& allocator = Allocator()) + : m_allocator(allocator) { if (words > FixedWords) { - new (&pointer) word_type_ptr(new word_type[words]); - std::memcpy(pointer.get(), other.pointer.get(), words * sizeof(word_type)); + new (&pointer) word_type_ptr(m_allocator.allocate(words)); + for (size_t i = 0; i < words; ++i) { + new (pointer + i) word_type(other.pointer[i]); + } + std::copy_n(other.pointer, words, pointer); } else { for (size_type i = 0; i < words; ++i) { new (&fixed[i]) word_type(other.fixed[i]); @@ -97,9 +116,13 @@ class bit_array } } template - Storage(size_type words, const U& val) { + Storage(size_type words, const U& val, const Allocator& allocator = Allocator()) + : m_allocator(allocator) { if (words > FixedWords) { - new (&pointer) word_type_ptr(new word_type[words](val)); + new (&pointer) word_type_ptr(m_allocator.allocate(words)); + for (size_t i = 0; i < words; ++i) { + new (pointer + i) word_type(val); + } } else { for (size_type i = 0; i < words; ++i) { new (&fixed[i]) word_type(val); @@ -107,21 +130,17 @@ class bit_array } } Storage() = delete; - ~Storage() {} + ~Storage() {}; } storage; static constexpr size_type Words(size_type N) { return (N * bitsof() + bitsof() - 1) / bitsof(); }; - /* - constexpr size_type Words() const { - return Words(m_size); - }*/ public: ~bit_array() { if (size() > FixedBits) { - storage.pointer.~unique_ptr(); + storage.m_allocator.deallocate(storage.pointer, Words(size())); } else { for (size_type i = 0; i < Words(size()); ++i) { storage.fixed[i].~word_type(); @@ -134,50 +153,36 @@ class bit_array */ bit_array() = delete; - constexpr bit_array(const size_type size) - : m_size(size), storage(Words(size)) { + constexpr bit_array(const size_type size, const Allocator& allocator = Allocator()) + : m_size(size), storage(Words(size), allocator) { } template - constexpr bit_array(const size_type size, const U& integral) - : bit_array(size) { - assert(bitsof() <= size); - if (size() > FixedBits) { - std::memcpy(storage.pointer, &integral, sizeof(integral)); - } else { - std::memcpy(storage.fixed, &integral, sizeof(integral)); - } - bool sign_extend = false; - if constexpr (std::is_signed_v) { - sign_extend = (integral & (1 << (bitsof() - 1))) ? true : false; - } - if (sign_extend) { - for (auto it = begin() + bitsof(); it != end(); ++it) { - *it = bit1; - } - } else { - for (auto it = begin() + bitsof(); it != end(); ++it) { - *it = bit0; - } - } + constexpr bit_array(const size_type size, const U& integral, const Allocator& allocator = Allocator()) + : m_size(size), storage(Words(size), allocator, detail::uninitialized) { + this->from_integral(integral); } - constexpr bit_array(const size_type size, const word_type val) - : m_size(size), storage(Words(size), val) { + constexpr bit_array(const size_type size, const word_type val, const Allocator& allocator = Allocator()) + : m_size(size), storage(Words(size), val, allocator) { } - constexpr bit_array(const size_type size, const value_type bit_val) + constexpr bit_array(const size_type size, const value_type bit_val, const Allocator& allocator = Allocator()) requires(!std::is_same::value) - : m_size(size), storage(Words(size)) { + : m_size(size), storage(Words(size), allocator, detail::uninitialized) { this->fill(bit_val); } - constexpr bit_array(const bit_array& other) + constexpr bit_array(const bit_array& other) : m_size(other.size()), storage(Words(size()), other.storage) { } - constexpr bit_array(const bit_sized_range auto& other) - : bit_array(other.size()) { + constexpr bit_array(const bit_array& other, const Allocator& allocator) + : m_size(other.size()), storage(Words(size()), other.storage, allocator) { + } + + constexpr bit_array(const bit_sized_range auto& other, const Allocator& allocator = Allocator()) + : bit_array(other.size(), allocator, detail::uninitialized) { ::bit::copy(other.begin(), other.end(), this->begin()); } @@ -185,9 +190,13 @@ class bit_array : m_size(other.size()), storage(Words(size()), std::move(other.storage)) { } - constexpr bit_array(const std::initializer_list init) + constexpr bit_array(bit_array&& other, const Allocator& allocator) + : m_size(other.size()), storage(Words(size()), std::move(other.storage), allocator) { + } + + constexpr bit_array(const std::initializer_list init, const Allocator& allocator = Allocator()) requires(!std::is_same_v) - : bit_array(init.size()) { + : m_size(init.size()), storage(Words(size()), allocator, detail::uninitialized) { std::copy(init.begin(), init.end(), this->begin()); } @@ -202,14 +211,14 @@ class bit_array #endif template - constexpr bit_array(const std::initializer_list init) - : bit_array(bitsof() * init.size()) { + constexpr bit_array(const std::initializer_list init, const Allocator& allocator = Allocator()) + : m_size(bitsof() * init.size()), storage(Words(size()), allocator, detail::uninitialized) { std::copy(init.begin(), init.end(), data()); } - constexpr bit_array(const std::string_view s) + constexpr bit_array(const std::string_view s, const Allocator& allocator = Allocator()) requires(std::is_same_v) - : bit_array((std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))) { + : m_size(std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1')), storage(Words(size()), allocator, detail::uninitialized) { size_type i = 0; for (char c : s) { if (c == '0') { @@ -256,7 +265,7 @@ class bit_array */ constexpr word_type* data() noexcept { if (size() > FixedBits) { - return storage.pointer.get(); + return storage.pointer; } else { return storage.fixed; } @@ -264,7 +273,7 @@ class bit_array constexpr const word_type* data() const noexcept { if (size() > FixedBits) { - return storage.pointer.get(); + return storage.pointer; } else { return storage.fixed; } @@ -275,7 +284,7 @@ class bit_array */ constexpr iterator begin() noexcept { if (size() > FixedBits) { - return iterator(storage.pointer.get()); + return iterator(storage.pointer); } else { return iterator(storage.fixed); } @@ -283,7 +292,7 @@ class bit_array constexpr const_iterator begin() const noexcept { if (size() > FixedBits) { - return const_iterator(storage.pointer.get()); + return const_iterator(storage.pointer); } else { return const_iterator(storage.fixed); } diff --git a/include/bitlib/bit-containers/bit_array_ref.hpp b/include/bitlib/bit-containers/bit_array_ref.hpp index 038c9bc9..c19a6e3c 100644 --- a/include/bitlib/bit-containers/bit_array_ref.hpp +++ b/include/bitlib/bit-containers/bit_array_ref.hpp @@ -11,22 +11,28 @@ #include #include -#include // memcpy #include #include -#include #include // std::dynamic_extent #include #include "bitlib/bit-algorithms/bit_algorithm.hpp" #include "bitlib/bit-containers/bit_array_base.hpp" #include "bitlib/bit-containers/bit_bitsof.hpp" +#include "bitlib/bit-containers/bit_policy.hpp" #include "bitlib/bit-containers/bit_span.hpp" #include "bitlib/bit-iterator/bit.hpp" #include "bitlib/bit_concepts.hpp" namespace bit { +namespace detail { +template +struct bit_array_ref_iterator_types { + using iterator = bit_iterator; + using const_iterator = bit_iterator; +}; +} // namespace detail /** * @brief A non-owning reference to a bit array * @@ -37,11 +43,11 @@ namespace bit { * @tparam T The value type (typically bit_value) * @tparam W The word type used for storage */ -template +template > class bit_array_ref - : public bit_array_base, T, std::dynamic_extent, W, bit_iterator, bit_iterator> { + : public bit_array_base, T, std::dynamic_extent, W, Policy, detail::bit_array_ref_iterator_types> { public: - using base = bit_array_base, T, std::dynamic_extent, W, bit_iterator, bit_iterator>; + using base = bit_array_base, T, std::dynamic_extent, W, Policy, detail::bit_array_ref_iterator_types>; using base::end; using typename base::const_iterator; using typename base::const_pointer; diff --git a/include/bitlib/bit-containers/bit_literal.hpp b/include/bitlib/bit-containers/bit_literal.hpp index 44a0cccb..cb809f68 100644 --- a/include/bitlib/bit-containers/bit_literal.hpp +++ b/include/bitlib/bit-containers/bit_literal.hpp @@ -15,9 +15,16 @@ // Project sources #include "bitlib/bit-containers/bit_array.hpp" +#include "bitlib/bit-containers/bit_policy.hpp" +#include "bitlib/bit-iterator/bit_details.hpp" namespace bit { +template +class bit_array; + +class bit_value; + // ========================================================================== // template constexpr void _parameter_pack_base_bits_prefix_len(size_t& base, size_t& bits, size_t& prefix_len, uint64_t& num) { @@ -103,14 +110,15 @@ constexpr std::pair _parameter_pack_decode_prefixed_num() { } // namespace bit template -constexpr bit::bit_array().first> operator""_b() { - bit::bit_array().first> arr{}; - auto [bits, num] = bit::_parameter_pack_decode_prefixed_num(); - for (int i = 0; i < arr.size(); i++) { - arr[i] = (num & 0x1) ? bit::bit1 : bit::bit0; - num >>= 1; - } - return arr; +constexpr auto operator""_b() { + constexpr auto pair = bit::_parameter_pack_decode_prefixed_num(); + constexpr auto bits = pair.first; + constexpr auto num = pair.second; + static_assert( + (((1ull << bits) - 1ull) >= num), + "bit literal size prefix has too few bits to represent the given value"); + using word_type = bit::ceil_integral; + return bit::bit_array>(static_cast(num)); } -#endif +#endif // _BIT_LITERAL_HPP_INCLUDED diff --git a/include/bitlib/bit-containers/bit_policy.hpp b/include/bitlib/bit-containers/bit_policy.hpp new file mode 100644 index 00000000..03943f0e --- /dev/null +++ b/include/bitlib/bit-containers/bit_policy.hpp @@ -0,0 +1,108 @@ +// ================================= BIT_ARRAY_REF =================================== // +// Project: The Experimental Bit Algorithms Library +// \file bit_array_ref.hpp +// Description: Implementation of bit_array_ref +// Creator: Vincent Reverdy +// Contributor: Peter McLean [2025] +// License: BSD 3-Clause License +// ========================================================================== // +#ifndef _BIT_POLICY_HPP_INCLUDED +#define _BIT_POLICY_HPP_INCLUDED + +#include "bitlib/bit_concepts.hpp" +#include "bitlib/bit-algorithms/bit_algorithm.hpp" +#include "bitlib/bit-iterator/bit.hpp" +#include "bitlib/bit-iterator/bit_details.hpp" + +namespace bit::policy { + +struct truncate; +struct sign_extend; +template +struct typical { + using allocator = std::allocator>; + using extension = sign_extend; + using truncation = truncate; +}; + +struct truncate { + template + constexpr static void to_integral(const bit_sized_range auto& value, U& integral) noexcept { + bit_pointer integral_begin(&integral); + ::bit::copy(value.begin(), value.begin() + bitsof(), integral_begin); + } + template + constexpr static void from_integral(bit_sized_range auto& value, const U& integral) noexcept { + const bit_pointer integral_begin(&integral); + if constexpr (N == std::dynamic_extent) { + ::bit::copy(integral_begin, integral_begin + value.size(), value.begin()); + } else { + ::bit::copy(integral_begin, integral_begin + N, value.begin()); + } + } +}; + +struct sign_extend { + template + constexpr static void to_integral(const bit_sized_range auto& value, U& integral) noexcept { + bit_pointer integral_begin(&integral); + if constexpr (N == std::dynamic_extent) { + if constexpr (std::is_signed_v) { + if (value.last()[-1]) { + ::bit::fill(integral_begin + value.size(), integral_begin + bitsof(), bit1); + } + } + } else { + if constexpr (std::is_signed_v) { + if (value.begin()[N - 1]) { + ::bit::fill(integral_begin + value.size(), integral_begin + bitsof(), bit1); + } + } + } + } + template + constexpr static void to_integral(const bit_sized_range auto& value, U& integral, detail::uninitialized_t) noexcept { + bit_pointer integral_begin(&integral); + if constexpr (N == std::dynamic_extent) { + if constexpr (std::is_signed_v) { + ::bit::fill(integral_begin + value.size(), integral_begin + bitsof(), value.last()[-1]); + } + } else { + if constexpr (std::is_signed_v) { + ::bit::fill(integral_begin + N, integral_begin + bitsof(), value.begin()[N - 1]); + } + } + } + template + constexpr static void from_integral(bit_sized_range auto& value, const U& integral) noexcept { + if constexpr (N == std::dynamic_extent) { + if constexpr (std::is_signed_v) { + if (integral < 0) { + ::bit::fill(value.begin() + bitsof(), value.end(), bit1); + } + } + } else { + if constexpr (std::is_signed_v) { + if (integral < 0) { + ::bit::fill(value.begin() + N, value.end(), bit1); + } + } + } + } + template + constexpr static void from_integral(bit_sized_range auto& value, const U& integral, detail::uninitialized_t) noexcept { + if constexpr (N == std::dynamic_extent) { + if constexpr (std::is_signed_v) { + ::bit::fill(value.begin() + bitsof(), value.end(), (integral < 0) ? bit1 : bit0); + } + } else { + if constexpr (std::is_signed_v) { + ::bit::fill(value.begin() + N, value.end(), (integral < 0) ? bit1 : bit0); + } + } + } +}; + +} // namespace bit::policy + +#endif // _BIT_POLICY_HPP_INCLUDED diff --git a/include/bitlib/bit-containers/bit_span.hpp b/include/bitlib/bit-containers/bit_span.hpp index a1bd52a5..346ac854 100644 --- a/include/bitlib/bit-containers/bit_span.hpp +++ b/include/bitlib/bit-containers/bit_span.hpp @@ -12,11 +12,12 @@ // Project sources #include "bitlib/bit-algorithms/bit_algorithm.hpp" #include "bitlib/bit-containers/bit_bitsof.hpp" +#include "bitlib/bit-containers/bit_policy.hpp" #include "bitlib/bit-iterator/bit.hpp" namespace bit { -template +template class bit_array_ref; // Helper storage: for a fixed extent no runtime size is stored. @@ -35,7 +36,7 @@ struct bit_span_storage { bit_span_storage(std::size_t size) : size_(size) {} }; -template +template > class bit_span : private bit_span_storage { public: using word_type = WordType; @@ -130,7 +131,7 @@ class bit_span : private bit_span_storage { constexpr bit_span subspan(size_type offset, size_type count = std::dynamic_extent) const noexcept requires(Extent == std::dynamic_extent); - constexpr bit_array_ref operator()(size_type begin, size_type end) const noexcept; + constexpr bit_array_ref operator()(size_type begin, size_type end) const noexcept; template constexpr bit_span first() const noexcept @@ -171,46 +172,46 @@ bit_span(WordType*, std::size_t) -> bit_span; // Default constructor: // For dynamic extent, we start with a null pointer and size zero. // For fixed extent, the pointer is null but the size is always Extent. -template -constexpr bit_span::bit_span() noexcept : bit_span_storage(), data_(nullptr) {} +template +constexpr bit_span::bit_span() noexcept : bit_span_storage(), data_(nullptr) {} -template -constexpr bit_span::bit_span(const bit_span& other) noexcept : bit_span_storage(other), data_(other.data_) { +template +constexpr bit_span::bit_span(const bit_span& other) noexcept : bit_span_storage(other), data_(other.data_) { } // Construct from a bit_pointer and a bit count. -template -constexpr bit_span::bit_span(pointer data, size_type bit_count) noexcept +template +constexpr bit_span::bit_span(pointer data, size_type bit_count) noexcept requires(Extent == std::dynamic_extent) : bit_span_storage(bit_count), data_(data) {} // Construct from a WordType pointer and a word count. // This converts the word count to a bit count using bitsof. -template -constexpr bit_span::bit_span(WordType* word_ptr, size_type bit_count) noexcept +template +constexpr bit_span::bit_span(WordType* word_ptr, size_type bit_count) noexcept requires(Extent == std::dynamic_extent) : bit_span_storage(bit_count), data_(pointer(word_ptr, 0)) {} -template -constexpr bit_span::bit_span(WordType& word_ref, size_type bit_count) noexcept +template +constexpr bit_span::bit_span(WordType& word_ref, size_type bit_count) noexcept requires(Extent == std::dynamic_extent) : bit_span(&word_ref, bit_count) {} // Construct from a bit_pointer and a bit count. -template -constexpr bit_span::bit_span(pointer data) noexcept : bit_span_storage(), data_(data) {} +template +constexpr bit_span::bit_span(pointer data) noexcept : bit_span_storage(), data_(data) {} -template -constexpr bit_span::bit_span(WordType* word_ptr) noexcept : bit_span_storage(), data_(pointer(word_ptr, 0)) {} +template +constexpr bit_span::bit_span(WordType* word_ptr) noexcept : bit_span_storage(), data_(pointer(word_ptr, 0)) {} -template -constexpr bit_span::bit_span(WordType* word_ptr) +template +constexpr bit_span::bit_span(WordType* word_ptr) requires(std::is_scalar_v) : bit_span_storage(), data_(word_ptr) { } -template -constexpr bit_span::bit_span(WordType& word_ref) +template +constexpr bit_span::bit_span(WordType& word_ref) requires(std::is_scalar_v) : bit_span(&word_ref) { } @@ -218,75 +219,74 @@ constexpr bit_span::bit_span(WordType& word_ref) // --- Observers --- // Returns the number of bits in the span. -template -constexpr bit_span::size_type bit_span::size() const noexcept { - if constexpr (Extent == std::dynamic_extent) { - return this->size_; - } else { - return Extent; - } +template +constexpr bit_span::size_type bit_span::size() const noexcept { + if constexpr (Extent == std::dynamic_extent) { + return this->size_; + } else { + return Extent; + } } // Checks if the span is empty. -template -constexpr bool bit_span::empty() const noexcept { return size() == 0; } +template +constexpr bool bit_span::empty() const noexcept { return size() == 0; } // --- Element Access --- // Non-const element access. -template -constexpr bit_span::reference bit_span::operator[](size_type pos) { return begin()[pos]; } +template +constexpr bit_span::reference bit_span::operator[](size_type pos) { return begin()[pos]; } // Const element access returns a const_reference. -template -constexpr bit_span::const_reference bit_span::operator[](size_type pos) const { return begin()[pos]; } +template +constexpr bit_span::const_reference bit_span::operator[](size_type pos) const { return begin()[pos]; } -template -constexpr bit_span::reference bit_span::at(size_type pos) { - if (pos >= size()) { - throw std::out_of_range("span::at - index out of range"); - } - return data_[pos]; +template +constexpr bit_span::reference bit_span::at(size_type pos) { + if (pos >= size()) { + throw std::out_of_range("span::at - index out of range"); + } + return data_[pos]; } // Const element access returns a const_reference. -template -constexpr bit_span::const_reference bit_span::at(size_type pos) const { - if (pos >= size()) { - throw std::out_of_range("span::at - index out of range"); - } - return data_[pos]; +template +constexpr bit_span::const_reference bit_span::at(size_type pos) const { + if (pos >= size()) { + throw std::out_of_range("span::at - index out of range"); + } + return data_[pos]; } // Non-const front/back access. -template -constexpr bit_span::reference bit_span::front() { return begin()[0]; } -template -constexpr bit_span::reference bit_span::back() { return begin()[size() - 1]; } +template +constexpr bit_span::reference bit_span::front() { return begin()[0]; } +template +constexpr bit_span::reference bit_span::back() { return begin()[size() - 1]; } // Const front/back access returns a const_reference. -template -constexpr bit_span::const_reference bit_span::front() const { return begin()[0]; } -template -constexpr bit_span::const_reference bit_span::back() const { return begin()[size() - 1]; } +template +constexpr bit_span::const_reference bit_span::front() const { return begin()[0]; } +template +constexpr bit_span::const_reference bit_span::back() const { return begin()[size() - 1]; } // --- Iterators --- // Returns an iterator to the first bit. -template -constexpr bit_span::iterator bit_span::begin() const noexcept { return iterator(data_); } +template +constexpr bit_span::iterator bit_span::begin() const noexcept { return iterator(data_); } // Returns an iterator just past the last bit. -template -constexpr bit_span::iterator bit_span::end() const noexcept { return begin() + size(); } +template +constexpr bit_span::iterator bit_span::end() const noexcept { return begin() + size(); } // --- Subspan Functionality --- -template -template -constexpr auto bit_span::subspan() const noexcept +template +template +constexpr auto bit_span::subspan() const noexcept -> bit_span()> { - using NewSpanType = bit_span()>; if constexpr (NewExtent == std::dynamic_extent) { @@ -297,36 +297,46 @@ constexpr auto bit_span::subspan() const noexcept } // Creates a subspan starting at offset with count bits. -template -constexpr bit_span bit_span::subspan(size_type offset, size_type count) const noexcept requires(Extent == std::dynamic_extent) { - size_type new_count = (count == std::dynamic_extent) ? size() - offset : count; - return bit_span(begin() + offset, new_count); +template +constexpr bit_span bit_span::subspan(size_type offset, size_type count) const noexcept + requires(Extent == std::dynamic_extent) +{ + size_type new_count = (count == std::dynamic_extent) ? size() - offset : count; + return bit_span(begin() + offset, new_count); } -template -constexpr bit_array_ref bit_span::operator()(size_type begin, size_type end) const noexcept { - return bit_array_ref(&(this->begin()[begin]), end - begin); +template +constexpr bit_array_ref bit_span::operator()(size_type begin, size_type end) const noexcept { + return bit_array_ref(&(this->begin()[begin]), end - begin); } -template -template -constexpr bit_span bit_span::first() const noexcept requires(NewExtent != std::dynamic_extent) { +template +template +constexpr bit_span bit_span::first() const noexcept + requires(NewExtent != std::dynamic_extent) +{ return bit_span(data_); } -template -constexpr bit_span bit_span::first(size_type offset) const noexcept requires(Extent == std::dynamic_extent) { +template +constexpr bit_span bit_span::first(size_type offset) const noexcept + requires(Extent == std::dynamic_extent) +{ return bit_span(begin() + offset); } -template -template -constexpr bit_span bit_span::last() const noexcept requires(RevOffset != std::dynamic_extent) { +template +template +constexpr bit_span bit_span::last() const noexcept + requires(RevOffset != std::dynamic_extent) +{ return bit_span(begin() + size() - RevOffset); } -template -constexpr bit_span bit_span::last(size_type offset) const noexcept requires(Extent == std::dynamic_extent) { +template +constexpr bit_span bit_span::last(size_type offset) const noexcept + requires(Extent == std::dynamic_extent) +{ } } // namespace bit diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index c59b97dd..bd602267 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -1105,6 +1105,15 @@ constexpr auto with_bit_iterator_adapter( } } +namespace detail { + +struct uninitialized_t { + explicit uninitialized_t() = default; +}; +inline constexpr uninitialized_t uninitialized{}; + +} // namespace detail + // ========================================================================== // } // namespace bit #endif // _BIT_DETAILS_HPP_INCLUDED diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index eda549e9..f50e47ae 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -29,6 +29,7 @@ target_sources(bitlib-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/test-copy_backward.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-copy.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-count.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-count_leading.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-equal.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-fill.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-find.cpp @@ -40,6 +41,7 @@ target_sources(bitlib-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/test-shift.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-span.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-swap_ranges.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test-to_string.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-transform.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-usecase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test-array_ref.cpp diff --git a/test/src/test-count_leading.cpp b/test/src/test-count_leading.cpp new file mode 100644 index 00000000..0eb85bae --- /dev/null +++ b/test/src/test-count_leading.cpp @@ -0,0 +1,18 @@ +#include +#include +#include + +#include "bitlib/bit-containers/bit_array.hpp" +#include "bitlib/bit-containers/bit_array_dynamic_extent.hpp" +#include "bitlib/bit-algorithms/count.hpp" +#include "fixtures.hpp" + +// Third-party libraries +#include "gtest/gtest.h" + +TEST(CountLeading, CountLeadingZeroes) { + auto num = bit::bit_array(bit::bit0); + auto clz = bit::count_msb(num.begin(), num.end(), bit::bit0); + std::cout << "Count leading zeroes: " << clz << std::endl; + EXPECT_EQ(clz, 128); +} diff --git a/test/src/test-literal.cpp b/test/src/test-literal.cpp index b8411515..d91e2cf3 100644 --- a/test/src/test-literal.cpp +++ b/test/src/test-literal.cpp @@ -22,8 +22,8 @@ TEST(BitLiteral, dec_base) { auto one = 1'1_b; EXPECT_EQ(one.size(), 1); EXPECT_EQ(one[0], bit::bit1); - auto three = 2'1'1_b; - EXPECT_EQ(three.size(), 2); + auto three = 4'1'1_b; + EXPECT_EQ(three.size(), 4); EXPECT_EQ(three[0], bit::bit1); EXPECT_EQ(three[1], bit::bit1); auto ten = 4'10_b; @@ -71,8 +71,8 @@ TEST(BitLiteral, UserDefinedLiteral) { EXPECT_EQ(arr.size(), 8); EXPECT_EQ(arr[7], bit::bit0); EXPECT_EQ(arr[0], bit::bit1); - auto arr2 = 0b1'01001101_b; - EXPECT_EQ(arr2.size(), 1); + auto arr2 = 0b1000'01001101_b; + EXPECT_EQ(arr2.size(), 8); } TEST(BitLiteral, UserDefinedHexLiteral) { @@ -95,3 +95,12 @@ TEST(BitLiteral, UserDefinedDecLiteral) { auto arr3 = 16'123'45_b; EXPECT_EQ(arr, arr3); } + +TEST(BitLiteral, SizeOneLiteral) { + auto arr = 1_b; + EXPECT_EQ(arr.size(), 1); + + bit::bit_value b = arr; + EXPECT_EQ(b, bit::bit1); + EXPECT_EQ(arr, bit::bit1); +} diff --git a/test/src/test-to_string.cpp b/test/src/test-to_string.cpp new file mode 100644 index 00000000..8ed62903 --- /dev/null +++ b/test/src/test-to_string.cpp @@ -0,0 +1,28 @@ +#include +#include +#include + +#include "bitlib/bit-containers/bit_array.hpp" +#include "bitlib/bit-containers/bit_array_dynamic_extent.hpp" +#include "bitlib/bit-algorithms/count.hpp" +#include "bitlib/bit-algorithms/to_string.hpp" +#include "fixtures.hpp" + +// Third-party libraries +#include "gtest/gtest.h" + +TEST(ToString, Blah) { + auto num = 0x20'DEADBEEF_b; + EXPECT_EQ(static_cast(num), 0xDEADBEEF); + auto str = bit::to_string<16>(num); + EXPECT_EQ(str, "DEADBEEF"); + num = 0x20'0EADBEEF_b; + str = bit::to_string<16>(num); + EXPECT_EQ(str, "EADBEEF"); + str = bit::to_string<16, true>(num); + EXPECT_EQ(str, "0EADBEEF"); + auto num2 = 0x23'10EADBEEF_b; + EXPECT_EQ(num2.size(), 35); + str = bit::to_string<16>(num2); + EXPECT_EQ(str, "10EADBEEF"); +} From d917039b0a91140f8a22d047c2a56647659ebedd Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Wed, 25 Jun 2025 01:27:12 +0000 Subject: [PATCH 70/85] Make bit::bit_array an alias of bit::array (#19) * Make bit::bit_array an alias of bit::array * Missing cstdint include * Refactor includes w.r.t bit_span --- include/bitlib/bit-algorithms/accumulate.hpp | 2 +- .../bitlib/bit-algorithms/to_from_string.hpp | 164 ++++++++++++++++++ include/bitlib/bit-algorithms/to_string.hpp | 5 +- include/bitlib/bit-containers/bit_array.hpp | 91 ++++++---- .../bitlib/bit-containers/bit_array_base.hpp | 22 ++- .../bit_array_dynamic_extent.hpp | 67 ++++--- .../bitlib/bit-containers/bit_array_ref.hpp | 59 ++++--- include/bitlib/bit-containers/bit_literal.hpp | 13 +- include/bitlib/bit-containers/bit_span.hpp | 10 +- include/bitlib/bit-containers/bit_vector.hpp | 1 - include/bitlib/bit_concepts.hpp | 1 + test/src/test-array.cpp | 80 ++++----- test/src/test-count_leading.cpp | 2 +- test/src/test-to_string.cpp | 4 +- 14 files changed, 352 insertions(+), 169 deletions(-) create mode 100644 include/bitlib/bit-algorithms/to_from_string.hpp diff --git a/include/bitlib/bit-algorithms/accumulate.hpp b/include/bitlib/bit-algorithms/accumulate.hpp index 1e5e46cc..f01c0833 100644 --- a/include/bitlib/bit-algorithms/accumulate.hpp +++ b/include/bitlib/bit-algorithms/accumulate.hpp @@ -1,4 +1,4 @@ -// ================================= BIT_ARRAY_REF =================================== // +// ================================= array_REF =================================== // // Project: The Experimental Bit Algorithms Library // \file accumulate.hpp // Description: Implementation of accumulate diff --git a/include/bitlib/bit-algorithms/to_from_string.hpp b/include/bitlib/bit-algorithms/to_from_string.hpp new file mode 100644 index 00000000..faf98b0e --- /dev/null +++ b/include/bitlib/bit-algorithms/to_from_string.hpp @@ -0,0 +1,164 @@ +// ================================= array_REF =================================== // +// Project: The Experimental Bit Algorithms Library +// \file to_string.hpp +// Description: Implementation of array_ref +// Creator: Vincent Reverdy +// Contributor: Peter McLean [2025] +// License: BSD 3-Clause License +// ========================================================================== // + +#ifndef _BIT_TO_STRING_HPP_INCLUDED +#define _BIT_TO_STRING_HPP_INCLUDED + +#include + +#include "bitlib/bit-algorithms/accumulate.hpp" +#include "bitlib/bit-algorithms/count.hpp" +#include "bitlib/bit-containers/array_dynamic_extent.hpp" +#include "bitlib/bit-containers/bit_vector.hpp" +#include "bitlib/bit_concepts.hpp" + +namespace bit { + +namespace string { + +template +constexpr auto make_digit_map() { + static_assert((Base >= 2) && ((Base & (Base - 1)) == 0), "Base must be power of 2 >= 2"); + static_assert(Base <= 64, "Base too large for simple char mapping"); + + std::array map = {}; + for (std::size_t i = 0; i < Base; ++i) { + map[i] = (i < 10) ? ('0' + i) : ('A' + (i - 10)); + } + return map; +} + +template +constexpr auto make_from_digit_map() { + static_assert((Base >= 2) && ((Base & (Base - 1)) == 0), "Base must be power of 2 >= 2"); + static_assert(Base <= 64, "Base too large for simple char mapping"); + + std::array map = {}; + for (std::size_t i = 0; i < 128; ++i) { + map[i] = ~0; + if (i >= '0' && i <= '9') { + map[i] = i - '0'; + } + if (i >= 'a' && i <= 'z') { + map[i] = (i - 'a') + 10; + } + if (i >= 'A' && i <= 'Z') { + map[i] = (i - 'A') + 10; + } + } + return map; +} + +struct metadata_t { + size_t base; + bool is_signed; + std::endian endian; + bool str_sign_extend_zeros; +}; + +constexpr metadata_t typical(size_t base = 10, bool str_sign_extend_zeros = false) { + return { + .base = base, + .is_signed = false, + .endian = std::endian::big, + .str_sign_extend_zeros = str_sign_extend_zeros}; +} + +} // namespace string + +template +constexpr std::string to_string(const bit_iterator& first, const bit_iterator& last, std::string prefix = "") { + static_assert(meta.endian == std::endian::big, "Only bit big endian support (MSB on the left)"); + if constexpr (std::has_single_bit(meta.base)) { + constexpr const auto base_bits = std::bit_width(meta.base - 1); + + int skip_leading_bits = meta.str_sign_extend_zeros ? 0 : count_msb(first, last, bit0); + + int str_len = (distance(first, last) - skip_leading_bits); + str_len += (0 != (str_len % base_bits)); + std::string& str = prefix; + str.resize(str.length() + str_len); + + static constexpr auto base_digits = string::make_digit_map(); + + return accumulate( + policy::AccumulateNoInitialSubword{}, + first, last - skip_leading_bits, (str.data() + str_len), + [](char* acc, auto word, const size_t bits = bitsof()) { + const int characters = ((bits + base_bits - 1) / base_bits); + acc -= characters; + for (int i = characters - 1; i >= 0; i--) { + acc[i] = base_digits[word & (meta.base - 1)]; + word >>= base_bits; + } + return acc; + }); + } else { + return "not_implented_yet"; + } +} + +template +constexpr std::string to_string(const bit_sized_range auto& bits, std::string prefix = "") { + return to_string(bits.begin(), bits.end(), prefix); +} + +template , typename RandomAccessIt> +constexpr void from_string( + Policy, + const char* str_first, const char* str_last, + bit_iterator bit_first, bit_iterator bit_last) { + const auto str_len = str_last - str_first; +} + +template +constexpr bit_vector<> from_string(const char* first, const char* last) { + static_assert(meta.endian == std::endian::big, "Only bit big endian support (MSB on the left)"); + if constexpr (std::has_single_bit(meta.base)) { + constexpr const auto base_bits = std::bit_width(meta.base - 1); + static constexpr auto base_from_digits = string::make_from_digit_map(); + + bit_vector<> vec; + + last--; + while (last >= first) { + uint64_t work = 0; + size_t bits = 0; + for (; (bits < bitsof()) && (last >= first); last--) { + char c = *last; + // TODO: This should be a policy + if (c >= base_from_digits.size()) { + continue; + } + auto digit = base_from_digits[c]; + // TODO: This should be a policy + if (~0 == digit) { + continue; + } + work |= (digit << bits); + bits += base_bits; + } + if (bits) { + vec.append_range(array<>(bits, work)); + } + } + return vec; + } else { + //from_string base 10 not implemented yet; + } +} + +template +constexpr bit_vector<> from_string(const std::string& str) { + return from_string(str.c_str(), str.c_str() + str.length()); +} + +} // namespace bit + +#endif // _BIT_TO_STRING_HPP_INCLUDED \ No newline at end of file diff --git a/include/bitlib/bit-algorithms/to_string.hpp b/include/bitlib/bit-algorithms/to_string.hpp index cb31c8d7..be9050d3 100644 --- a/include/bitlib/bit-algorithms/to_string.hpp +++ b/include/bitlib/bit-algorithms/to_string.hpp @@ -1,7 +1,7 @@ -// ================================= BIT_ARRAY_REF =================================== // +// ================================= array_REF =================================== // // Project: The Experimental Bit Algorithms Library // \file to_string.hpp -// Description: Implementation of bit_array_ref +// Description: Implementation of array_ref // Creator: Vincent Reverdy // Contributor: Peter McLean [2025] // License: BSD 3-Clause License @@ -11,6 +11,7 @@ #define _BIT_TO_STRING_HPP_INCLUDED #include +#include #include "bitlib/bit-algorithms/accumulate.hpp" #include "bitlib/bit-algorithms/count.hpp" diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index a46a3c0b..23263fc0 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -25,12 +25,30 @@ #include "bitlib/bit-containers/bit_array_base.hpp" #include "bitlib/bit-containers/bit_bitsof.hpp" #include "bitlib/bit-containers/bit_policy.hpp" -#include "bitlib/bit-containers/bit_span.hpp" #include "bitlib/bit-iterator/bit.hpp" #include "bitlib/bit_concepts.hpp" namespace bit { -// ========================================================================== // + +template , + std::conditional_t< + (N == std::dynamic_extent), + std::uintptr_t, + ceil_integral>, + T>, + typename Policy = policy::typical> +class array; + +template >, + typename Policy = policy::typical> +using bit_array = array; namespace detail { @@ -38,7 +56,7 @@ template constexpr size_t Words() { return (N * bitsof() + bitsof() - 1) / bitsof(); } template -struct bit_array_iterator_types { +struct array_iterator_types { using iterator = typename std::conditional, bit_iterator()>::iterator>, typename std::array()>::iterator>::type; @@ -49,13 +67,10 @@ struct bit_array_iterator_types { }; } // namespace detail -template >, - typename Policy = policy::typical> -class bit_array : public bit_array_base, T, N, W, Policy, detail::bit_array_iterator_types> { +template +class array : public array_base, T, N, W, Policy, detail::array_iterator_types> { public: - using base = bit_array_base, T, N, W, Policy, detail::bit_array_iterator_types>; + using base = array_base, T, N, W, Policy, detail::array_iterator_types>; using base::end; using typename base::const_iterator; using typename base::const_pointer; @@ -82,47 +97,47 @@ class bit_array : public bit_array_base, T, N, W, Policy, det /* * Constructors, copies and moves... */ - constexpr bit_array() noexcept : storage{} {} + constexpr array() noexcept : storage{} {} - constexpr bit_array(value_type bit_val) { + constexpr array(value_type bit_val) { this->fill(bit_val); } template - constexpr bit_array(const U& integral) { + constexpr array(const U& integral) { this->from_integral(integral); } - constexpr bit_array(const bit_array& other) noexcept + constexpr array(const array& other) noexcept : storage(other.storage) {} - constexpr bit_array(const bit_array&& other) noexcept + constexpr array(const array&& other) noexcept : storage(other.storage) {} - constexpr bit_array(const bit_sized_range auto& other) { + constexpr array(const bit_sized_range auto& other) { if (other.size() != this->size()) [[unlikely]] { - throw std::invalid_argument("other bit_range contains an invalid number of bits for bit_array."); + throw std::invalid_argument("other bit_range contains an invalid number of bits for array."); } ::bit::copy(other.begin(), other.end(), this->begin()); }; - constexpr bit_array(const std::initializer_list init) + constexpr array(const std::initializer_list init) requires(!std::is_same_v) { if (init.size() != bitsof(*this)) [[unlikely]] { - throw std::invalid_argument("initialize_list contains an invalid number of bits for bit_array."); + throw std::invalid_argument("initialize_list contains an invalid number of bits for array."); } std::copy(init.begin(), init.end(), this->begin()); } - constexpr bit_array(const std::initializer_list init) { + constexpr array(const std::initializer_list init) { if (init.size() != bitsof(*this)) [[unlikely]] { - throw std::invalid_argument("initialize_list contains an invalid number of bits for bit_array."); + throw std::invalid_argument("initialize_list contains an invalid number of bits for array."); } std::copy(init.begin(), init.end(), this->begin()); } - constexpr bit_array(const std::initializer_list init) : storage{} { + constexpr array(const std::initializer_list init) : storage{} { // Make sure we handle the case where init.size() != Words auto it = init.begin(); for (size_type i = 0; i < std::min(Words(N), init.size()); ++i, ++it) { @@ -130,11 +145,11 @@ class bit_array : public bit_array_base, T, N, W, Policy, det } } - constexpr bit_array(const std::string_view s) + constexpr array(const std::string_view s) requires(std::is_same_v) { if (bitsof(*this) != static_cast(std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))) [[unlikely]] { - throw std::invalid_argument("String contains an invalid number of bits for bit_array."); + throw std::invalid_argument("String contains an invalid number of bits for array."); }; size_type i = 0; for (char c : s) { @@ -146,21 +161,21 @@ class bit_array : public bit_array_base, T, N, W, Policy, det } } - ~bit_array() = default; + ~array() = default; /* * Assignment */ - constexpr bit_array& operator=(const bit_array& other) = default; + constexpr array& operator=(const array& other) = default; - constexpr bit_array& operator=(const bit_sized_range auto& other) { + constexpr array& operator=(const bit_sized_range auto& other) { if (other.size() != this->size()) [[unlikely]] { - throw std::invalid_argument("other bit_sized_range contains an invalid number of bits for bit_array."); + throw std::invalid_argument("other bit_sized_range contains an invalid number of bits for array."); } ::bit::copy(other.begin(), other.end(), this->begin()); return *this; }; - constexpr bit_array& operator=(bit_array&& other) noexcept { + constexpr array& operator=(array&& other) noexcept { std::copy(other.storage.begin(), other.storage.end(), storage.begin()); return *this; } @@ -194,7 +209,7 @@ class bit_array : public bit_array_base, T, N, W, Policy, det /* * Operations */ - constexpr void swap(bit_array& other) noexcept { + constexpr void swap(array& other) noexcept { std::swap(this->storage, other.storage); } @@ -205,11 +220,11 @@ class bit_array : public bit_array_base, T, N, W, Policy, det } }; -static_assert(bit_range>, "bit_array does not satisfy bit_range concept!"); -static_assert(bit_sized_range>, "bit_array does not satisfy bit_sized_range concept!"); +static_assert(bit_range>, "array does not satisfy bit_range concept!"); +static_assert(bit_sized_range>, "array does not satisfy bit_sized_range concept!"); #ifdef CONTIGUOUS_RANGE -static_assert(bit_contiguous_range>, "bit_array does not satisfy bit_contiguous_range concept!"); -static_assert(bit_contiguous_sized_range>, "bit_array does not satisfy bit_contiguous_sized_range concept!"); +static_assert(bit_contiguous_range>, "array does not satisfy bit_contiguous_range concept!"); +static_assert(bit_contiguous_sized_range>, "array does not satisfy bit_contiguous_sized_range concept!"); #endif #if 0 @@ -217,18 +232,18 @@ static_assert(bit_contiguous_sized_range>, "bit_array does not sat // CTAD guide for the constructor taking a word_type&, // deducing Extent as bitsof(). template -bit_array(word_type&) -> bit_array()>; +array(word_type&) -> array()>; template -bit_array(word_type*) -> bit_array()>; +array(word_type*) -> array()>; // CTAD guide for the constructor taking a word_type* and a size, // which should always be used when Extent is std::dynamic_extent. template -bit_array(word_type&, std::size_t) -> bit_array; +array(word_type&, std::size_t) -> array; template -bit_array(word_type*, std::size_t) -> bit_array; +array(word_type*, std::size_t) -> array; #endif } // namespace bit -#endif // _BIT_ARRAY_HPP_INCLUDED \ No newline at end of file +#endif // _array_HPP_INCLUDED \ No newline at end of file diff --git a/include/bitlib/bit-containers/bit_array_base.hpp b/include/bitlib/bit-containers/bit_array_base.hpp index ed3f432b..af75d2bc 100644 --- a/include/bitlib/bit-containers/bit_array_base.hpp +++ b/include/bitlib/bit-containers/bit_array_base.hpp @@ -24,25 +24,24 @@ #include "bitlib/bit-algorithms/bit_algorithm.hpp" #include "bitlib/bit-containers/bit_bitsof.hpp" #include "bitlib/bit-containers/bit_policy.hpp" -#include "bitlib/bit-containers/bit_span.hpp" #include "bitlib/bit-iterator/bit.hpp" namespace bit { template -class bit_array_ref; +class array_ref; template -class bit_array; +class array; /** - * @brief Base class template for bit_array implementations + * @brief Base class template for array implementations * * This is a CRTP (Curiously Recurring Template Pattern) base class that provides - * common functionality for bit_array variants. + * common functionality for array variants. * * @tparam Derived The derived class (CRTP pattern) * @tparam T The value type (typically bit_value) @@ -52,7 +51,7 @@ class bit_array; * @tparam Iterators A struct that provides iterator and const_iterator types */ template -class bit_array_base { +class array_base { protected: constexpr Derived& derived() noexcept { return static_cast(*this); @@ -161,17 +160,17 @@ class bit_array_base { } /** - * @brief Slice operations - returns a bit_array_ref + * @brief Slice operations - returns a array_ref */ constexpr auto operator()(size_type offset, size_type right) const noexcept { - return bit_array_ref(&this->at(offset), right - offset); + return array_ref(&this->at(offset), right - offset); } /** - * @brief Slice operations - returns a bit_array_ref + * @brief Slice operations - returns a array_ref */ constexpr auto operator()(size_type offset, size_type right) noexcept { - return bit_array_ref(&this->at(offset), right - offset); + return array_ref(&this->at(offset), right - offset); } // Common operations @@ -210,7 +209,7 @@ class bit_array_base { return integral; } - using compatible_bitarray = bit_array; + using compatible_bitarray = array; constexpr compatible_bitarray operator~() { compatible_bitarray result(derived().size()); @@ -295,4 +294,3 @@ constexpr bool operator==(const bit_sized_range auto& lhs, const bit_sized_range } // namespace bit #endif // _BIT_ARRAY_BASE_HPP_INCLUDED - // ========================================================================== // diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index 53544db2..57907fbd 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -1,6 +1,6 @@ // ================================= BIT_ARRAY =================================== // // Project: The Experimental Bit Algorithms Library -// \file bit_array.hpp +// \file bit_array_dynamic_extent.hpp // Description: Implementation of bit_array // Creator: Vincent Reverdy // Contributor: Peter McLean [2025] @@ -22,7 +22,6 @@ #include "bitlib/bit-containers/bit_array_base.hpp" #include "bitlib/bit-containers/bit_bitsof.hpp" #include "bitlib/bit-containers/bit_policy.hpp" -#include "bitlib/bit-containers/bit_span.hpp" #include "bitlib/bit-iterator/bit.hpp" #include "bitlib/bit_concepts.hpp" @@ -30,7 +29,7 @@ namespace bit { namespace detail { template -struct bit_array_dextent_iterator_types { +struct array_dextent_iterator_types { using iterator = typename std::conditional, bit_iterator, word_type*>::type; @@ -40,10 +39,10 @@ struct bit_array_dextent_iterator_types { }; } // namespace detail template -class bit_array - : public bit_array_base, T, std::dynamic_extent, W, Policy, detail::bit_array_dextent_iterator_types> { +class array + : public array_base, T, std::dynamic_extent, W, Policy, detail::array_dextent_iterator_types> { public: - using base = bit_array_base, T, std::dynamic_extent, W, Policy, detail::bit_array_dextent_iterator_types>; + using base = array_base, T, std::dynamic_extent, W, Policy, detail::array_dextent_iterator_types>; using base::end; using typename base::const_iterator; using typename base::const_pointer; @@ -138,7 +137,7 @@ class bit_array }; public: - ~bit_array() { + ~array() { if (size() > FixedBits) { storage.m_allocator.deallocate(storage.pointer, Words(size())); } else { @@ -151,50 +150,50 @@ class bit_array /* * Constructors, copies and moves... */ - bit_array() = delete; + array() = delete; - constexpr bit_array(const size_type size, const Allocator& allocator = Allocator()) + constexpr array(const size_type size, const Allocator& allocator = Allocator()) : m_size(size), storage(Words(size), allocator) { } template - constexpr bit_array(const size_type size, const U& integral, const Allocator& allocator = Allocator()) + constexpr array(const size_type size, const U& integral, const Allocator& allocator = Allocator()) : m_size(size), storage(Words(size), allocator, detail::uninitialized) { this->from_integral(integral); } - constexpr bit_array(const size_type size, const word_type val, const Allocator& allocator = Allocator()) + constexpr array(const size_type size, const word_type val, const Allocator& allocator = Allocator()) : m_size(size), storage(Words(size), val, allocator) { } - constexpr bit_array(const size_type size, const value_type bit_val, const Allocator& allocator = Allocator()) + constexpr array(const size_type size, const value_type bit_val, const Allocator& allocator = Allocator()) requires(!std::is_same::value) : m_size(size), storage(Words(size), allocator, detail::uninitialized) { this->fill(bit_val); } - constexpr bit_array(const bit_array& other) + constexpr array(const array& other) : m_size(other.size()), storage(Words(size()), other.storage) { } - constexpr bit_array(const bit_array& other, const Allocator& allocator) + constexpr array(const array& other, const Allocator& allocator) : m_size(other.size()), storage(Words(size()), other.storage, allocator) { } - constexpr bit_array(const bit_sized_range auto& other, const Allocator& allocator = Allocator()) - : bit_array(other.size(), allocator, detail::uninitialized) { + constexpr array(const bit_sized_range auto& other, const Allocator& allocator = Allocator()) + : array(other.size(), allocator, detail::uninitialized) { ::bit::copy(other.begin(), other.end(), this->begin()); } - constexpr bit_array(bit_array&& other) + constexpr array(array&& other) : m_size(other.size()), storage(Words(size()), std::move(other.storage)) { } - constexpr bit_array(bit_array&& other, const Allocator& allocator) + constexpr array(array&& other, const Allocator& allocator) : m_size(other.size()), storage(Words(size()), std::move(other.storage), allocator) { } - constexpr bit_array(const std::initializer_list init, const Allocator& allocator = Allocator()) + constexpr array(const std::initializer_list init, const Allocator& allocator = Allocator()) requires(!std::is_same_v) : m_size(init.size()), storage(Words(size()), allocator, detail::uninitialized) { std::copy(init.begin(), init.end(), this->begin()); @@ -203,7 +202,7 @@ class bit_array #if 0 No known conversion from bool to bit_value bit_value has explicit constructor from bool to bit_value so this doesnt work - constexpr bit_array::bit_array(const std::initializer_list init) + constexpr array::array(const std::initializer_list init) : storage(std::make_unique(Words(init.size()))), m_size(init.size()) { std::copy(init.begin(), init.end(), this->begin()); @@ -211,12 +210,12 @@ class bit_array #endif template - constexpr bit_array(const std::initializer_list init, const Allocator& allocator = Allocator()) + constexpr array(const std::initializer_list init, const Allocator& allocator = Allocator()) : m_size(bitsof() * init.size()), storage(Words(size()), allocator, detail::uninitialized) { std::copy(init.begin(), init.end(), data()); } - constexpr bit_array(const std::string_view s, const Allocator& allocator = Allocator()) + constexpr array(const std::string_view s, const Allocator& allocator = Allocator()) requires(std::is_same_v) : m_size(std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1')), storage(Words(size()), allocator, detail::uninitialized) { size_type i = 0; @@ -232,9 +231,9 @@ class bit_array /* * Assignment */ - constexpr bit_array& operator=(const bit_array& other) { + constexpr array& operator=(const array& other) { if (nullptr == data() || size() != other.size()) { - throw std::invalid_argument("Cannot reassign bit_array size"); + throw std::invalid_argument("Cannot reassign array size"); } if (this == &other) [[unlikely]] { return *this; @@ -243,19 +242,19 @@ class bit_array return *this; } - constexpr bit_array& operator=(const bit_sized_range auto& other) { + constexpr array& operator=(const bit_sized_range auto& other) { if (other.size() != this->size()) [[unlikely]] { - throw std::invalid_argument("other bit_range contains an invalid number of bits for bit_array."); + throw std::invalid_argument("other bit_range contains an invalid number of bits for array."); } ::bit::copy(other.begin(), other.end(), this->begin()); return *this; }; - constexpr bit_array& operator=(bit_array&& other) { + constexpr array& operator=(array&& other) { if (nullptr == data() || size() != other.size()) { - throw std::invalid_argument("Cannot reassign bit_array size"); + throw std::invalid_argument("Cannot reassign array size"); } - bit_array temp(std::move(other)); + array temp(std::move(other)); swap(temp); return *this; } @@ -308,7 +307,7 @@ class bit_array /* * Operations */ - constexpr void swap(bit_array& other) noexcept { + constexpr void swap(array& other) noexcept { assert(size() == other.size()); if (size() > FixedBits) { std::swap(this->storage.pointer, other.storage.pointer); @@ -320,11 +319,11 @@ class bit_array } }; -static_assert(bit_range>, "bit_array<> does not satisfy bit_contiguous_range concept!"); -static_assert(bit_sized_range>, "bit_array<> does not satisfy bit_contiguous_sized_range concept!"); +static_assert(bit_range>, "array<> does not satisfy bit_contiguous_range concept!"); +static_assert(bit_sized_range>, "array<> does not satisfy bit_contiguous_sized_range concept!"); #ifdef CONTIGUOUS_RANGE -static_assert(bit_contiguous_range>, "bit_array<> does not satisfy bit_contiguous_range concept!"); -static_assert(bit_contiguous_sized_range>, "bit_array<> does not satisfy bit_contiguous_sized_range concept!"); +static_assert(bit_contiguous_range>, "array<> does not satisfy bit_contiguous_range concept!"); +static_assert(bit_contiguous_sized_range>, "array<> does not satisfy bit_contiguous_sized_range concept!"); #endif // ========================================================================== // diff --git a/include/bitlib/bit-containers/bit_array_ref.hpp b/include/bitlib/bit-containers/bit_array_ref.hpp index c19a6e3c..5706b6e0 100644 --- a/include/bitlib/bit-containers/bit_array_ref.hpp +++ b/include/bitlib/bit-containers/bit_array_ref.hpp @@ -20,15 +20,20 @@ #include "bitlib/bit-containers/bit_array_base.hpp" #include "bitlib/bit-containers/bit_bitsof.hpp" #include "bitlib/bit-containers/bit_policy.hpp" -#include "bitlib/bit-containers/bit_span.hpp" #include "bitlib/bit-iterator/bit.hpp" #include "bitlib/bit_concepts.hpp" namespace bit { +template > +class array_ref; + +template > +using bit_array_ref = array_ref; + namespace detail { template -struct bit_array_ref_iterator_types { +struct array_ref_iterator_types { using iterator = bit_iterator; using const_iterator = bit_iterator; }; @@ -36,18 +41,18 @@ struct bit_array_ref_iterator_types { /** * @brief A non-owning reference to a bit array * - * Similar to bit_array_dynamic_extent but does not allocate or deallocate memory. + * Similar to array_dynamic_extent but does not allocate or deallocate memory. * The pointer and size are const class members and cannot be re-bound. * Assignment operations always copy content and can't rebind the pointer/size. * * @tparam T The value type (typically bit_value) * @tparam W The word type used for storage */ -template > -class bit_array_ref - : public bit_array_base, T, std::dynamic_extent, W, Policy, detail::bit_array_ref_iterator_types> { +template +class array_ref + : public array_base, T, std::dynamic_extent, W, Policy, detail::array_ref_iterator_types> { public: - using base = bit_array_base, T, std::dynamic_extent, W, Policy, detail::bit_array_ref_iterator_types>; + using base = array_base, T, std::dynamic_extent, W, Policy, detail::array_ref_iterator_types>; using base::end; using typename base::const_iterator; using typename base::const_pointer; @@ -66,7 +71,7 @@ class bit_array_ref public: // Constructors - bit_array_ref() = delete; + array_ref() = delete; /** * @brief Constructs a non-owning reference to a bit array @@ -74,7 +79,7 @@ class bit_array_ref * @param storage Pointer to the storage * @param size Number of bits */ - constexpr bit_array_ref(word_type* storage, size_type size) + constexpr array_ref(word_type* storage, size_type size) : m_storage(storage), m_size(size) { } @@ -85,7 +90,7 @@ class bit_array_ref * @param storage bit_pointer to the storage * @param size Number of bits */ - constexpr bit_array_ref(bit_pointer storage, size_type size) + constexpr array_ref(bit_pointer storage, size_type size) : m_storage(storage), m_size(size) { } @@ -95,7 +100,7 @@ class bit_array_ref * * @param other bit_sized_range */ - constexpr bit_array_ref(bit_range auto& other, size_type size) + constexpr array_ref(bit_range auto& other, size_type size) : m_storage(&(*other.begin())), m_size(size) { assert(size <= (other.end() - other.begin())); @@ -106,7 +111,7 @@ class bit_array_ref * * @param other bit_sized_range */ - constexpr bit_array_ref(const bit_range auto& other, size_type size) + constexpr array_ref(const bit_range auto& other, size_type size) requires(std::is_const_v) : m_storage(&(*other.begin())), m_size(size) { @@ -116,19 +121,19 @@ class bit_array_ref /** * @brief Copy constructor */ - constexpr bit_array_ref(const bit_array_ref& other) = default; + constexpr array_ref(const array_ref& other) = default; /** * @brief Move constructor */ - constexpr bit_array_ref(bit_array_ref&& other) = default; + constexpr array_ref(array_ref&& other) = default; /** * @brief Range Assignment operator - copies content but doesn't rebind */ - constexpr bit_array_ref& operator=(const bit_sized_range auto& other) { + constexpr array_ref& operator=(const bit_sized_range auto& other) { if (m_size != other.size()) { - throw std::invalid_argument("Cannot assign from bit_array_ref of different size"); + throw std::invalid_argument("Cannot assign from array_ref of different size"); } ::bit::copy(other.begin(), other.end(), this->begin()); return *this; @@ -137,10 +142,10 @@ class bit_array_ref /** * @brief Copy Assignment operator - copies content but doesn't rebind */ - constexpr bit_array_ref& operator=(const bit_array_ref& other) { + constexpr array_ref& operator=(const array_ref& other) { if (this != &other) { if (m_size != other.m_size) { - throw std::invalid_argument("Cannot assign from bit_array_ref of different size"); + throw std::invalid_argument("Cannot assign from array_ref of different size"); } ::bit::copy(other.begin(), other.end(), this->begin()); } @@ -150,10 +155,10 @@ class bit_array_ref /** * @brief Move assignment operator - copies content but doesn't rebind */ - constexpr bit_array_ref& operator=(bit_array_ref&& other) { + constexpr array_ref& operator=(array_ref&& other) { if (this != &other) { if (m_size != other.size()) { - throw std::invalid_argument("Cannot assign from bit_array_ref of different size"); + throw std::invalid_argument("Cannot assign from array_ref of different size"); } ::bit::copy(other.begin(), other.end(), this->begin()); } @@ -163,7 +168,7 @@ class bit_array_ref /** * @brief No destructor needed as we don't own the memory */ - ~bit_array_ref() = default; + ~array_ref() = default; /* * Iterators @@ -186,19 +191,19 @@ class bit_array_ref /* * Operations */ - constexpr void swap(bit_array_ref& other) { + constexpr void swap(array_ref& other) { if (m_size != other.m_size) { - throw std::invalid_argument("Cannot swap bit_array_ref of different sizes"); + throw std::invalid_argument("Cannot swap array_ref of different sizes"); } swap_ranges(begin(), end(), other.begin()); } }; -static_assert(bit_range>, "bit_array_ref<> does not satisfy bit_range concept!"); -static_assert(bit_sized_range>, "bit_array_ref<> does not satisfy bit_sized_range concept!"); +static_assert(bit_range>, "array_ref<> does not satisfy bit_range concept!"); +static_assert(bit_sized_range>, "array_ref<> does not satisfy bit_sized_range concept!"); #ifdef CONTIGUOUS_RANGE -static_assert(bit_contiguous_range>, "bit_array_ref<> does not satisfy bit_contiguous_range concept!"); -static_assert(bit_contiguous_sized_range>, "bit_array_ref<> does not satisfy bit_contiguous_sized_range concept!"); +static_assert(bit_contiguous_range>, "array_ref<> does not satisfy bit_contiguous_range concept!"); +static_assert(bit_contiguous_sized_range>, "array_ref<> does not satisfy bit_contiguous_sized_range concept!"); #endif // ========================================================================== // diff --git a/include/bitlib/bit-containers/bit_literal.hpp b/include/bitlib/bit-containers/bit_literal.hpp index cb809f68..cc579939 100644 --- a/include/bitlib/bit-containers/bit_literal.hpp +++ b/include/bitlib/bit-containers/bit_literal.hpp @@ -1,7 +1,7 @@ -// ================================= BIT_ARRAY =================================== // +// ================================= array =================================== // // Project: The Experimental Bit Algorithms Library // \file bit_literal.hpp -// Description: Implementation of bit_array user defined literal +// Description: Implementation of array user defined literal // Creator: Vincent Reverdy // Contributor: Peter McLean [2025] // License: BSD 3-Clause License @@ -20,10 +20,13 @@ namespace bit { +class bit_value; + template -class bit_array; +class array; -class bit_value; +template +using bit_array = array; // ========================================================================== // template @@ -118,7 +121,7 @@ constexpr auto operator""_b() { (((1ull << bits) - 1ull) >= num), "bit literal size prefix has too few bits to represent the given value"); using word_type = bit::ceil_integral; - return bit::bit_array>(static_cast(num)); + return bit::bit_array>(static_cast(num)); } #endif // _BIT_LITERAL_HPP_INCLUDED diff --git a/include/bitlib/bit-containers/bit_span.hpp b/include/bitlib/bit-containers/bit_span.hpp index 346ac854..d7afe202 100644 --- a/include/bitlib/bit-containers/bit_span.hpp +++ b/include/bitlib/bit-containers/bit_span.hpp @@ -11,15 +11,13 @@ #include // Project sources #include "bitlib/bit-algorithms/bit_algorithm.hpp" +#include "bitlib/bit-containers/bit_array_ref.hpp" #include "bitlib/bit-containers/bit_bitsof.hpp" #include "bitlib/bit-containers/bit_policy.hpp" #include "bitlib/bit-iterator/bit.hpp" namespace bit { -template -class bit_array_ref; - // Helper storage: for a fixed extent no runtime size is stored. template struct bit_span_storage { @@ -131,7 +129,7 @@ class bit_span : private bit_span_storage { constexpr bit_span subspan(size_type offset, size_type count = std::dynamic_extent) const noexcept requires(Extent == std::dynamic_extent); - constexpr bit_array_ref operator()(size_type begin, size_type end) const noexcept; + constexpr bit_array_ref operator()(size_type begin, size_type end) const noexcept; template constexpr bit_span first() const noexcept @@ -306,8 +304,8 @@ constexpr bit_span bit_span -constexpr bit_array_ref bit_span::operator()(size_type begin, size_type end) const noexcept { - return bit_array_ref(&(this->begin()[begin]), end - begin); +constexpr bit_array_ref bit_span::operator()(size_type begin, size_type end) const noexcept { + return bit_array_ref(&(this->begin()[begin]), end - begin); } template diff --git a/include/bitlib/bit-containers/bit_vector.hpp b/include/bitlib/bit-containers/bit_vector.hpp index a3fa9285..147a62ea 100644 --- a/include/bitlib/bit-containers/bit_vector.hpp +++ b/include/bitlib/bit-containers/bit_vector.hpp @@ -22,7 +22,6 @@ #include // Project sources #include "bitlib/bit-algorithms/bit_algorithm.hpp" -#include "bitlib/bit-containers/bit_span.hpp" #include "bitlib/bit-iterator/bit.hpp" #include "bitlib/bit_concepts.hpp" diff --git a/include/bitlib/bit_concepts.hpp b/include/bitlib/bit_concepts.hpp index 01f3c14d..027a8a41 100644 --- a/include/bitlib/bit_concepts.hpp +++ b/include/bitlib/bit_concepts.hpp @@ -2,6 +2,7 @@ #define _BIT_CONCEPTS_HPP_INCLUDED #include +#include #include #include diff --git a/test/src/test-array.cpp b/test/src/test-array.cpp index d7b60752..fc532b28 100644 --- a/test/src/test-array.cpp +++ b/test/src/test-array.cpp @@ -14,7 +14,7 @@ */ // Tests the default c'tor. TEST(ArrayTest, DefaultConstructor) { - bit::bit_array barr(bit::bit0); + bit::bit_array<11> barr(bit::bit0); EXPECT_EQ(2, sizeof(barr)); EXPECT_EQ(11u, barr.size()); EXPECT_EQ(bit::bit0, barr[0]); @@ -31,14 +31,14 @@ TEST(ArrayTest, DefaultConstructor) { } TEST(ArrayTest, BitsOf) { - bit::bit_array barr(bit::bit0); + bit::bit_array<11> barr(bit::bit0); EXPECT_EQ(11u, bitsof(barr)); - EXPECT_EQ(11u, bitsof(bit::bit_array())); + EXPECT_EQ(11u, bitsof(bit::bit_array<11>())); } TEST(ArrayTest, BasicIteration) { // <-- LSB, apparently šŸ™„ - bit::bit_array barr("0110_0101_110"); + bit::bit_array<11> barr("0110_0101_110"); int i = 0; for (const auto& bbit : barr) { switch (10 - i++) { @@ -58,7 +58,7 @@ TEST(ArrayTest, BasicIteration) { } TEST(ArrayTest, ZeroSize) { - bit::bit_array barr{}; + bit::bit_array<0> barr{}; std::array foo{}; EXPECT_EQ(sizeof(foo), sizeof(barr)); EXPECT_EQ(0, bitsof(barr)); @@ -66,7 +66,7 @@ TEST(ArrayTest, ZeroSize) { // Test that the default constructor initializes all bits to false. TEST(ArrayTest, DefaultInitialization) { - bit::bit_array ba; + bit::bit_array<8> ba; for (size_t i = 0; i < ba.size(); ++i) { EXPECT_FALSE(ba[i]) << "Bit " << i << " should be false by default"; } @@ -74,7 +74,7 @@ TEST(ArrayTest, DefaultInitialization) { // Test the fill() method. TEST(ArrayTest, FillMethod) { - bit::bit_array ba; + bit::bit_array<10> ba; ba.fill(bit::bit_value(true)); for (size_t i = 0; i < ba.size(); ++i) { EXPECT_TRUE(ba[i]) << "Bit " << i << " should be true after fill(true)"; @@ -87,7 +87,7 @@ TEST(ArrayTest, FillMethod) { // Test element access via operator[] and at(), including out-of-range checking. TEST(ArrayTest, ElementAccess) { - bit::bit_array ba; + bit::bit_array<5> ba; ba.fill(bit::bit_value(false)); ba[2] = bit::bit_value(true); EXPECT_TRUE(ba.at(2)); @@ -96,7 +96,7 @@ TEST(ArrayTest, ElementAccess) { // Test front() and back() member functions. TEST(ArrayTest, FrontBackAccess) { - bit::bit_array ba; + bit::bit_array<4> ba; ba.fill(bit::bit_value(false)); ba.front() = bit::bit_value(true); ba.back() = bit::bit_value(true); @@ -106,7 +106,7 @@ TEST(ArrayTest, FrontBackAccess) { // Test iterator functionality (both non-const and range-based). TEST(ArrayTest, IteratorFunctionality) { - bit::bit_array ba; + bit::bit_array<4> ba; ba.fill(bit::bit_value(false)); int index = 0; for (auto it = ba.begin(); it != ba.end(); ++it) { @@ -121,9 +121,9 @@ TEST(ArrayTest, IteratorFunctionality) { // Test const_iterator functionality. TEST(ArrayTest, ConstIteratorFunctionality) { - bit::bit_array ba; + bit::bit_array<4> ba; ba.fill(bit::bit_value(true)); - const bit::bit_array& const_ba = ba; + const bit::bit_array<4>& const_ba = ba; for (auto it = const_ba.begin(); it != const_ba.end(); ++it) { EXPECT_TRUE(*it); } @@ -131,7 +131,7 @@ TEST(ArrayTest, ConstIteratorFunctionality) { // Test the swap() member function. TEST(ArrayTest, SwapFunctionality) { - bit::bit_array ba1, ba2; + bit::bit_array<4> ba1, ba2; ba1.fill(bit::bit_value(false)); ba2.fill(bit::bit_value(true)); ba1.swap(ba2); @@ -143,18 +143,18 @@ TEST(ArrayTest, SwapFunctionality) { // Test comparison operators (== and !=). TEST(ArrayTest, ComparisonOperators) { - bit::bit_array ba1 = {bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; - bit::bit_array ba2 = {bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; + bit::bit_array<5> ba1 = {bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; + bit::bit_array<5> ba2 = {bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; EXPECT_EQ(ba1, ba2); ba2[2] = bit::bit_value(true); // Change one element EXPECT_NE(ba1, ba2); - bit::bit_array ba3{bit::bit1, bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; + bit::bit_array ba3{bit::bit1, bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; EXPECT_NE(ba1, ba3); } // Test the data() method to access the underlying storage. TEST(ArrayTest, DataAccess) { - bit::bit_array ba; + bit::bit_array<8> ba; ba.fill(bit::bit_value(false)); // Assume data() returns a pointer to a boolean array. uint8_t* data_ptr = ba.data(); @@ -165,18 +165,18 @@ TEST(ArrayTest, DataAccess) { // Test size() and empty() functions. TEST(ArrayTest, SizeAndEmpty) { - bit::bit_array ba_empty; + bit::bit_array<0> ba_empty; EXPECT_EQ(ba_empty.size(), 0); EXPECT_TRUE(ba_empty.empty()); - bit::bit_array ba; + bit::bit_array<5> ba; EXPECT_EQ(ba.size(), 5); EXPECT_FALSE(ba.empty()); } // Test initializer list construction. TEST(ArrayTest, InitializerListConstruction) { - bit::bit_array ba = {bit::bit1, bit::bit0, bit::bit1}; + bit::bit_array<3> ba = {bit::bit1, bit::bit0, bit::bit1}; EXPECT_TRUE(ba[0]); EXPECT_FALSE(ba[1]); EXPECT_TRUE(ba[2]); @@ -184,19 +184,19 @@ TEST(ArrayTest, InitializerListConstruction) { // Test copy constructor and copy assignment operator. TEST(ArrayTest, CopyAndAssignment) { - bit::bit_array ba1 = {bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; - bit::bit_array ba_copy(ba1); + bit::bit_array<5> ba1 = {bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; + bit::bit_array<5> ba_copy(ba1); EXPECT_EQ(ba1, ba_copy); - bit::bit_array ba_assigned; + bit::bit_array<5> ba_assigned; ba_assigned = ba1; EXPECT_EQ(ba1, ba_assigned); } // Test move semantics (move constructor and move assignment), if implemented. TEST(ArrayTest, MoveSemantics) { - bit::bit_array ba1 = {bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; - bit::bit_array ba_moved(std::move(ba1)); + bit::bit_array<5> ba1 = {bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; + bit::bit_array<5> ba_moved(std::move(ba1)); // We test the moved-to container's values. The moved-from object is valid but unspecified. EXPECT_TRUE(ba_moved[0]); EXPECT_FALSE(ba_moved[1]); @@ -204,7 +204,7 @@ TEST(ArrayTest, MoveSemantics) { EXPECT_FALSE(ba_moved[3]); EXPECT_TRUE(ba_moved[4]); - bit::bit_array ba2 = {bit::bit0, bit::bit0, bit::bit0, bit::bit0, bit::bit0}; + bit::bit_array<5> ba2 = {bit::bit0, bit::bit0, bit::bit0, bit::bit0, bit::bit0}; ba2 = std::move(ba_moved); EXPECT_TRUE(ba2[0]); EXPECT_FALSE(ba2[1]); @@ -214,15 +214,15 @@ TEST(ArrayTest, MoveSemantics) { } TEST(ArrayTest, Throws) { - bit::bit_array ba1{bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; + bit::bit_array<5> ba1{bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; EXPECT_THROW(ba1.at(5), std::out_of_range); - bit::bit_array ba2{bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; - bit::bit_array ba3(ba2); + bit::bit_array ba2{bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; + bit::bit_array<5> ba3(ba2); EXPECT_EQ(ba1, ba3); EXPECT_EQ(ba1, ba2); - bit::bit_array ba4{bit::bit1, bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; + bit::bit_array ba4{bit::bit1, bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; - using barr5 = bit::bit_array; // command in template messes up gtest macro + using barr5 = bit::bit_array<5>; // command in template messes up gtest macro EXPECT_THROW(barr5{ba4}, std::invalid_argument) << "Copy constructor must take the same size"; EXPECT_THROW(barr5{bit::bit0}, std::invalid_argument) << "Initializer list must be the correct size"; @@ -245,7 +245,7 @@ TEST(BitArrayDynamicTest, Throws) { // Test Suite for bit::bit_array<> // -// As bit_array<> is dynamic, bitsof will not return the number stored bits +// As array<> is dynamic, bitsof will not return the number stored bits // the class contains a size_t and a pointer TEST(BitArrayDynamicTest, Bitsof) { bit::bit_array<> arr(23, bit::bit1); @@ -278,13 +278,13 @@ TEST(BitArrayDynamicTest, CopyConstructorCopiesContent) { // Test copy constructor and copy assignment operator. TEST(BitArrayDynamicTest, CopyAndAssignment) { - bit::bit_array ba1{bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; - bit::bit_array ba_copy(ba1); + bit::bit_array ba1{bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; + bit::bit_array ba_copy(ba1); EXPECT_EQ(ba1, ba_copy); - bit::bit_array ba2{bit::bit1, bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; + bit::bit_array ba2{bit::bit1, bit::bit1, bit::bit0, bit::bit1, bit::bit0, bit::bit1}; EXPECT_THROW(ba1 = ba2, std::invalid_argument) << "Copy assignment from invalid size should throw"; - EXPECT_THROW(ba1 = ba2(0, 6), std::invalid_argument) << "Assign from bit_sized_range (bit_array_ref) of unequal size should throw"; + EXPECT_THROW(ba1 = ba2(0, 6), std::invalid_argument) << "Assign from bit_sized_range (array_ref) of unequal size should throw"; } TEST(BitArrayDynamicTest, MoveConstructorMovesContent) { @@ -314,7 +314,7 @@ TEST(BitArrayDynamicTest, InitializerListWordTypeConstructorWorks) { // For this test, we assume that the initializer list for WordType initializes the underlying storage. // Here we use two bytes as an example. std::initializer_list init = {0b10101010, 0b01010101}; - bit::bit_array arr(init); + bit::bit_array arr(init); // Assuming each std::uint8_t provides 8 bits, we expect the size to be the number of initializer elements * 8. EXPECT_EQ(arr.size(), init.size() * 8u); // Check that the underlying storage matches the initializer values. @@ -459,12 +459,12 @@ TEST(BitArrayDynamicTest, StringConstructor) { // Test comparison operators (== and !=). TEST(BitArrayDynamicTest, ComparisonOperators) { - bit::bit_array ba1{bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; - bit::bit_array ba2{bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; + bit::bit_array ba1{bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; + bit::bit_array ba2{bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; EXPECT_EQ(ba1, ba2); ba2[2] = bit::bit_value(true); // Change one element EXPECT_NE(ba1, ba2); - bit::bit_array ba3{bit::bit0, bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; + bit::bit_array ba3{bit::bit0, bit::bit1, bit::bit1, bit::bit0, bit::bit0, bit::bit1}; EXPECT_NE(ba1, ba3); } diff --git a/test/src/test-count_leading.cpp b/test/src/test-count_leading.cpp index 0eb85bae..254b4dec 100644 --- a/test/src/test-count_leading.cpp +++ b/test/src/test-count_leading.cpp @@ -11,7 +11,7 @@ #include "gtest/gtest.h" TEST(CountLeading, CountLeadingZeroes) { - auto num = bit::bit_array(bit::bit0); + auto num = bit::bit_array<128>(bit::bit0); auto clz = bit::count_msb(num.begin(), num.end(), bit::bit0); std::cout << "Count leading zeroes: " << clz << std::endl; EXPECT_EQ(clz, 128); diff --git a/test/src/test-to_string.cpp b/test/src/test-to_string.cpp index 8ed62903..65ffc50d 100644 --- a/test/src/test-to_string.cpp +++ b/test/src/test-to_string.cpp @@ -2,10 +2,10 @@ #include #include -#include "bitlib/bit-containers/bit_array.hpp" -#include "bitlib/bit-containers/bit_array_dynamic_extent.hpp" #include "bitlib/bit-algorithms/count.hpp" #include "bitlib/bit-algorithms/to_string.hpp" +#include "bitlib/bit-containers/bit_array.hpp" +#include "bitlib/bit-containers/bit_array_dynamic_extent.hpp" #include "fixtures.hpp" // Third-party libraries From f46007dc615c3b3d3ee5a458b27ab9ec7540837b Mon Sep 17 00:00:00 2001 From: Peter McLean <112978670+Peter-McLean-Altera@users.noreply.github.com> Date: Sun, 6 Jul 2025 01:40:22 -0400 Subject: [PATCH 71/85] Pipecleaning policy and casting issues (#20) * Pipecleaning policy and casting issues * Fix policy/conversion issues * mdspan_accessor was templated incorrectly. bad constructor for bit_reference causes unexpected behavior * More missing policy templating * Correct static assert target class * Disable from_string temporarily as include DAG is misordered with it * Fix to_integral * compile-time array_ref extent. word accessor * Warnings cleanup * Fix exact integral type * Squash more warnings * Prevent some aliasing in the test code which clutters warnings * Avoid unused variable --------- Co-authored-by: Peter McLean --- include/bitlib/bit-algorithms/accumulate.hpp | 3 - .../bitlib/bit-algorithms/bit_algorithm.hpp | 2 +- include/bitlib/bit-algorithms/shift.hpp | 29 +- .../bitlib/bit-algorithms/to_from_string.hpp | 8 +- include/bitlib/bit-containers/bit_array.hpp | 29 +- .../bitlib/bit-containers/bit_array_base.hpp | 19 +- .../bit_array_dynamic_extent.hpp | 75 ++- .../bitlib/bit-containers/bit_array_ref.hpp | 80 +-- .../bit-containers/bit_mdspan_accessor.hpp | 28 +- include/bitlib/bit-containers/bit_policy.hpp | 16 +- include/bitlib/bit-containers/bit_span.hpp | 42 +- include/bitlib/bit-containers/bit_vector.hpp | 3 +- include/bitlib/bit-iterator/bit_details.hpp | 94 ++- include/bitlib/bit-iterator/bit_reference.hpp | 11 +- .../bit-iterator/bit_word_pointer_adapter.hpp | 2 +- test/src/fixtures.hpp | 126 ++-- test/src/test-copy.cpp | 8 +- test/src/test-count.cpp | 29 +- test/src/test-equal.cpp | 8 +- test/src/test-iterator_adapter.cpp | 12 +- test/src/test-mdspan.cpp | 69 +++ test/src/test-move.cpp | 101 ++-- test/src/vector_test.cpp | 547 +++++++++--------- 23 files changed, 740 insertions(+), 601 deletions(-) diff --git a/include/bitlib/bit-algorithms/accumulate.hpp b/include/bitlib/bit-algorithms/accumulate.hpp index f01c0833..62b77ada 100644 --- a/include/bitlib/bit-algorithms/accumulate.hpp +++ b/include/bitlib/bit-algorithms/accumulate.hpp @@ -36,9 +36,7 @@ constexpr auto accumulate( using size_type = typename bit_iterator::size_type; constexpr size_type digits = bitsof(); - size_type total_bits_to_op = distance(first, last); - bool keep_going = true;; if constexpr (initial_sub_word) { size_type sub_digits; if constexpr (forward) { @@ -90,7 +88,6 @@ constexpr auto accumulate_while( BinaryOperationSubword binary_op_subword) { using word_type = typename bit_iterator::word_type; using size_type = typename bit_iterator::size_type; - using difference_type = typename bit_iterator::difference_type; constexpr size_type digits = bitsof(); size_type total_bits_to_op = distance(first, last); diff --git a/include/bitlib/bit-algorithms/bit_algorithm.hpp b/include/bitlib/bit-algorithms/bit_algorithm.hpp index 6266e640..af07236a 100644 --- a/include/bitlib/bit-algorithms/bit_algorithm.hpp +++ b/include/bitlib/bit-algorithms/bit_algorithm.hpp @@ -33,7 +33,7 @@ #include "rotate.hpp" #include "shift.hpp" #include "swap_ranges.hpp" -#include "to_string.hpp" +#include "to_from_string.hpp" #include "transform.hpp" #include "type_traits.hpp" // ========================================================================== // diff --git a/include/bitlib/bit-algorithms/shift.hpp b/include/bitlib/bit-algorithms/shift.hpp index 279877bc..c93da4ea 100644 --- a/include/bitlib/bit-algorithms/shift.hpp +++ b/include/bitlib/bit-algorithms/shift.hpp @@ -98,9 +98,6 @@ bit_iterator shift_left( write_word(new_word, first, d - n); return first + d - n; } - // Multiple word case - word_type first_value = *first.base(); - word_type last_value = !is_last_aligned ? *last.base() : 0; // Align first if (!is_first_aligned) { @@ -286,19 +283,19 @@ bit_iterator shift_right( // Shift bit sequence to the msb if (offset == 0) { - auto new_first = bit::bit_iterator( - STD_SHIFT_RIGHT( - first.base(), - last.base(), - word_shifts), - first.position() - ); - // https://en.cppreference.com/w/cpp/algorithm/shift - // "Elements that are in the original range but not the new range - // are left in a valid but unspecified state." - // - //bit::fill(first, new_first, bit::bit0); - return first + n; + /*auto new_first = */ + static_cast(bit::bit_iterator( + STD_SHIFT_RIGHT( + first.base(), + last.base(), + word_shifts), + first.position())); + // https://en.cppreference.com/w/cpp/algorithm/shift + // "Elements that are in the original range but not the new range + // are left in a valid but unspecified state." + // + //bit::fill(first, new_first, bit::bit0); + return first + n; } if (bit::distance(first, middle) >= digits) diff --git a/include/bitlib/bit-algorithms/to_from_string.hpp b/include/bitlib/bit-algorithms/to_from_string.hpp index faf98b0e..4008e440 100644 --- a/include/bitlib/bit-algorithms/to_from_string.hpp +++ b/include/bitlib/bit-algorithms/to_from_string.hpp @@ -14,8 +14,6 @@ #include "bitlib/bit-algorithms/accumulate.hpp" #include "bitlib/bit-algorithms/count.hpp" -#include "bitlib/bit-containers/array_dynamic_extent.hpp" -#include "bitlib/bit-containers/bit_vector.hpp" #include "bitlib/bit_concepts.hpp" namespace bit { @@ -109,6 +107,9 @@ constexpr std::string to_string(const bit_sized_range auto& bits, std::string pr return to_string(bits.begin(), bits.end(), prefix); } +/* +Commenting this out temporarily as the reference to bit_vector/bit_array messes up include dependency DAG + template , typename RandomAccessIt> constexpr void from_string( Policy, @@ -145,7 +146,7 @@ constexpr bit_vector<> from_string(const char* first, const char* last) { bits += base_bits; } if (bits) { - vec.append_range(array<>(bits, work)); + vec.append_range(bit_array<>(bits, work)); } } return vec; @@ -158,6 +159,7 @@ template constexpr bit_vector<> from_string(const std::string& str) { return from_string(str.c_str(), str.c_str() + str.length()); } +*/ } // namespace bit diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index 23263fc0..a6e87d67 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -39,7 +39,7 @@ template >, T>, - typename Policy = policy::typical> + typename Policy = policy::typical>> class array; template >, - typename Policy = policy::typical> + typename Policy = policy::typical>> using bit_array = array; namespace detail { @@ -68,10 +68,11 @@ struct array_iterator_types { } // namespace detail template -class array : public array_base, T, N, W, Policy, detail::array_iterator_types> { +class array : public array_base, T, N, W, false, Policy, detail::array_iterator_types> { public: - using base = array_base, T, N, W, Policy, detail::array_iterator_types>; + using base = array_base, T, N, W, false, Policy, detail::array_iterator_types>; using base::end; + using base::size; using typename base::const_iterator; using typename base::const_pointer; using typename base::const_reference; @@ -108,13 +109,13 @@ class array : public array_base, T, N, W, Policy, detail::array_i this->from_integral(integral); } - constexpr array(const array& other) noexcept - : storage(other.storage) {} + constexpr array(const array& other) noexcept + : base(), storage(other.storage) {} - constexpr array(const array&& other) noexcept - : storage(other.storage) {} + constexpr array(const array&& other) noexcept + : base(), storage(other.storage) {} - constexpr array(const bit_sized_range auto& other) { + constexpr array(const bit_sized_range auto& other) : base() { if (other.size() != this->size()) [[unlikely]] { throw std::invalid_argument("other bit_range contains an invalid number of bits for array."); } @@ -123,21 +124,21 @@ class array : public array_base, T, N, W, Policy, detail::array_i constexpr array(const std::initializer_list init) requires(!std::is_same_v) - { + : base() { if (init.size() != bitsof(*this)) [[unlikely]] { throw std::invalid_argument("initialize_list contains an invalid number of bits for array."); } std::copy(init.begin(), init.end(), this->begin()); } - constexpr array(const std::initializer_list init) { + constexpr array(const std::initializer_list init) : base() { if (init.size() != bitsof(*this)) [[unlikely]] { throw std::invalid_argument("initialize_list contains an invalid number of bits for array."); } std::copy(init.begin(), init.end(), this->begin()); } - constexpr array(const std::initializer_list init) : storage{} { + constexpr array(const std::initializer_list init) : base(), storage{} { // Make sure we handle the case where init.size() != Words auto it = init.begin(); for (size_type i = 0; i < std::min(Words(N), init.size()); ++i, ++it) { @@ -147,7 +148,7 @@ class array : public array_base, T, N, W, Policy, detail::array_i constexpr array(const std::string_view s) requires(std::is_same_v) - { + : base() { if (bitsof(*this) != static_cast(std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1'))) [[unlikely]] { throw std::invalid_argument("String contains an invalid number of bits for array."); }; @@ -209,7 +210,7 @@ class array : public array_base, T, N, W, Policy, detail::array_i /* * Operations */ - constexpr void swap(array& other) noexcept { + constexpr void swap(array& other) noexcept { std::swap(this->storage, other.storage); } diff --git a/include/bitlib/bit-containers/bit_array_base.hpp b/include/bitlib/bit-containers/bit_array_base.hpp index af75d2bc..0fe78af2 100644 --- a/include/bitlib/bit-containers/bit_array_base.hpp +++ b/include/bitlib/bit-containers/bit_array_base.hpp @@ -28,7 +28,7 @@ namespace bit { -template +template class array_ref; template -class array_base { +template +class array_base : public detail::container_size_storage { protected: constexpr Derived& derived() noexcept { return static_cast(*this); @@ -73,6 +73,11 @@ class array_base { using iterator = Iterators::iterator; using const_iterator = Iterators::const_iterator; + constexpr array_base() noexcept : detail::container_size_storage() {} + constexpr array_base(const size_type& size) noexcept + requires(N == std::dynamic_extent) + : detail::container_size_storage(size) {} + // Element access constexpr reference operator[](size_type pos) { return derived().begin()[pos]; @@ -163,14 +168,14 @@ class array_base { * @brief Slice operations - returns a array_ref */ constexpr auto operator()(size_type offset, size_type right) const noexcept { - return array_ref(&this->at(offset), right - offset); + return array_ref(&this->at(offset), right - offset); } /** * @brief Slice operations - returns a array_ref */ constexpr auto operator()(size_type offset, size_type right) noexcept { - return array_ref(&this->at(offset), right - offset); + return array_ref(&this->at(offset), right - offset); } // Common operations @@ -193,7 +198,7 @@ class array_base { ::bit::copy(derived().begin(), end(), bit_pointer(&integral)); } if (derived().size() < bitsof()) { - Policy::extension::template to_integral(derived(), integral); + Policy::extension::template to_integral(derived(), integral, detail::uninitialized); } } else { if constexpr (N > bitsof()) { @@ -202,7 +207,7 @@ class array_base { ::bit::copy(derived().begin(), end(), bit_pointer(&integral)); } if constexpr (N < bitsof()) { - Policy::extension::template to_integral(derived(), integral); + Policy::extension::template to_integral(derived(), integral, detail::uninitialized); } } diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index 57907fbd..3c334dbc 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -40,10 +40,11 @@ struct array_dextent_iterator_types { } // namespace detail template class array - : public array_base, T, std::dynamic_extent, W, Policy, detail::array_dextent_iterator_types> { + : public array_base, T, std::dynamic_extent, W, false, Policy, detail::array_dextent_iterator_types> { public: - using base = array_base, T, std::dynamic_extent, W, Policy, detail::array_dextent_iterator_types>; + using base = array_base, T, std::dynamic_extent, W, false, Policy, detail::array_dextent_iterator_types>; using base::end; + using base::size; using typename base::const_iterator; using typename base::const_pointer; using typename base::const_reference; @@ -58,11 +59,12 @@ class array private: using word_type_ptr = word_type*; + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wsizeof-pointer-div" static const size_type FixedWords = sizeof(word_type_ptr) / sizeof(word_type); + #pragma GCC diagnostic pop static const size_type FixedBits = FixedWords * bitsof(); - const size_type m_size; - struct Storage { union { word_type_ptr pointer; @@ -152,72 +154,72 @@ class array */ array() = delete; - constexpr array(const size_type size, const Allocator& allocator = Allocator()) - : m_size(size), storage(Words(size), allocator) { + constexpr array(const size_type extent, const Allocator& allocator = Allocator()) + : base(extent), storage(Words(extent), allocator) { } template - constexpr array(const size_type size, const U& integral, const Allocator& allocator = Allocator()) - : m_size(size), storage(Words(size), allocator, detail::uninitialized) { + constexpr array(const size_type extent, const U& integral, const Allocator& allocator = Allocator()) + : base(extent), storage(Words(extent), allocator, detail::uninitialized) { this->from_integral(integral); } - constexpr array(const size_type size, const word_type val, const Allocator& allocator = Allocator()) - : m_size(size), storage(Words(size), val, allocator) { + constexpr array(const size_type extent, const word_type val, const Allocator& allocator = Allocator()) + : base(extent), storage(Words(extent), val, allocator) { } - constexpr array(const size_type size, const value_type bit_val, const Allocator& allocator = Allocator()) + constexpr array(const size_type extent, const value_type bit_val, const Allocator& allocator = Allocator()) requires(!std::is_same::value) - : m_size(size), storage(Words(size), allocator, detail::uninitialized) { + : base(extent), storage(Words(extent), allocator, detail::uninitialized) { this->fill(bit_val); } constexpr array(const array& other) - : m_size(other.size()), storage(Words(size()), other.storage) { + : base(other.size()), storage(Words(size()), other.storage) { } constexpr array(const array& other, const Allocator& allocator) - : m_size(other.size()), storage(Words(size()), other.storage, allocator) { + : base(other.size()), storage(Words(size()), other.storage, allocator) { } constexpr array(const bit_sized_range auto& other, const Allocator& allocator = Allocator()) - : array(other.size(), allocator, detail::uninitialized) { + : base(other.size()), storage(Words(size()), allocator, detail::uninitialized) { ::bit::copy(other.begin(), other.end(), this->begin()); } - constexpr array(array&& other) - : m_size(other.size()), storage(Words(size()), std::move(other.storage)) { + constexpr array(array&& other) + : base(other.size()), storage(Words(size()), std::move(other.storage)) { } - constexpr array(array&& other, const Allocator& allocator) - : m_size(other.size()), storage(Words(size()), std::move(other.storage), allocator) { + constexpr array(array&& other, const Allocator& allocator) + : base(other.size()), storage(Words(size()), std::move(other.storage), allocator) { } constexpr array(const std::initializer_list init, const Allocator& allocator = Allocator()) requires(!std::is_same_v) - : m_size(init.size()), storage(Words(size()), allocator, detail::uninitialized) { + : base(init.size()), storage(Words(size()), allocator, detail::uninitialized) { std::copy(init.begin(), init.end(), this->begin()); } #if 0 No known conversion from bool to bit_value bit_value has explicit constructor from bool to bit_value so this doesnt work - constexpr array::array(const std::initializer_list init) + constexpr array::array(const std::initializer_list init) : storage(std::make_unique(Words(init.size()))), - m_size(init.size()) { + base(init.size()) { std::copy(init.begin(), init.end(), this->begin()); } #endif template constexpr array(const std::initializer_list init, const Allocator& allocator = Allocator()) - : m_size(bitsof() * init.size()), storage(Words(size()), allocator, detail::uninitialized) { + : base(bitsof() * init.size()), storage(Words(size()), allocator, detail::uninitialized) { std::copy(init.begin(), init.end(), data()); } constexpr array(const std::string_view s, const Allocator& allocator = Allocator()) requires(std::is_same_v) - : m_size(std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1')), storage(Words(size()), allocator, detail::uninitialized) { + : base(std::count(s.begin(), s.end(), '0') + std::count(s.begin(), s.end(), '1')), storage(Words(size()), allocator, detail::uninitialized) { size_type i = 0; for (char c : s) { if (c == '0') { @@ -231,9 +233,9 @@ class array /* * Assignment */ - constexpr array& operator=(const array& other) { + constexpr array& operator=(const array& other) { if (nullptr == data() || size() != other.size()) { - throw std::invalid_argument("Cannot reassign array size"); + throw std::invalid_argument("Cannot reassign array extent"); } if (this == &other) [[unlikely]] { return *this; @@ -242,7 +244,7 @@ class array return *this; } - constexpr array& operator=(const bit_sized_range auto& other) { + constexpr array& operator=(const bit_sized_range auto& other) { if (other.size() != this->size()) [[unlikely]] { throw std::invalid_argument("other bit_range contains an invalid number of bits for array."); } @@ -250,11 +252,11 @@ class array return *this; }; - constexpr array& operator=(array&& other) { + constexpr array& operator=(array&& other) { if (nullptr == data() || size() != other.size()) { - throw std::invalid_argument("Cannot reassign array size"); + throw std::invalid_argument("Cannot reassign array extent"); } - array temp(std::move(other)); + array temp(std::move(other)); swap(temp); return *this; } @@ -297,17 +299,10 @@ class array } } - /* - * Capacity - */ - constexpr size_type size() const noexcept { - return m_size; - } - /* * Operations */ - constexpr void swap(array& other) noexcept { + constexpr void swap(array& other) noexcept { assert(size() == other.size()); if (size() > FixedBits) { std::swap(this->storage.pointer, other.storage.pointer); @@ -319,8 +314,8 @@ class array } }; -static_assert(bit_range>, "array<> does not satisfy bit_contiguous_range concept!"); -static_assert(bit_sized_range>, "array<> does not satisfy bit_contiguous_sized_range concept!"); +static_assert(bit_range>, "array<> does not satisfy bit_contiguous_range concept!"); +static_assert(bit_sized_range>, "array<> does not satisfy bit_contiguous_sized_range concept!"); #ifdef CONTIGUOUS_RANGE static_assert(bit_contiguous_range>, "array<> does not satisfy bit_contiguous_range concept!"); static_assert(bit_contiguous_sized_range>, "array<> does not satisfy bit_contiguous_sized_range concept!"); diff --git a/include/bitlib/bit-containers/bit_array_ref.hpp b/include/bitlib/bit-containers/bit_array_ref.hpp index 5706b6e0..796858a4 100644 --- a/include/bitlib/bit-containers/bit_array_ref.hpp +++ b/include/bitlib/bit-containers/bit_array_ref.hpp @@ -25,11 +25,11 @@ namespace bit { -template > +template >> class array_ref; -template > -using bit_array_ref = array_ref; +template >> +using bit_array_ref = array_ref; namespace detail { template @@ -48,12 +48,13 @@ struct array_ref_iterator_types { * @tparam T The value type (typically bit_value) * @tparam W The word type used for storage */ -template +template class array_ref - : public array_base, T, std::dynamic_extent, W, Policy, detail::array_ref_iterator_types> { + : public array_base, T, N, W, false, Policy, detail::array_ref_iterator_types> { public: - using base = array_base, T, std::dynamic_extent, W, Policy, detail::array_ref_iterator_types>; + using base = array_base, T, N, W, false, Policy, detail::array_ref_iterator_types>; using base::end; + using base::size; using typename base::const_iterator; using typename base::const_pointer; using typename base::const_reference; @@ -67,7 +68,6 @@ class array_ref private: const bit_pointer m_storage; - const size_type m_size; public: // Constructors @@ -77,22 +77,30 @@ class array_ref * @brief Constructs a non-owning reference to a bit array * * @param storage Pointer to the storage - * @param size Number of bits + * @param extent Number of bits */ - constexpr array_ref(word_type* storage, size_type size) - : m_storage(storage), - m_size(size) { + constexpr array_ref(word_type* storage, size_type extent) + requires(N == std::dynamic_extent) + : base(extent), m_storage(storage) { + } + constexpr array_ref(word_type* storage) + requires(N != std::dynamic_extent) + : base(), m_storage(storage) { } /** * @brief Constructs a non-owning reference to a bit array using a bit_pointer * * @param storage bit_pointer to the storage - * @param size Number of bits + * @param extent Number of bits */ - constexpr array_ref(bit_pointer storage, size_type size) - : m_storage(storage), - m_size(size) { + constexpr array_ref(bit_pointer storage, size_type extent) + requires(N == std::dynamic_extent) + : base(extent), m_storage(storage) { + } + constexpr array_ref(bit_pointer storage) + requires(N != std::dynamic_extent) + : base(), m_storage(storage) { } /** @@ -100,10 +108,15 @@ class array_ref * * @param other bit_sized_range */ - constexpr array_ref(bit_range auto& other, size_type size) - : m_storage(&(*other.begin())), - m_size(size) { - assert(size <= (other.end() - other.begin())); + constexpr array_ref(bit_range auto& other, size_type extent) + requires(N == std::dynamic_extent) + : base(extent), m_storage(&(*other.begin())) { + assert(extent <= static_cast(other.end() - other.begin())); + } + constexpr array_ref(bit_range auto& other) + requires(N != std::dynamic_extent) + : base(), m_storage(&(*other.begin())) { + assert(N <= static_cast(other.end() - other.begin())); } /** @@ -111,11 +124,15 @@ class array_ref * * @param other bit_sized_range */ - constexpr array_ref(const bit_range auto& other, size_type size) - requires(std::is_const_v) - : m_storage(&(*other.begin())), - m_size(size) { - assert(size <= (other.end() - other.begin())); + constexpr array_ref(const bit_range auto& other, size_type extent) + requires((N == std::dynamic_extent) && std::is_const_v) + : base(extent), m_storage(&(*other.begin())) { + assert(extent <= (other.end() - other.begin())); + } + constexpr array_ref(const bit_range auto& other) + requires((N != std::dynamic_extent) && std::is_const_v) + : base(), m_storage(&(*other.begin())) { + assert(N <= (other.end() - other.begin())); } /** @@ -132,7 +149,7 @@ class array_ref * @brief Range Assignment operator - copies content but doesn't rebind */ constexpr array_ref& operator=(const bit_sized_range auto& other) { - if (m_size != other.size()) { + if (size() != other.size()) { throw std::invalid_argument("Cannot assign from array_ref of different size"); } ::bit::copy(other.begin(), other.end(), this->begin()); @@ -144,7 +161,7 @@ class array_ref */ constexpr array_ref& operator=(const array_ref& other) { if (this != &other) { - if (m_size != other.m_size) { + if (size() != other.size()) { throw std::invalid_argument("Cannot assign from array_ref of different size"); } ::bit::copy(other.begin(), other.end(), this->begin()); @@ -157,7 +174,7 @@ class array_ref */ constexpr array_ref& operator=(array_ref&& other) { if (this != &other) { - if (m_size != other.size()) { + if (size() != other.size()) { throw std::invalid_argument("Cannot assign from array_ref of different size"); } ::bit::copy(other.begin(), other.end(), this->begin()); @@ -181,18 +198,11 @@ class array_ref return const_iterator(m_storage); } - /* - * Capacity - */ - constexpr size_type size() const noexcept { - return m_size; - } - /* * Operations */ constexpr void swap(array_ref& other) { - if (m_size != other.m_size) { + if (size() != other.size()) { throw std::invalid_argument("Cannot swap array_ref of different sizes"); } swap_ranges(begin(), end(), other.begin()); diff --git a/include/bitlib/bit-containers/bit_mdspan_accessor.hpp b/include/bitlib/bit-containers/bit_mdspan_accessor.hpp index a948a117..9b8cd82b 100644 --- a/include/bitlib/bit-containers/bit_mdspan_accessor.hpp +++ b/include/bitlib/bit-containers/bit_mdspan_accessor.hpp @@ -18,7 +18,7 @@ template struct bit_default_accessor { using element_type = bit_value; using data_handle_type = bit_pointer; - using reference = bit_reference; + using reference = bit_reference; using offset_policy = bit_default_accessor; constexpr reference access(data_handle_type p, std::size_t i) const noexcept { return p[i]; @@ -27,6 +27,32 @@ struct bit_default_accessor { return p + i; } }; + +template +struct bit_word_accessor : private detail::container_size_storage { + using element_type = bit_array; + using data_handle_type = bit_pointer; + using reference = bit_array_ref; + using offset_policy = bit_word_accessor; + constexpr reference access(data_handle_type p, std::size_t i) const noexcept + requires(N == std::dynamic_extent) + { + return reference(p + i * this->size(), this->size()); + } + constexpr reference access(data_handle_type p, std::size_t i) const noexcept + requires(N != std::dynamic_extent) + { + return reference(p + i * this->size()); + } + constexpr data_handle_type offset(data_handle_type p, std::size_t i) const noexcept { + return p + i * this->size(); + } + constexpr bit_word_accessor() : detail::container_size_storage() {} + constexpr bit_word_accessor(const size_t& size) + requires(N == std::dynamic_extent) + : detail::container_size_storage(size) {} +}; + } // namespace bit #endif diff --git a/include/bitlib/bit-containers/bit_policy.hpp b/include/bitlib/bit-containers/bit_policy.hpp index 03943f0e..844ecd93 100644 --- a/include/bitlib/bit-containers/bit_policy.hpp +++ b/include/bitlib/bit-containers/bit_policy.hpp @@ -65,11 +65,15 @@ struct sign_extend { bit_pointer integral_begin(&integral); if constexpr (N == std::dynamic_extent) { if constexpr (std::is_signed_v) { - ::bit::fill(integral_begin + value.size(), integral_begin + bitsof(), value.last()[-1]); + ::bit::fill(integral_begin + value.size(), integral_begin + bitsof(), value.end()[-1]); + } else { + ::bit::fill(integral_begin + value.size(), integral_begin + bitsof(), bit0); } } else { if constexpr (std::is_signed_v) { - ::bit::fill(integral_begin + N, integral_begin + bitsof(), value.begin()[N - 1]); + ::bit::fill(integral_begin + value.size(), integral_begin + bitsof(), value.begin()[N - 1]); + } else { + ::bit::fill(integral_begin + value.size(), integral_begin + bitsof(), bit0); } } } @@ -92,13 +96,9 @@ struct sign_extend { template constexpr static void from_integral(bit_sized_range auto& value, const U& integral, detail::uninitialized_t) noexcept { if constexpr (N == std::dynamic_extent) { - if constexpr (std::is_signed_v) { - ::bit::fill(value.begin() + bitsof(), value.end(), (integral < 0) ? bit1 : bit0); - } + ::bit::fill(value.begin() + bitsof(), value.end(), (integral < 0) ? bit1 : bit0); } else { - if constexpr (std::is_signed_v) { - ::bit::fill(value.begin() + N, value.end(), (integral < 0) ? bit1 : bit0); - } + ::bit::fill(value.begin() + N, value.end(), (integral < 0) ? bit1 : bit0); } } }; diff --git a/include/bitlib/bit-containers/bit_span.hpp b/include/bitlib/bit-containers/bit_span.hpp index d7afe202..7eaf466b 100644 --- a/include/bitlib/bit-containers/bit_span.hpp +++ b/include/bitlib/bit-containers/bit_span.hpp @@ -34,7 +34,7 @@ struct bit_span_storage { bit_span_storage(std::size_t size) : size_(size) {} }; -template > +template >> class bit_span : private bit_span_storage { public: using word_type = WordType; @@ -125,22 +125,22 @@ class bit_span : private bit_span_storage { // Creates a subspan starting at offset with count bits. template - constexpr auto subspan() const noexcept -> bit_span()>; - constexpr bit_span subspan(size_type offset, size_type count = std::dynamic_extent) const noexcept + constexpr auto subspan() const noexcept -> bit_span(), Policy>; + constexpr bit_span subspan(size_type offset, size_type count = std::dynamic_extent) const noexcept requires(Extent == std::dynamic_extent); - constexpr bit_array_ref operator()(size_type begin, size_type end) const noexcept; + constexpr bit_array_ref operator()(size_type begin, size_type end) const noexcept; template - constexpr bit_span first() const noexcept + constexpr bit_span first() const noexcept requires(NewExtent != std::dynamic_extent); - constexpr bit_span first(size_type offset) const noexcept + constexpr bit_span first(size_type offset) const noexcept requires(Extent == std::dynamic_extent); template - constexpr bit_span last() const noexcept + constexpr bit_span last() const noexcept requires(RevOffset != std::dynamic_extent); - constexpr bit_span last(size_type offset) const noexcept + constexpr bit_span last(size_type offset) const noexcept requires(Extent == std::dynamic_extent); }; @@ -284,8 +284,8 @@ constexpr bit_span::iterator bit_span template constexpr auto bit_span::subspan() const noexcept - -> bit_span()> { - using NewSpanType = bit_span()>; + -> bit_span(), Policy> { + using NewSpanType = bit_span(), Policy>; if constexpr (NewExtent == std::dynamic_extent) { return NewSpanType(begin() + NewOffset, size() - NewOffset); @@ -296,43 +296,43 @@ constexpr auto bit_span::subspan() const noexcept // Creates a subspan starting at offset with count bits. template -constexpr bit_span bit_span::subspan(size_type offset, size_type count) const noexcept +constexpr bit_span bit_span::subspan(size_type offset, size_type count) const noexcept requires(Extent == std::dynamic_extent) { size_type new_count = (count == std::dynamic_extent) ? size() - offset : count; - return bit_span(begin() + offset, new_count); + return bit_span(begin() + offset, new_count); } template -constexpr bit_array_ref bit_span::operator()(size_type begin, size_type end) const noexcept { - return bit_array_ref(&(this->begin()[begin]), end - begin); +constexpr bit_array_ref bit_span::operator()(size_type begin, size_type end) const noexcept { + return bit_array_ref(&(this->begin()[begin]), end - begin); } template template -constexpr bit_span bit_span::first() const noexcept +constexpr bit_span bit_span::first() const noexcept requires(NewExtent != std::dynamic_extent) { - return bit_span(data_); + return bit_span(data_); } template -constexpr bit_span bit_span::first(size_type offset) const noexcept +constexpr bit_span bit_span::first(size_type offset) const noexcept requires(Extent == std::dynamic_extent) { - return bit_span(begin() + offset); + return bit_span(begin() + offset); } template template -constexpr bit_span bit_span::last() const noexcept +constexpr bit_span bit_span::last() const noexcept requires(RevOffset != std::dynamic_extent) { - return bit_span(begin() + size() - RevOffset); + return bit_span(begin() + size() - RevOffset); } template -constexpr bit_span bit_span::last(size_type offset) const noexcept +constexpr bit_span bit_span::last(size_type offset) const noexcept requires(Extent == std::dynamic_extent) { } diff --git a/include/bitlib/bit-containers/bit_vector.hpp b/include/bitlib/bit-containers/bit_vector.hpp index 147a62ea..cb5703d2 100644 --- a/include/bitlib/bit-containers/bit_vector.hpp +++ b/include/bitlib/bit-containers/bit_vector.hpp @@ -22,8 +22,9 @@ #include // Project sources #include "bitlib/bit-algorithms/bit_algorithm.hpp" -#include "bitlib/bit-iterator/bit.hpp" #include "bitlib/bit_concepts.hpp" +#include "bitlib/bit-containers/bit_array_ref.hpp" +#include "bitlib/bit-iterator/bit.hpp" // Third-party libraries // Miscellaneous diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index bd602267..cac7b81f 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -286,12 +286,12 @@ using _wider_type_t = typename _wider_type::type; #endif /* -exact_ceil_integral is used to determine the exact integral type that a proxy reference +exact_floor_integral is used to determine the exact integral type that a proxy reference can be implicitly converted to. -If the proxy reference supports multiple types, it will pick the largest, preferring unsigned. +If the proxy reference supports multiple types, it will pick the smallest, preferring unsigned. */ template -struct exact_ceil_integral { +struct exact_floor_integral { private: using U = std::remove_cvref_t; @@ -308,28 +308,29 @@ struct exact_ceil_integral { } public: - using type = std::conditional_t< - is_exactly_convertible(), uint64_t, + using type = std::conditional_t< - is_exactly_convertible(), int64_t, + is_exactly_convertible(), uint8_t, std::conditional_t< - is_exactly_convertible(), uint32_t, + is_exactly_convertible(), int8_t, std::conditional_t< - is_exactly_convertible(), int32_t, + is_exactly_convertible(), uint16_t, std::conditional_t< - is_exactly_convertible(), uint16_t, + is_exactly_convertible(), int16_t, std::conditional_t< - is_exactly_convertible(), int16_t, + is_exactly_convertible(), uint32_t, std::conditional_t< - is_exactly_convertible(), uint8_t, + is_exactly_convertible(), int32_t, std::conditional_t< - is_exactly_convertible(), int8_t, - void>>>>>>>>; + is_exactly_convertible(), uint64_t, + std::conditional_t< + is_exactly_convertible(), int64_t, + void>>>>>>>>; }; // Helper alias template -using exact_ceil_integral_t = typename exact_ceil_integral::type; +using exact_floor_integral_t = typename exact_floor_integral::type; /* ******************* IMPLEMENTATION DETAILS: UTILITIES ******************** */ // Assertions @@ -449,8 +450,10 @@ constexpr T lsr(const T val, const size_type shift) { } template -constexpr exact_ceil_integral_t lsr(const T val, const size_type shift) { - return static_cast>(static_cast>>(val) >> shift); +constexpr exact_floor_integral_t lsr(const T val, const size_type shift) { + static_assert(!std::is_same_v, void>, + "Type T must be convertible to an integral type"); + return static_cast>(static_cast>>(val) >> shift); } enum class _mask_len { @@ -804,32 +807,32 @@ constexpr T _bitswap() noexcept { // Replaces bits of src0 by the ones of src1 where the mask is true template -constexpr exact_ceil_integral_t _bitblend( +constexpr exact_floor_integral_t _bitblend( const T src0_, const U src1_, - const exact_ceil_integral_t msk) noexcept - requires(std::is_same_v, exact_ceil_integral_t>) + const exact_floor_integral_t msk) noexcept + requires(std::is_same_v, exact_floor_integral_t>) { - static_assert(binary_digits>::value, ""); - const exact_ceil_integral_t src0 = static_cast>(src0_); - const exact_ceil_integral_t src1 = static_cast>(src1_); + static_assert(binary_digits>::value, ""); + const exact_floor_integral_t src0 = static_cast>(src0_); + const exact_floor_integral_t src1 = static_cast>(src1_); return src0 ^ ((src0 ^ src1) & msk); } // Replaces len bits of src0 by the ones of src1 starting at start template -constexpr exact_ceil_integral_t _bitblend( +constexpr exact_floor_integral_t _bitblend( const T src0_, const U src1_, - const exact_ceil_integral_t start, - const exact_ceil_integral_t len) noexcept - requires(std::is_same_v, exact_ceil_integral_t>) + const exact_floor_integral_t start, + const exact_floor_integral_t len) noexcept + requires(std::is_same_v, exact_floor_integral_t>) { - static_assert(binary_digits>::value, ""); - constexpr exact_ceil_integral_t digits = bitsof>(); - const exact_ceil_integral_t src0 = static_cast>(src0_); - const exact_ceil_integral_t src1 = static_cast>(src1_); - const exact_ceil_integral_t msk = _mask, _mask_len::unknown>(len) << start; + static_assert(binary_digits>::value, ""); + constexpr exact_floor_integral_t digits = bitsof>(); + const exact_floor_integral_t src0 = static_cast>(src0_); + const exact_floor_integral_t src1 = static_cast>(src1_); + const exact_floor_integral_t msk = _mask, _mask_len::unknown>(len) << start; return src0 ^ ((src0 ^ src1) & msk * (start < digits)); } // -------------------------------------------------------------------------- // @@ -866,7 +869,6 @@ template constexpr void _bitexch(T& src0, T& src1, S start0, S start1, S len) noexcept { static_assert(binary_digits::value, ""); - constexpr auto digits = binary_digits::value; const T msk = _mask(len); if (start0 >= start1) { src0 = src0 ^ ( @@ -1112,6 +1114,34 @@ struct uninitialized_t { }; inline constexpr uninitialized_t uninitialized{}; +template +struct container_size_storage { + constexpr size_type size() const noexcept { + return Extent; + } + + constexpr container_size_storage() noexcept {} +}; + +template +struct container_size_storage { + using maybe_const_size_type = std::conditional_t>; + + maybe_const_size_type size_; + constexpr size_type size() const noexcept { + return size_; + } + constexpr void resize(const size_type& new_size) + requires(resizeable) + { + size_ = new_size; + } + + constexpr container_size_storage() noexcept : size_() {} + constexpr container_size_storage(const size_type& size) noexcept + : size_(size) {} +}; + } // namespace detail // ========================================================================== // diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index 55fc4bf7..6a706c6a 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -49,11 +49,6 @@ class bit_reference { // Lifecycle public: - template - requires(std::is_const_v> == std::is_const_v>) - constexpr bit_reference(const bit_reference& other) noexcept - : _ref(other._ref), _mask(other._mask) { - } constexpr bit_reference(const bit_reference& other) noexcept; explicit constexpr bit_reference(WordRef ref) noexcept; constexpr bit_reference(WordRef ref, size_type pos); @@ -80,6 +75,7 @@ class bit_reference { // Access public: constexpr bit_pointer operator&() const noexcept; + constexpr bit_pointer operator&() noexcept; // Swap members public: @@ -227,6 +223,11 @@ template constexpr bit_pointer> bit_reference::operator&() const noexcept { return bit_pointer(&_ref, _tzcnt(_mask)); } + +template +constexpr bit_pointer> bit_reference::operator&() noexcept { + return bit_pointer(&_ref, _tzcnt(_mask)); +} // -------------------------------------------------------------------------- // diff --git a/include/bitlib/bit-iterator/bit_word_pointer_adapter.hpp b/include/bitlib/bit-iterator/bit_word_pointer_adapter.hpp index 797fcd1e..d9c207a6 100644 --- a/include/bitlib/bit-iterator/bit_word_pointer_adapter.hpp +++ b/include/bitlib/bit-iterator/bit_word_pointer_adapter.hpp @@ -202,7 +202,7 @@ constexpr auto operator-( auto main = (lhs._source - rhs._source); return main * static_cast>(bit_word_pointer_adapter::ratio) + (static_cast(lhs._index) - static_cast(rhs._index)); } else { - // ā€œsmall→largeā€ mode: each base‐step is 1 small word, but difference is in big words: + // "small→large" mode: each base‐step is 1 small word, but difference is in big words: auto small_diff = (lhs._source - rhs._source); return small_diff / static_cast>(bit_word_pointer_adapter::ratio); } diff --git a/test/src/fixtures.hpp b/test/src/fixtures.hpp index 099d038f..b2d6c36b 100644 --- a/test/src/fixtures.hpp +++ b/test/src/fixtures.hpp @@ -46,11 +46,10 @@ class VectorTest : public testing::Test { std::vector random_bitvecs; std::vector> random_boolvecs; std::vector random_vec; - const size_t word_size = 4; - const size_t digits = bit::binary_digits::value; - const size_t bit_size = word_size * digits; - void SetUp() override { + const size_t digits = bit::binary_digits::value; + const size_t word_size = 4; + const size_t bit_size = word_size * digits; empty_vec = vec_type(); random_vec = get_random_vec(word_size); for (size_t cont_size = 1; cont_size < bit_size; ++cont_size) { @@ -79,53 +78,53 @@ TYPED_TEST_SUITE(VectorTest, BaseTypes); template class SingleRangeTest : public testing::Test { - protected: + protected: + using base_type = WordType; - using base_type = WordType; + std::vector> random_bitvecs; + std::vector> random_boolvecs; + std::vector random_vec; - std::vector> random_bitvecs; - std::vector> random_boolvecs; - std::vector random_vec; + protected: + void SetUp() override { + const size_t digits = bit::binary_digits::value; size_t word_size = 4; - size_t digits = bit::binary_digits::value; - size_t bit_size = word_size*digits; - - void SetUp() override { - random_vec = get_random_vec(word_size); - for (size_t cont_size = 1; cont_size < bit_size; ++cont_size) { - auto bitvec = bit::bit_vector(bit_size); - std::copy(random_vec.begin(), random_vec.end(), bitvec.begin().base()); - bitvec.resize(cont_size); - - auto boolvec = boolvec_from_bitvec(bitvec); - random_bitvecs.push_back(bitvec); - random_boolvecs.push_back(boolvec); - } - word_size = 2*64*64; - random_vec = get_random_vec(word_size); - size_t bit_size = (word_size)*digits; - for (size_t cont_size = bit_size - digits - 4; cont_size < bit_size - digits + 4; ++cont_size) { - auto bitvec = bit::bit_vector(bit_size); - std::copy(random_vec.begin(), random_vec.end(), bitvec.begin().base()); - bitvec.resize(cont_size); + size_t bit_size = word_size * digits; + random_vec = get_random_vec(word_size); + for (size_t cont_size = 1; cont_size < bit_size; ++cont_size) { + auto bitvec = bit::bit_vector(bit_size); + std::copy(random_vec.begin(), random_vec.end(), bitvec.begin().base()); + bitvec.resize(cont_size); - auto boolvec = boolvec_from_bitvec(bitvec); - random_bitvecs.push_back(bitvec); - random_boolvecs.push_back(boolvec); - } + auto boolvec = boolvec_from_bitvec(bitvec); + random_bitvecs.push_back(bitvec); + random_boolvecs.push_back(boolvec); + } + word_size = 2 * 64 * 64; + bit_size = word_size * digits; + random_vec = get_random_vec(word_size); + for (size_t cont_size = bit_size - digits - 4; cont_size < bit_size - digits + 4; ++cont_size) { + auto bitvec = bit::bit_vector(bit_size); + std::copy(random_vec.begin(), random_vec.end(), bitvec.begin().base()); + bitvec.resize(cont_size); - auto zeros = bit::bit_vector(bit_size); - std::fill(zeros.begin(), zeros.end(), bit::bit0); - *(zeros.end() - 1024 - digits - 4) = bit::bit1; - random_bitvecs.push_back(zeros); - random_boolvecs.push_back(boolvec_from_bitvec(zeros)); - - auto ones = bit::bit_vector(bit_size); - std::fill(ones.begin(), ones.end(), bit::bit1); - *(ones.end() - 1024 - digits - 4) = bit::bit0; - random_bitvecs.push_back(ones); - random_boolvecs.push_back(boolvec_from_bitvec(ones)); + auto boolvec = boolvec_from_bitvec(bitvec); + random_bitvecs.push_back(bitvec); + random_boolvecs.push_back(boolvec); } + + auto zeros = bit::bit_vector(bit_size); + std::fill(zeros.begin(), zeros.end(), bit::bit0); + *(zeros.end() - 1024 - digits - 4) = bit::bit1; + random_bitvecs.push_back(zeros); + random_boolvecs.push_back(boolvec_from_bitvec(zeros)); + + auto ones = bit::bit_vector(bit_size); + std::fill(ones.begin(), ones.end(), bit::bit1); + *(ones.end() - 1024 - digits - 4) = bit::bit0; + random_bitvecs.push_back(ones); + random_boolvecs.push_back(boolvec_from_bitvec(ones)); + } }; TYPED_TEST_SUITE(SingleRangeTest, BaseTypes); @@ -141,24 +140,25 @@ class DoubleRangeTest : public testing::Test { std::vector> random_boolvecs2; std::vector random_vec; std::vector random_vec_big; - size_t digits = bit::binary_digits::value; - size_t word_size = 4; - size_t bit_size = word_size*bit::binary_digits::value; - size_t big_size = 64*64*2; void SetUp() override { - // TODO this is ugly, need to refactor - random_vec = get_random_vec(word_size); - random_vec_big = get_random_vec(big_size); - for (size_t cont_size = 1; cont_size < bit_size; ++cont_size) { - auto bitvec = bit::bit_vector(bit_size); - std::copy(random_vec.begin(), random_vec.end(), bitvec.begin().base()); - bitvec.resize(cont_size); - - auto boolvec = boolvec_from_bitvec(bitvec); - random_bitvecs1.push_back(bitvec); - random_boolvecs1.push_back(boolvec); - } + const size_t word_size = 4; + const size_t big_size = 64 * 64 * 2; + const size_t digits = bit::binary_digits::value; + const size_t bit_size = word_size * digits; + + // TODO this is ugly, need to refactor + random_vec = get_random_vec(word_size); + random_vec_big = get_random_vec(big_size); + for (size_t cont_size = 1; cont_size < bit_size; ++cont_size) { + auto bitvec = bit::bit_vector(bit_size); + std::copy(random_vec.begin(), random_vec.end(), bitvec.begin().base()); + bitvec.resize(cont_size); + + auto boolvec = boolvec_from_bitvec(bitvec); + random_bitvecs1.push_back(bitvec); + random_boolvecs1.push_back(boolvec); + } for (int i = -4; i < 4; ++i) { size_t cont_size = (big_size-1)*digits + i; auto bitvec = bit::bit_vector(big_size*digits); @@ -199,9 +199,7 @@ class MixedDoubleRangeTest : public testing::Test { using FromWordType = typename FromToPair::first_type; using ToWordType = typename FromToPair::second_type; - static constexpr size_t digits = bit::binary_digits::value; static constexpr size_t word_size = 4; - static constexpr size_t bit_size = word_size * digits; static constexpr size_t big_size = 64 * 64 * 2; // test data @@ -231,7 +229,7 @@ class MixedDoubleRangeTest : public testing::Test { bitvecs.push_back(bv); boolvecs.push_back(boolvec_from_bitvec(bv)); } - // ā€œbigā€ runs around (big_size-1)*digits +/-4 + // "big" runs around (big_size-1)*digits +/-4 const auto max_digits = std::max(bit::bitsof(), bit::bitsof()); for (int i = -4; i < 4; ++i) { size_t cont_size = big_bit_size - max_digits + i; diff --git a/test/src/test-copy.cpp b/test/src/test-copy.cpp index f1b3187b..045fe743 100644 --- a/test/src/test-copy.cpp +++ b/test/src/test-copy.cpp @@ -74,11 +74,9 @@ TYPED_TEST(DoubleRangeTest, Copy) { TYPED_TEST(MixedDoubleRangeTest, Copy) { for (size_t idx = 0; idx < this->random_bitvecs1.size(); ++idx) { - using FromWordType = typename TestFixture::FromWordType; - using ToWordType = typename TestFixture::ToWordType; - bit::bit_vector& bitvec1 = this->random_bitvecs1[idx]; - bit::bit_vector& bitvec2 = this->random_bitvecs2[idx]; - constexpr auto min_digits = std::min(bit::bitsof(), bit::bitsof()); + bit::bit_vector& bitvec1 = this->random_bitvecs1[idx]; + bit::bit_vector& bitvec2 = this->random_bitvecs2[idx]; + constexpr auto min_digits = std::min(bit::bitsof(), bit::bitsof()); std::vector& boolvec1 = this->random_boolvecs1[idx]; std::vector& boolvec2 = this->random_boolvecs2[idx]; long long start1 = generate_random_number( diff --git a/test/src/test-count.cpp b/test/src/test-count.cpp index bb0ad072..8dca0004 100644 --- a/test/src/test-count.cpp +++ b/test/src/test-count.cpp @@ -1,11 +1,10 @@ // ============================== COUNT TESTS =============================== // // Project: The Experimental Bit Algorithms Library -// Description: Tests for count algorithms +// Description: Tests for count algorithms // Contributor(s): Bryce Kille // License: BSD 3-Clause License // ========================================================================== // - // ============================== PREAMBLE ================================== // // C++ standard library #include @@ -20,20 +19,20 @@ // ========================================================================== // TYPED_TEST(SingleRangeTest, Count) { - for (size_t idx = 0; idx < this->bit_size - 1; ++idx) { - bit::bit_vector bitvec = this->random_bitvecs[idx]; - std::vector boolvec = this->random_boolvecs[idx]; - size_t start_count = 16; - while (start_count--) { - unsigned long long start = generate_random_number(0, std::min(bitvec.size() - 1, 16)); - auto bitret = bit::count(bitvec.begin() + start, bitvec.end(), bit::bit1); - auto boolret = std::count(boolvec.begin() + start, boolvec.end(), true); - EXPECT_EQ(bitret, boolret); - bitret = bit::count(bitvec.begin() + start, bitvec.end(), bit::bit0); - boolret = std::count(boolvec.begin() + start, boolvec.end(), false); - EXPECT_EQ(bitret, boolret); - } + for (size_t idx = 0; idx < this->random_bitvecs.size(); ++idx) { + bit::bit_vector bitvec = this->random_bitvecs[idx]; + std::vector boolvec = this->random_boolvecs[idx]; + size_t start_count = 16; + while (start_count--) { + unsigned long long start = generate_random_number(0, std::min(bitvec.size() - 1, 16)); + auto bitret = bit::count(bitvec.begin() + start, bitvec.end(), bit::bit1); + auto boolret = std::count(boolvec.begin() + start, boolvec.end(), true); + EXPECT_EQ(bitret, boolret); + bitret = bit::count(bitvec.begin() + start, bitvec.end(), bit::bit0); + boolret = std::count(boolvec.begin() + start, boolvec.end(), false); + EXPECT_EQ(bitret, boolret); } + } } diff --git a/test/src/test-equal.cpp b/test/src/test-equal.cpp index 2a842aa5..01355f3a 100644 --- a/test/src/test-equal.cpp +++ b/test/src/test-equal.cpp @@ -77,11 +77,9 @@ TYPED_TEST(DoubleRangeTest, Equal) { TYPED_TEST(MixedDoubleRangeTest, Equal) { for (size_t idx = 0; idx < this->random_bitvecs1.size(); ++idx) { - using FromWordType = typename TestFixture::FromWordType; - using ToWordType = typename TestFixture::ToWordType; - bit::bit_vector& bitvec1 = this->random_bitvecs1[idx]; - bit::bit_vector& bitvec2 = this->random_bitvecs2[idx]; - constexpr auto min_digits = std::min(bit::bitsof(), bit::bitsof()); + bit::bit_vector& bitvec1 = this->random_bitvecs1[idx]; + bit::bit_vector& bitvec2 = this->random_bitvecs2[idx]; + constexpr auto min_digits = std::min(bit::bitsof(), bit::bitsof()); std::vector& boolvec1 = this->random_boolvecs1[idx]; std::vector& boolvec2 = this->random_boolvecs2[idx]; long long start1 = generate_random_number( diff --git a/test/src/test-iterator_adapter.cpp b/test/src/test-iterator_adapter.cpp index f8be23d5..81b94f7f 100644 --- a/test/src/test-iterator_adapter.cpp +++ b/test/src/test-iterator_adapter.cpp @@ -23,7 +23,7 @@ // // GoogleTest suite for bit::bit_word_pointer_adapter. -// Covers both ā€œbig‐to‐smallā€ and ā€œsmall‐to‐largeā€ modes. +// Covers both "big‐to‐small" and "small‐to‐large" modes. // // Assumes a little‐endian platform (so that reinterpret_cast<…> of bytes @@ -45,16 +45,6 @@ TEST(IteratorAdapter, Basic) { EXPECT_EQ(*adapter, 0x56); } -// ----------------------------------------------------------------------------- -// Helper: Convert a 32‐bit word into its 4 individual bytes (little‐endian) -// ----------------------------------------------------------------------------- -static void split_u32_le(uint32_t value, uint8_t out_bytes[4]) { - out_bytes[0] = static_cast(value & 0xFFu); - out_bytes[1] = static_cast((value >> 8) & 0xFFu); - out_bytes[2] = static_cast((value >> 16) & 0xFFu); - out_bytes[3] = static_cast((value >> 24) & 0xFFu); -} - // ----------------------------------------------------------------------------- // TEST SUITE: Big-to-Small Mode (BaseIterator.value_type > Iterator.value_type) // ----------------------------------------------------------------------------- diff --git a/test/src/test-mdspan.cpp b/test/src/test-mdspan.cpp index 78729830..a3d66615 100644 --- a/test/src/test-mdspan.cpp +++ b/test/src/test-mdspan.cpp @@ -41,3 +41,72 @@ TEST(MdSpanTest, BitDefaultLayout) { } } } + +TEST(MdSpanTest, BitFixedWordLayout) { + uint32_t rot = 0xDEADBEEF; + bit::bit_array<> dynarr(5 * 6 * 7); + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 6; j++) { + for (int k = 0; k < 7; k++) { + dynarr[i * 6 * 7 + j * 7 + k] = (rot & 1) ? bit::bit1 : bit::bit0; + rot = (rot >> 1) | ((rot & 1) << 31); + } + } + } + rot = 0xDEADBEEF; + using accessor_t = bit::bit_word_accessor<5, uintptr_t>; + using mdspan_t = + std::mdspan< + accessor_t::element_type, + std::dextents, + std::layout_right, + accessor_t>; + + mdspan_t myspan(&dynarr[0], 6, 7); + for (size_t i = 0; i < myspan.extent(0); i++) { + for (size_t j = 0; j < myspan.extent(1); j++) { + auto ref = myspan[i, j]; + for (size_t k = 0; k < 5; k++) { + bit::bit_value expected = ((rot & 1) ? bit::bit1 : bit::bit0); + EXPECT_EQ(ref[k], expected); + rot = (rot >> 1) | ((rot & 1) << 31); + } + } + } +} + +TEST(MdSpanTest, BitDynamicWordLayout) { + uint32_t rot = 0xDEADBEEF; + bit::bit_array<> dynarr(5 * 6 * 7); + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 6; j++) { + for (int k = 0; k < 7; k++) { + dynarr[i * 6 * 7 + j * 7 + k] = (rot & 1) ? bit::bit1 : bit::bit0; + rot = (rot >> 1) | ((rot & 1) << 31); + } + } + } + rot = 0xDEADBEEF; + using accessor_t = bit::bit_word_accessor; + using extents_t = std::dextents; + using mdspan_t = + std::mdspan< + accessor_t::element_type, + std::dextents, + std::layout_right, + accessor_t>; + + accessor_t myaccessor(5); + extents_t dyn_ext{6, 7}; + mdspan_t myspan(&dynarr[0], dyn_ext, myaccessor); + for (size_t i = 0; i < myspan.extent(0); i++) { + for (size_t j = 0; j < myspan.extent(1); j++) { + auto ref = myspan[i, j]; + for (size_t k = 0; k < 5; k++) { + bit::bit_value expected = ((rot & 1) ? bit::bit1 : bit::bit0); + EXPECT_EQ(ref[k], expected); + rot = (rot >> 1) | ((rot & 1) << 31); + } + } + } +} diff --git a/test/src/test-move.cpp b/test/src/test-move.cpp index bede7a79..f1f9d807 100644 --- a/test/src/test-move.cpp +++ b/test/src/test-move.cpp @@ -1,11 +1,10 @@ // ============================= MOVE TESTS =============================== // // Project: The Experimental Bit Algorithms Library -// Description: Tests for move algorithms +// Description: Tests for move algorithms // Contributor(s): Bryce Kille // License: BSD 3-Clause License // ========================================================================== // - // ============================== PREAMBLE ================================== // // C++ standard library #include @@ -20,57 +19,55 @@ // ========================================================================== // TYPED_TEST(DoubleRangeTest, Move) { - for (size_t idx = 0; idx < this->random_bitvecs1.size(); ++idx) { - using WordType = typename TestFixture::base_type; - bit::bit_vector& bitvec1 = this->random_bitvecs1[idx]; - bit::bit_vector& bitvec2 = this->random_bitvecs2[idx]; - constexpr auto digits = bit::binary_digits::value; - std::vector& boolvec1 = this->random_boolvecs1[idx]; - std::vector& boolvec2 = this->random_boolvecs2[idx]; - long long start1 = generate_random_number( - 0, - std::min(bitvec1.size() - 1, digits + 1)); - long long start2 = generate_random_number( - 0, - std::min(bitvec2.size() - 1, digits + 1)); - const auto min_range = (start2 > start1) ? start2 - start1 : 0; - const auto max_range = std::max( - min_range, - std::min(digits, bitvec1.size() - start1)); - long long end1 = generate_random_number(min_range, max_range); + using WordType = typename TestFixture::base_type; + constexpr auto digits = bit::binary_digits::value; + for (size_t idx = 0; idx < this->random_bitvecs1.size(); ++idx) { + bit::bit_vector& bitvec1 = this->random_bitvecs1[idx]; + bit::bit_vector& bitvec2 = this->random_bitvecs2[idx]; + std::vector& boolvec1 = this->random_boolvecs1[idx]; + std::vector& boolvec2 = this->random_boolvecs2[idx]; + long long start1 = generate_random_number( + 0, + std::min(bitvec1.size() - 1, digits + 1)); + long long start2 = generate_random_number( + 0, + std::min(bitvec2.size() - 1, digits + 1)); + const auto min_range = (start2 > start1) ? start2 - start1 : 0; + const auto max_range = std::max( + min_range, + std::min(digits, bitvec1.size() - start1)); + long long end1 = generate_random_number(min_range, max_range); - auto bitret = bit::move( - bitvec1.begin() + start1, - bitvec1.end() - end1, - bitvec2.begin() + start2); - auto boolret = std::move( - boolvec1.begin() + start1, - boolvec1.end() - end1, - boolvec2.begin() + start2); - EXPECT_EQ( - bit::distance(bitvec2.begin(), bitret), - std::distance(boolvec2.begin(), boolret)); - EXPECT_TRUE(std::equal( - bitvec2.begin(), bitvec2.end(), - boolvec2.begin(), boolvec2.end(), comparator) - ); - start2 = generate_random_number(0, start1); - bitret = bit::move( - bitvec1.begin() + start1, - bitvec1.end() - end1, - bitvec1.begin() + start2); - boolret = std::move( - boolvec1.begin() + start1, - boolvec1.end() - end1, - boolvec1.begin() + start2); - EXPECT_EQ( - bit::distance(bitvec1.begin(), bitret), - std::distance(boolvec1.begin(), boolret)); - EXPECT_TRUE(std::equal( - bitvec1.begin(), bitvec1.end(), - boolvec1.begin(), boolvec1.end(), comparator) - ); - } + auto bitret = bit::move( + bitvec1.begin() + start1, + bitvec1.end() - end1, + bitvec2.begin() + start2); + auto boolret = std::move( + boolvec1.begin() + start1, + boolvec1.end() - end1, + boolvec2.begin() + start2); + EXPECT_EQ( + bit::distance(bitvec2.begin(), bitret), + std::distance(boolvec2.begin(), boolret)); + EXPECT_TRUE(std::equal( + bitvec2.begin(), bitvec2.end(), + boolvec2.begin(), boolvec2.end(), comparator)); + start2 = generate_random_number(0, start1); + bitret = bit::move( + bitvec1.begin() + start1, + bitvec1.end() - end1, + bitvec1.begin() + start2); + boolret = std::move( + boolvec1.begin() + start1, + boolvec1.end() - end1, + boolvec1.begin() + start2); + EXPECT_EQ( + bit::distance(bitvec1.begin(), bitret), + std::distance(boolvec1.begin(), boolret)); + EXPECT_TRUE(std::equal( + bitvec1.begin(), bitvec1.end(), + boolvec1.begin(), boolvec1.end(), comparator)); + } } diff --git a/test/src/vector_test.cpp b/test/src/vector_test.cpp index 5c896b48..78c5e241 100644 --- a/test/src/vector_test.cpp +++ b/test/src/vector_test.cpp @@ -33,9 +33,10 @@ TYPED_TEST(VectorTest, SizeInitializerConstructor) { EXPECT_FALSE(bv); } using WordType = typename TestFixture::base_type; - using vec_type = typename TestFixture::vec_type; - for (unsigned int veclen = 0; veclen < 4 * this->digits; veclen++) { - this->empty_vec = vec_type(veclen); + constexpr auto digits = bit::binary_digits::value; + using VecType = typename TestFixture::vec_type; + for (unsigned int veclen = 0; veclen < 4 * digits; veclen++) { + this->empty_vec = VecType(veclen); for (unsigned int i = 0; i < veclen; ++i) { EXPECT_FALSE(this->empty_vec[i]); } @@ -51,87 +52,90 @@ TYPED_TEST(VectorTest, SizeInitializerConstructor) { } TYPED_TEST(VectorTest, CountValueConstructor) { - using vec_type = typename TestFixture::vec_type; - for (unsigned int veclen = 0; veclen < 4*this->digits; veclen++) { - this->empty_vec = vec_type(veclen, bit::bit1); - unsigned int i = 0; - // TODO misplaced test for range-based for loop - for (auto bv: this->empty_vec) { - EXPECT_TRUE(bv); - i++; - } - EXPECT_EQ(i, veclen); - this->empty_vec = vec_type(veclen, bit::bit0); - i = 0; - // TODO misplaced test for range-based for loop - for (auto bv: this->empty_vec) { - EXPECT_FALSE(bv); - i++; - } - EXPECT_EQ(i, veclen); + using VecType = typename TestFixture::vec_type; + using WordType = typename TestFixture::base_type; + constexpr auto digits = bit::binary_digits::value; + for (unsigned int veclen = 0; veclen < 4 * digits; veclen++) { + this->empty_vec = VecType(veclen, bit::bit1); + unsigned int i = 0; + // TODO misplaced test for range-based for loop + for (auto bv : this->empty_vec) { + EXPECT_TRUE(bv); + i++; + } + EXPECT_EQ(i, veclen); + this->empty_vec = VecType(veclen, bit::bit0); + i = 0; + // TODO misplaced test for range-based for loop + for (auto bv : this->empty_vec) { + EXPECT_FALSE(bv); + i++; } + EXPECT_EQ(i, veclen); + } } // Test when first, last are WordType iterators TYPED_TEST(VectorTest, WordIterPairConstructor) { using WordType = typename TestFixture::base_type; - using vec_type = typename TestFixture::vec_type; + using VecType = typename TestFixture::vec_type; using iterator_type = typename std::vector::iterator; - for (unsigned int veclen = 0; veclen < 4*this->digits; veclen++) { - std::vector word_vec = get_random_vec(veclen); - vec_type test(word_vec.begin(), word_vec.end()); - EXPECT_TRUE(std::equal( - test.begin(), - test.end(), - bit::bit_iterator(word_vec.begin()), - bit::bit_iterator(word_vec.end()))); + constexpr auto digits = bit::binary_digits::value; + for (unsigned int veclen = 0; veclen < 4 * digits; veclen++) { + std::vector word_vec = get_random_vec(veclen); + VecType test(word_vec.begin(), word_vec.end()); + EXPECT_TRUE(std::equal( + test.begin(), + test.end(), + bit::bit_iterator(word_vec.begin()), + bit::bit_iterator(word_vec.end()))); } } // Test when first, last are bool iterators TYPED_TEST(VectorTest, BoolIterPairConstructor) { - using vec_type = typename TestFixture::vec_type; - for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { - auto& boolvec = this->random_boolvecs[vec_idx]; - vec_type test = vec_type(boolvec.begin(), boolvec.end()); - EXPECT_TRUE(std::equal( - test.begin(), - test.end(), - boolvec.begin(), - boolvec.end(), - comparator)); - } + using VecType = typename TestFixture::vec_type; + for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { + auto& boolvec = this->random_boolvecs[vec_idx]; + VecType test = VecType(boolvec.begin(), boolvec.end()); + EXPECT_TRUE(std::equal( + test.begin(), + test.end(), + boolvec.begin(), + boolvec.end(), + comparator)); + } } // Test copy ctor TYPED_TEST(VectorTest, CopyConstructor) { - using vec_type = typename TestFixture::vec_type; - for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { - auto& bitvec = this->random_bitvecs[vec_idx]; - auto& boolvec = this->random_boolvecs[vec_idx]; - vec_type test = vec_type(bitvec); - EXPECT_TRUE(std::equal( - test.begin(), - test.end(), - boolvec.begin(), - boolvec.end(), - comparator)); - } + using VecType = typename TestFixture::vec_type; + for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { + auto& bitvec = this->random_bitvecs[vec_idx]; + auto& boolvec = this->random_boolvecs[vec_idx]; + VecType test = VecType(bitvec); + EXPECT_TRUE(std::equal( + test.begin(), + test.end(), + boolvec.begin(), + boolvec.end(), + comparator)); + } } TYPED_TEST(VectorTest, MoveConstructor) { - using vec_type = typename TestFixture::vec_type; - for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { - auto& bitvec = this->random_bitvecs[vec_idx]; - auto& boolvec = this->random_boolvecs[vec_idx]; - vec_type test = vec_type(std::move(bitvec)); - EXPECT_TRUE(std::equal( - test.begin(), - test.end(), - boolvec.begin(), - boolvec.end(), - comparator)); - } + using VecType = typename TestFixture::vec_type; + for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { + auto& bitvec = this->random_bitvecs[vec_idx]; + auto& boolvec = this->random_boolvecs[vec_idx]; + VecType test = VecType(std::move(bitvec)); + EXPECT_TRUE(std::equal( + test.begin(), + test.end(), + boolvec.begin(), + boolvec.end(), + comparator)); + } } // Tests the string c'tor. @@ -140,20 +144,21 @@ TYPED_TEST(VectorTest, StringConstructor) { EXPECT_EQ(false, static_cast(this->v3_[0])); EXPECT_EQ(true, static_cast(this->v3_[8])); using WordType = typename TestFixture::base_type; - for (unsigned int strlen = 0; strlen < 4*this->digits; strlen++) { - std::string rand_bs(strlen, 0); - this->empty_vec_bool.clear(); - for (auto& pos: rand_bs) { - pos = generate_random_number('0', '1'); - this->empty_vec_bool.push_back(pos == '1'); - } - this->empty_vec = bit::bit_vector(rand_bs); - EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), - comparator)); + constexpr auto digits = bit::binary_digits::value; + for (unsigned int strlen = 0; strlen < 4 * digits; strlen++) { + std::string rand_bs(strlen, 0); + this->empty_vec_bool.clear(); + for (auto& pos : rand_bs) { + pos = generate_random_number('0', '1'); + this->empty_vec_bool.push_back(pos == '1'); + } + this->empty_vec = bit::bit_vector(rand_bs); + EXPECT_TRUE(std::equal( + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), + comparator)); } } @@ -213,9 +218,9 @@ TYPED_TEST(VectorTest, MoveAssignment) { // Test the initializer list c'tor TYPED_TEST(VectorTest, InitializerListConstructor) { bit::bit_vector v2_copy = this->v2_; - using vec_type = typename TestFixture::vec_type; + using VecType = typename TestFixture::vec_type; std::vector boolvec {true, false, true, true, true, false, false, true, false, true, true, false}; - vec_type test {true, false, true, true, true, false, false, true, false, true, true, false}; + VecType test{true, false, true, true, true, false, false, true, false, true, true, false}; EXPECT_TRUE(std::equal( test.begin(), test.end(), @@ -262,18 +267,20 @@ TYPED_TEST(VectorTest, BracketWrite) { // Test at TYPED_TEST(VectorTest, AtRead) { - EXPECT_EQ(this->v3_.at(0), bit::bit0); - EXPECT_EQ(this->v3_.at(8), bit::bit1); - for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { - auto& bitvec = this->random_bitvecs[vec_idx]; - auto& boolvec = this->random_boolvecs[vec_idx]; - for (unsigned int i = 0; i < boolvec.size(); i++) { - EXPECT_TRUE(comparator(bitvec.at(i), boolvec.at(i))); - } - for (unsigned int i = boolvec.size(); i < boolvec.size() + 4*this->digits; i++) { - EXPECT_THROW(bitvec.at(i), std::out_of_range); - } + using WordType = typename TestFixture::base_type; + constexpr auto digits = bit::binary_digits::value; + EXPECT_EQ(this->v3_.at(0), bit::bit0); + EXPECT_EQ(this->v3_.at(8), bit::bit1); + for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { + auto& bitvec = this->random_bitvecs[vec_idx]; + auto& boolvec = this->random_boolvecs[vec_idx]; + for (unsigned int i = 0; i < boolvec.size(); i++) { + EXPECT_TRUE(comparator(bitvec.at(i), boolvec.at(i))); + } + for (unsigned int i = boolvec.size(); i < boolvec.size() + 4 * digits; i++) { + EXPECT_THROW(bitvec.at(i), std::out_of_range); } + } } @@ -314,20 +321,22 @@ TYPED_TEST(VectorTest, ReserveAndCapacity) { // Test shrink_to_fit TYPED_TEST(VectorTest, ShrinkToFit) { - this->empty_vec.shrink_to_fit(); - EXPECT_EQ(this->empty_vec.capacity(), 0); - this->empty_vec.reserve(12345); - this->empty_vec.shrink_to_fit(); - EXPECT_EQ(this->empty_vec.capacity(), 0); - for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { - auto& bitvec = this->random_bitvecs[vec_idx]; - for (unsigned int _ = 0; _ < this->digits; _++) { - bitvec.pop_back(); - } - auto old_cap = bitvec.capacity(); - bitvec.shrink_to_fit(); - EXPECT_LT(bitvec.capacity(), old_cap); - } + using WordType = typename TestFixture::base_type; + constexpr auto digits = bit::binary_digits::value; + this->empty_vec.shrink_to_fit(); + EXPECT_EQ(this->empty_vec.capacity(), 0); + this->empty_vec.reserve(12345); + this->empty_vec.shrink_to_fit(); + EXPECT_EQ(this->empty_vec.capacity(), 0); + for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { + auto& bitvec = this->random_bitvecs[vec_idx]; + for (unsigned int _ = 0; _ < digits; _++) { + bitvec.pop_back(); + } + auto old_cap = bitvec.capacity(); + bitvec.shrink_to_fit(); + EXPECT_LT(bitvec.capacity(), old_cap); + } } /* @@ -356,145 +365,157 @@ TYPED_TEST(VectorTest, Clear) { // Test insert TYPED_TEST(VectorTest, InsertAtEnd1) { - // First signature - for (auto _ = 64*this->digits; _--;) { - bool to_insert_bool = generate_random_number(0, 1) > 0 ? true : false; - bit::bit_value to_insert_bit = to_insert_bool ? bit::bit1 : bit::bit0; - auto bitret = this->empty_vec.insert( - this->empty_vec.end(), - to_insert_bit); - auto boolret = this->empty_vec_bool.insert( - this->empty_vec_bool.end(), - to_insert_bool); - EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), - comparator)); - EXPECT_EQ( - std::distance(this->empty_vec.begin(), bitret), - std::distance(this->empty_vec_bool.begin(), boolret)); - } + using WordType = typename TestFixture::base_type; + constexpr auto digits = bit::binary_digits::value; + // First signature + for (auto _ = 64 * digits; _--;) { + bool to_insert_bool = generate_random_number(0, 1) > 0 ? true : false; + bit::bit_value to_insert_bit = to_insert_bool ? bit::bit1 : bit::bit0; + auto bitret = this->empty_vec.insert( + this->empty_vec.end(), + to_insert_bit); + auto boolret = this->empty_vec_bool.insert( + this->empty_vec_bool.end(), + to_insert_bool); + EXPECT_TRUE(std::equal( + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), + comparator)); + EXPECT_EQ( + std::distance(this->empty_vec.begin(), bitret), + std::distance(this->empty_vec_bool.begin(), boolret)); + } } TYPED_TEST(VectorTest, InsertAtBegin1) { - // First signature - for (auto _ = 64*this->digits; _--;) { - bool to_insert_bool = generate_random_number(0, 1) > 0 ? true : false; - bit::bit_value to_insert_bit = to_insert_bool ? bit::bit1 : bit::bit0; - auto bitret = this->empty_vec.insert( - this->empty_vec.begin(), - to_insert_bit); - auto boolret = this->empty_vec_bool.insert( - this->empty_vec_bool.begin(), - to_insert_bool); - EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), - comparator)); - EXPECT_EQ( - std::distance(this->empty_vec.begin(), bitret), - std::distance(this->empty_vec_bool.begin(), boolret)); - } + using WordType = typename TestFixture::base_type; + constexpr auto digits = bit::binary_digits::value; + // First signature + for (auto _ = 64 * digits; _--;) { + bool to_insert_bool = generate_random_number(0, 1) > 0 ? true : false; + bit::bit_value to_insert_bit = to_insert_bool ? bit::bit1 : bit::bit0; + auto bitret = this->empty_vec.insert( + this->empty_vec.begin(), + to_insert_bit); + auto boolret = this->empty_vec_bool.insert( + this->empty_vec_bool.begin(), + to_insert_bool); + EXPECT_TRUE(std::equal( + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), + comparator)); + EXPECT_EQ( + std::distance(this->empty_vec.begin(), bitret), + std::distance(this->empty_vec_bool.begin(), boolret)); + } } TYPED_TEST(VectorTest, InsertAtRand1) { - // First signature - for (auto _ = 64*this->digits; _--;) { - auto insert_location = generate_random_number(0, this->empty_vec.size()); - bool to_insert_bool = generate_random_number(0, 1) > 0 ? true : false; - bit::bit_value to_insert_bit = to_insert_bool ? bit::bit1 : bit::bit0; - auto bitret = this->empty_vec.insert( - this->empty_vec.begin() + insert_location, - to_insert_bit); - auto boolret = this->empty_vec_bool.insert( - this->empty_vec_bool.begin() + insert_location, - to_insert_bool); - EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), - comparator)); - EXPECT_EQ( - std::distance(this->empty_vec.begin(), bitret), - std::distance(this->empty_vec_bool.begin(), boolret)); - } + using WordType = typename TestFixture::base_type; + constexpr auto digits = bit::binary_digits::value; + // First signature + for (auto _ = 64 * digits; _--;) { + auto insert_location = generate_random_number(0, this->empty_vec.size()); + bool to_insert_bool = generate_random_number(0, 1) > 0 ? true : false; + bit::bit_value to_insert_bit = to_insert_bool ? bit::bit1 : bit::bit0; + auto bitret = this->empty_vec.insert( + this->empty_vec.begin() + insert_location, + to_insert_bit); + auto boolret = this->empty_vec_bool.insert( + this->empty_vec_bool.begin() + insert_location, + to_insert_bool); + EXPECT_TRUE(std::equal( + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), + comparator)); + EXPECT_EQ( + std::distance(this->empty_vec.begin(), bitret), + std::distance(this->empty_vec_bool.begin(), boolret)); + } } TYPED_TEST(VectorTest, InsertAtBegin2) { - // Second signature - for (auto _ = 16; _--;) { - auto to_insert = generate_random_number(0, 4*this->digits); - auto bitret = this->empty_vec.insert( - this->empty_vec.begin(), - to_insert, - bit::bit1); - auto boolret = this->empty_vec_bool.insert( - this->empty_vec_bool.begin(), - to_insert, - true); - EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), - comparator)); - EXPECT_EQ( - std::distance(this->empty_vec.begin(), bitret), - std::distance(this->empty_vec_bool.begin(), boolret)); - } + using WordType = typename TestFixture::base_type; + constexpr auto digits = bit::binary_digits::value; + // Second signature + for (auto _ = 16; _--;) { + auto to_insert = generate_random_number(0, 4 * digits); + auto bitret = this->empty_vec.insert( + this->empty_vec.begin(), + to_insert, + bit::bit1); + auto boolret = this->empty_vec_bool.insert( + this->empty_vec_bool.begin(), + to_insert, + true); + EXPECT_TRUE(std::equal( + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), + comparator)); + EXPECT_EQ( + std::distance(this->empty_vec.begin(), bitret), + std::distance(this->empty_vec_bool.begin(), boolret)); + } } TYPED_TEST(VectorTest, InsertAtEnd2) { - // Second signature - for (auto _ = 16; _--;) { - auto to_insert = generate_random_number(0, 444*this->digits); - auto bitret = this->empty_vec.insert( - this->empty_vec.end(), - to_insert, - bit::bit1); - auto boolret = this->empty_vec_bool.insert( - this->empty_vec_bool.end(), - to_insert, - true); - EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), - comparator)); - EXPECT_EQ( - std::distance(this->empty_vec.begin(), bitret), - std::distance(this->empty_vec_bool.begin(), boolret)); - } + using WordType = typename TestFixture::base_type; + constexpr auto digits = bit::binary_digits::value; + // Second signature + for (auto _ = 16; _--;) { + auto to_insert = generate_random_number(0, 444 * digits); + auto bitret = this->empty_vec.insert( + this->empty_vec.end(), + to_insert, + bit::bit1); + auto boolret = this->empty_vec_bool.insert( + this->empty_vec_bool.end(), + to_insert, + true); + EXPECT_TRUE(std::equal( + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), + comparator)); + EXPECT_EQ( + std::distance(this->empty_vec.begin(), bitret), + std::distance(this->empty_vec_bool.begin(), boolret)); + } } TYPED_TEST(VectorTest, InsertAtRand2) { - // Second signature - for (auto _ = 16; _--;) { - auto to_insert = generate_random_number(0, 4*this->digits); - auto insert_location = generate_random_number(0, this->empty_vec.size()); - auto bitret = this->empty_vec.insert( - this->empty_vec.begin() + insert_location, - to_insert, - bit::bit1); - auto boolret = this->empty_vec_bool.insert( - this->empty_vec_bool.begin() + insert_location, - to_insert, - true); - EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), - comparator)); - EXPECT_EQ( - std::distance(this->empty_vec.begin(), bitret), - std::distance(this->empty_vec_bool.begin(), boolret)); - } + using WordType = typename TestFixture::base_type; + constexpr auto digits = bit::binary_digits::value; + // Second signature + for (auto _ = 16; _--;) { + auto to_insert = generate_random_number(0, 4 * digits); + auto insert_location = generate_random_number(0, this->empty_vec.size()); + auto bitret = this->empty_vec.insert( + this->empty_vec.begin() + insert_location, + to_insert, + bit::bit1); + auto boolret = this->empty_vec_bool.insert( + this->empty_vec_bool.begin() + insert_location, + to_insert, + true); + EXPECT_TRUE(std::equal( + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), + comparator)); + EXPECT_EQ( + std::distance(this->empty_vec.begin(), bitret), + std::distance(this->empty_vec_bool.begin(), boolret)); + } } TYPED_TEST(VectorTest, InsertAtBegin3) { @@ -710,42 +731,46 @@ TYPED_TEST(VectorTest, EraseAtRand2) { // Test push_back TYPED_TEST(VectorTest, PushBack) { - // First signature - for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { - for (auto _ = 4*this->digits; _ > 0; _--) { - auto& bitvec = this->random_bitvecs[vec_idx]; - auto& boolvec = this->random_boolvecs[vec_idx]; - bool to_insert_bool = generate_random_number(0, 1) > 0 ? true : false; - bit::bit_value to_insert_bit = to_insert_bool ? bit::bit1 : bit::bit0; - bitvec.push_back(to_insert_bit); - boolvec.push_back(to_insert_bool); - EXPECT_TRUE(std::equal( - bitvec.begin(), - bitvec.end(), - boolvec.begin(), - boolvec.end(), - comparator)); - } + using WordType = typename TestFixture::base_type; + constexpr auto digits = bit::binary_digits::value; + // First signature + for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { + for (auto _ = 4 * digits; _ > 0; _--) { + auto& bitvec = this->random_bitvecs[vec_idx]; + auto& boolvec = this->random_boolvecs[vec_idx]; + bool to_insert_bool = generate_random_number(0, 1) > 0 ? true : false; + bit::bit_value to_insert_bit = to_insert_bool ? bit::bit1 : bit::bit0; + bitvec.push_back(to_insert_bit); + boolvec.push_back(to_insert_bool); + EXPECT_TRUE(std::equal( + bitvec.begin(), + bitvec.end(), + boolvec.begin(), + boolvec.end(), + comparator)); } + } } // Test pop_back TYPED_TEST(VectorTest, PopBack) { - // First signature - for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { - auto& bitvec = this->random_bitvecs[vec_idx]; - auto& boolvec = this->random_boolvecs[vec_idx]; - for (auto _ = std::min(boolvec.size(), 4*this->digits); _>0; _--) { - bitvec.pop_back(); - boolvec.pop_back(); - EXPECT_TRUE(std::equal( - bitvec.begin(), - bitvec.end(), - boolvec.begin(), - boolvec.end(), - comparator)); - } + using WordType = typename TestFixture::base_type; + constexpr auto digits = bit::binary_digits::value; + // First signature + for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { + auto& bitvec = this->random_bitvecs[vec_idx]; + auto& boolvec = this->random_boolvecs[vec_idx]; + for (auto _ = std::min(boolvec.size(), 4 * digits); _ > 0; _--) { + bitvec.pop_back(); + boolvec.pop_back(); + EXPECT_TRUE(std::equal( + bitvec.begin(), + bitvec.end(), + boolvec.begin(), + boolvec.end(), + comparator)); } + } } TEST(BitVectorTest, Slice) { From b95418ea8334f6e5af88bc5c0eeb1c33111e0211 Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Sun, 6 Jul 2025 20:59:06 +0000 Subject: [PATCH 72/85] Update readme add doc (#21) * First couple sections of new doc * Small tweaks/improvements to CMake constraint/requirements * Add documentation on bit_array and mdspan * Add a small section about policy * Add a note about licensing * Update license file to include libpopcnt license * Try harder to remove undesired paths from clang coverage --- .github/actions/coverage_clang/action.yml | 5 +- .github/actions/coverage_epilogue/action.yml | 86 ---- .github/actions/coverage_prologue/action.yml | 34 -- CMakeLists.txt | 14 +- LICENSE | 33 +- README.md | 466 ++++++++++-------- benchmark/CMakeLists.txt | 2 + doc/benchmarks.md | 118 +++++ .../bit-containers/bit_mdspan_accessor.hpp | 4 +- 9 files changed, 421 insertions(+), 341 deletions(-) delete mode 100644 .github/actions/coverage_epilogue/action.yml delete mode 100644 .github/actions/coverage_prologue/action.yml create mode 100644 doc/benchmarks.md diff --git a/.github/actions/coverage_clang/action.yml b/.github/actions/coverage_clang/action.yml index e75f6698..f3a93909 100644 --- a/.github/actions/coverage_clang/action.yml +++ b/.github/actions/coverage_clang/action.yml @@ -24,17 +24,18 @@ runs: # Show summary coverage report llvm-cov report ${{ inputs.build-output-dir }}/test/bitlib-tests \ -instr-profile=coverage.profdata \ - -ignore-filename-regex='/usr/|_deps/|libpopcnt\.h|test/inc/|test/src/' + -ignore-filename-regex='/usr/.*|.*_deps/.*|.*/libpopcnt\.h|.*test/inc/.*|.*test/src/.*' # Generate HTML report llvm-cov show ${{ inputs.build-output-dir }}/test/bitlib-tests \ -instr-profile=coverage.profdata \ -format=html \ -output-dir=out/coverage_clang \ - -ignore-filename-regex='/usr/|_deps/|libpopcnt\.h|test/inc/|test/src/' + -ignore-filename-regex='/usr/.*|.*_deps/.*|.*/libpopcnt\.h|.*test/inc/.*|.*test/src/.*' llvm-cov export ${{ inputs.build-output-dir }}/test/bitlib-tests \ -instr-profile=coverage.profdata \ + -ignore-filename-regex='/usr/.*|.*_deps/.*|.*/libpopcnt\.h|.*test/inc/.*|.*test/src/.*' \ -format=lcov > coverage_clang.info - name: Upload coverage artifact diff --git a/.github/actions/coverage_epilogue/action.yml b/.github/actions/coverage_epilogue/action.yml deleted file mode 100644 index 9515efc8..00000000 --- a/.github/actions/coverage_epilogue/action.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: "Coverage Epilogue" -description: "Processes coverage information with lcov and uploads it to coveralls/codecov" -inputs: - build-output-dir: - required: true - description: 'Build output directory' - compiler: - required: true - description: 'Compiler in use' - GITHUB_TOKEN: - required: true - -runs: - using: "composite" - steps: - - name: Generate Coverage Report (gcc) - if: ${{ inputs.compiler == 'gcc' }} - shell: bash - run: | - # Capture actual coverage data after tests - lcov --capture \ - --directory "${{ inputs.build-output-dir }}" \ - --output-file coverage.info \ - --ignore-errors mismatch - - # Combine with baseline - lcov --add-tracefile coverage.baseline \ - --add-tracefile coverage.info\ - --output-file coverage.info - - # Clean the coverage report from system and external deps - lcov --remove coverage.info \ - '/usr/*' \ - '*/_deps/*' \ - '*/include/bitlib/bit-algorithms/libpopcnt.h' \ - '*/test/inc/*' \ - '*/test/src/*' \ - --output-file coverage.info - - # Show a summary in the logs - lcov --list coverage.info - - # Generate an HTML report - genhtml coverage.info --output-directory out/coverage - - - name: Generate Coverage Report (clang) - if: ${{ inputs.compiler == 'clang' }} - shell: bash - run: | - # Merge raw coverage into .profdata - llvm-profdata merge -sparse coverage.profraw -o coverage.info - - # Show summary coverage report - llvm-cov report ${{ inputs.build-output-dir }}/test/bitlib-tests \ - -instr-profile=coverage.info \ - -ignore-filename-regex='/usr/|_deps/|libpopcnt\.h|test/inc/|test/src/' - - # Generate HTML report - llvm-cov show ${{ inputs.build-output-dir }}/test/bitlib-tests \ - -instr-profile=coverage.info \ - -format=html \ - -output-dir=out/coverage \ - -ignore-filename-regex='/usr/|_deps/|libpopcnt\.h|test/inc/|test/src/' - - - name: Upload coverage artifact - uses: actions/upload-artifact@v4 - with: - name: coverage-report - path: | - out/coverage - coverage.baseline - coverage.info - - - name: Coveralls - if: ${{ inputs.compiler != 'clang' }} - uses: coverallsapp/github-action@master - with: - github-token: ${{ inputs.GITHUB_TOKEN }} - path-to-lcov: coverage.info - - - name: Upload to Codecov - uses: codecov/codecov-action@v5 - with: - files: coverage.info - flags: unittests - name: codecov-coverage-report diff --git a/.github/actions/coverage_prologue/action.yml b/.github/actions/coverage_prologue/action.yml deleted file mode 100644 index 883b77c6..00000000 --- a/.github/actions/coverage_prologue/action.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: "Coverage Prologue" -description: "Installs lcov and generates the baseline coverage" -inputs: - build-output-dir: - required: true - description: 'Build output directory' - compiler: - required: true - description: 'Compiler in use' - -runs: - using: "composite" - steps: - - name: "Install lcov" - if: ${{ inputs.compiler == 'gcc' }} - shell: bash - run: | - sudo apt-get update - sudo apt-get install -y lcov - - - name: "Initialize 0% coverage baseline" - if: ${{ inputs.compiler == 'gcc' }} - shell: bash - run: | - lcov --capture --initial \ - --base-directory "$GITHUB_WORKSPACE" \ - --directory "${{ inputs.build-output-dir }}" \ - --output-file coverage.baseline \ - --ignore-errors mismatch - - - name: "LLVM profile env var" - if: ${{ inputs.compiler == 'clang' }} - shell: bash - run: echo "LLVM_PROFILE_FILE=${GITHUB_WORKSPACE}/coverage.profraw" >> $GITHUB_ENV \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 32f3b4a5..b2616ae2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,14 @@ -# specify the C++ standard -cmake_minimum_required(VERSION 3.14) -cmake_policy(SET CMP0168 NEW) -cmake_policy(SET CMP0167 NEW) +cmake_minimum_required(VERSION 3.20) + +if (POLICY CMP0168) + cmake_policy(SET CMP0168 NEW) +endif() +if (POLICY CMP0167) + cmake_policy(SET CMP0167 NEW) +endif() # set the project name -project(BitLib VERSION 0.3.0) +project(BitLib VERSION 1.0.0) option(BITLIB_HWY "Build with google highway SIMD extensions" OFF) option(BITLIB_BENCHMARK "Build bitlib benchmarks" OFF) diff --git a/LICENSE b/LICENSE index e66d784a..e0e49caf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -BSD 3-Clause License +BitLib BSD 3-Clause License Copyright (c) 2022, Bryce Kille All rights reserved. @@ -27,3 +27,34 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------THIRD-PARTY LICENSES------------------------------- + +libpopcnt.h - C/C++ library for counting the number of 1 bits (bit +population count) in an array as quickly as possible using +specialized CPU instructions i.e. POPCNT, AVX2, AVX512, NEON. + +Copyright (c) 2016 - 2020, Kim Walisch +Copyright (c) 2016 - 2018, Wojciech Muła +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index a8f81b4c..816efc02 100644 --- a/README.md +++ b/README.md @@ -2,48 +2,81 @@ ![Actions](https://github.com/PeterCDMcLean/BitLib/actions/workflows/cmake-multi-platform.yml/badge.svg?branch=master)[![Coverage Status](https://coveralls.io/repos/github/PeterCDMcLean/BitLib/badge.svg?branch=master)](https://coveralls.io/github/PeterCDMcLean/BitLib?branch=master) -**This repository acts as an efficient replacement of `std::vector`. It provides implementations of many of the functions in [``](https://en.cppreference.com/w/cpp/algorithm) optimized for containers of bits, in addition to providing a `bit_vector` class which has roughly the same interface as `std::vector`**. - -This project is built on "[bit iterators](https://github.com/vreverdy/bit)" developed by Vincent Reverdy and many of the implementations in `include/bit-algorithms` come from some of my previous work with Vincent [here](https://github.com/vreverdy/bit-algorithms). - -# Example -The code below is from `example/src/example1.cpp`. While the type of word that the bitvector is built off of is templated and you can use any unsigned type, it is likely that you'll want to use `uint64_t` or another 64 bit unsigned type, as that will leverage the most bit-parallelism. -```cpp -#include -#include "bitlib/bitlib.hpp" - -int main() { - bit::bit_vector bv1 ("011111010010"); - std::cout << "Original bitvec: " << bv1.debug_string() << std::endl; - // Original bitvec: 01111101 0010 - - // Same behavior as std::reverse - bit::reverse(bv1.begin(), bv1.end()); - std::cout << "Reversed bitvec: " << bv1.debug_string() << std::endl; - // Reversed bitvec: 01001011 1110 - - // Same behavior as std::rotate - bit::rotate(bv1.begin(), bv1.begin() + 3, bv1.end()); - std::cout << "Rotated bitvec: " << bv1.debug_string() << std::endl; - // Rotated bitvec: 01011111 0010 - - // Same behavior as the corresponding std::vector::push_back and std::vector::insert - bv1.push_back(bit::bit0); - bv1.insert(bv1.end(), 10, bit::bit1); - std::cout << "Extended bitvec: " << bv1.debug_string() << std::endl; - // Extended bitvec: 01011111 00100111 1111111 - - return 0; -} +# Overview + +This project provides convenient and efficient stl-like containers and algorithms for bit access. + +# Table of Contents + +- [Contributors](#contributors) +- [Requirements](#requirements) +- [Example](#example) +- [CMake](#cmake) +- [Sized Literal](#literal) +- [Slice Operator](#slice-operator) +- [Policy](#policy) +- [Owning Containers](#owning-containers) + - [bit_array](#bit_array) + - [bit_vector](#bit_vector) +- [Non-owning Views](#non-owning-views) + - [bit_array_ref](#bit_array_ref) + - [bit_span](#bit_span) + - [mdspan with bit_default_accessor](#mdspan-with-bit-accessors) +- [Iterators and References](#iterators-and-references) + - [bit_iterator](#bit_iterator) + - [bit_reference](#bit_reference) + - [bit_word_pointer_adapter](#bit_word_pointer_adapter) + - [bit_word_reference_adapter](#bit_word_reference_adapter) +- [Algorithms](#algorithms) +- [Testing](#testing) +- [License](#license) + +# Contributors +- Vincent Reverdy +- Bryce Kille +- Peter McLean + +# Requirements + +- _gcc_ or _clang_ +- `C++23` +- cmake verison 3.20. + * cmake version 3.31 if compiling bitlib's benchmark suite + +# Example + +# CMake +### options +bitlib provides the following cmake options and their defaults +```cmake +option(BITLIB_HWY "Build with google highway SIMD extensions" OFF) +option(BITLIB_BENCHMARK "Build bitlib benchmarks" OFF) +option(BITLIB_EXAMPLE "Build bitlib examples" OFF) +option(BITLIB_TEST "Build bitlib tests" OFF) +option(BITLIB_TEST_WERROR "Build bitlib tests with -Werror" OFF) +option(BITLIB_MDSPAN "Accessor Support for mdspan" ON) ``` -# Installation -BitLib is a header-only libarary. Currently, the BitLib library requires at least `-std=c++17`. +Options can be set through command line switches: +```bash +cmake -G .. -DBITLIB_MDSPAN=OFF +``` +or within cmake files before the bitlib targets have been added +```cmake +set(BITLIB_MDSPAN OFF CACHE BOOL "Disable mdspan" FORCE) +``` +The bitlib targets are added through one of two ways: -## CMake -You can automatically fetch the library using Cmake's `FetchContent`. +### add_subdirectory +```cmake +add_subdirectory(..../bitlib) +add_executable(example) +target_sources(example PRIVATE example.cpp) +target_link_libraries(example PRIVATE bitlib::bitlib) +``` +### FetchContent ```cmake include(FetchContent) FetchContent_Declare( @@ -53,205 +86,214 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(bitlib) -add_executable(example example.cpp) -target_link_libraries(example bitlib::bitlib) +add_executable(example) +target_sources(example PRIVATE example.cpp) +target_link_libraries(example PRIVATE bitlib::bitlib) ``` -## Manual include -Alternatively, you can copy the `include/bitlib` directory to somewhere in your include path. - -## SIMD support, testing and benchmarking +# Literal -SIMD support (enabled via Google's [highway](https://github.com/google/highway) library) can be enabled by defining `BITLIB_HWY`. For example, with `cmake`, you can run `cmake -DBITLIB_HWY=1`. Other options can be found in the `CMakeLists.txt` file: +This provides a sized literal for compile-time bit_array. -```cmake -option(BITLIB_HWY "Build with google highway SIMD extensions" OFF) -option(BITLIB_BENCHMARK "Build bitlib benchmarks" OFF) -option(BITLIB_EXAMPLE "Build bitlib examples" OFF) -option(BITLIB_TEST "Build bitlib tests" OFF) -option(BITLIB_PROFILE "Buid simple example for profiling" OFF) -option(BITLIB_COVERAGE "Compute test coverage" OFF) +There are four components of the literal: ``` + [base_prefix][size']data_b + | | | | + |---> | <--| |--- '_b' suffix +ex: 0xC'7B_b; +``` + - base prefix + - size + * before first apostrophe `'` + - data + - `_b` suffix -# Usage -The goal of BitLib is to be as similar to the C++ STL as possible. The interface of most functions and classes are the same as they are in the STL. Instead of the values being `bool`, we have `bit::bit_value`, which can take on either `bit::bit0` or `bit::bit1`. +### Base +Similar to standard C++ literals, the base is optionally prefixed: -## Containers - Right now, the only container I have implemented is the bitvector. `bit::bit_vector` is essentially a wrapper around `std::vector`. The interfaces are nearly identical. In addition to the normal `vector` constructors, you can also provide a string to construct your bitvector: -```cpp -using WordType = uint64_t; -bit::bit_vector bvec1 ("011111010010"); -``` +- default base 10 +- `0b` binary base +- `0x` hexadecimal base +- `0` octal base -While the type of word that the bitvector is built off of is templated and you can use any unsigned type, it is likely that you'll want to use `uint64_t` or another 64 bit unsigned type, as that will leverage the most bit-parallelism. +### Size +This user-defined literal repurposes the apostrophe `'` numeric separator in the C++ standard. +The digits of the numeric leading up to the first apostrophe are the number of bits in the literal. +All digits that follow the first apostrophe are digits in the actual numeric. +Any apostophes after the first are considered standard numeric separators. -## Algorithms -The algorithms again work in the same manner as the STL. The functions provided here have the same interface as those in the STL, however under the hood, they take advantage of bit-parallelism. It should be noted that if there is an STL algorithm that is not supported yet by BitLib, you can still use the STL implementation. For example: -```cpp -using WordType = uint64_t; -bit::bit_vector bvec1 ("011111010010"); -bit::bit_vector bvec2 = bvec1; -bit::equal(bvec1.begin(), bvec1.end(), bvec2.begin(), bvec1.end()); -std::equal(bvec1.begin(), bvec1.end(), bvec2.begin(), bvec1.end()); // Also works, but much slower as it works bit-by-bit -``` +> [!IMPORTANT] +> The size is expressed in the same base as the rest of the literal -For algorithms which take a function (i.e. `bit::transform`), the function should have `WordType` as the input types as well as the return type. For example, to compute the intersection of two bitvectors: -```cpp -using WordType = uint64_t; -auto binary_op = std::bit_and(); - -// Store the AND of bitvec1 and bitvec2 in bitvec3 -auto bitret = bit::transform( - bitvec1.begin(), - bitvec1.end(), - bitvec2.begin(), - bitvec3.begin() - binary_op); -``` +### Data -## Iterators -The bit-iterators are the foundation of the library. In most cases, users will only need to work w/ the `bit::bit_vector::begin()` and `bit::bit_vector::end()` methods to obtain iterators. However, constructing a bit iterator from any address is also straightforward: -```cpp -using WordType = uint64_t; -std::array wordArr = {1,2,3,4}; -bit::bit_iterator(&(wordArr[0])); // Constructs a bit iterator starting from the first bit from the first word of the vector -bit::bit_iterator(&(wordArr[0]), 1); // Constructs a bit iterator from the second bit (position 1) of the first word of the vector -``` +Numeric literal in the base described + +> [!CAUTION] +> This literal does not support negative literals + +> [!CAUTION] +> This literal does not support literals above 64 bits -In order to grab the underlying word that a bit pointed to by a bit_iterator comes from, you can use the `bit_iterator.base()` function. +### Suffix -It is worth noting that the "position" of a bit always increases from LSB to MSB. For those looking to create their own algorithms from bit_iterators, this can be a common "gotcha". For example, shifting a word to the right by `k` will eliminate the first `k` bits of the container. This is only important to those implementing their own algorithms. `bit::shift_*` works as described in the documentation i.e. `shift_right` shifts the container towards `end()` and `shift_left` shifts the container towards `begin()`. +The literal must be followed by the user defined literal suffix: _b +### Examples + +```c++ +#include "bitlib/bit.hpp" + +auto dec_lit = 31'123456_b; // 31 bit base 10 literal +auto hex_lit = 0x1F'1E240_b; // 31 bit hex literal +auto bin_lit = 0b11111'11110001001000000_b; //31 bit binary literal +auto oct_lit = 037'361100_b; // 31 bit octal literal ``` - MSB|<-----|LSB -Position: 76543210 -Value: 01010001 --> Sequence: 10001010 +# Slice Operator -// bit::shift_right by 2 - MSB|<-----|LSB -Position: 76543210 -Value: 01000100 --> Sequence: 00100010 +All containers and views provide a slice operator which take a half-open range and +return a [mutable view](#bit_array_ref) of the given range. +Example: + +```c++ +auto lit = 0x1F'1E240_b; +auto ref = lit(4, 8); // reference the bits from [4,8) i.e. 4,5,6,7. +assert(ref == 0x4'4_b); +assert(ref == 0x4); +lit(4, 8) = 0xA; +assert(lit == 0x1F'1E2A0_b); +assert(ref(1,4) == 0x5); // array_ref can be sliced further ``` -# Documentation -Given that the majority of the library is focused on having the same interface as the C++ STL iterators, containers, and algorithms, users should use the official [STL documentation website](https://en.cppreference.com/). We do plan on adding our own documentation in the future, however. +# Policy + +Template class controlling behavior such as expansion (aka sign-extension) and truncation +The containers can be specialized with a custom policy type to +throwing an exception on loss of data or clamping instead of truncation +The default policy truncates when necessary and sign extends for conversion to/from signed integrals -# Performance Benchmarks -I used Google's [benchmark](https://github.com/google/benchmark) library for computing benchmarks. Each benchmark is formatted as `{bit, BitArray, std}::function` (size) [(alignment-tags)]. +# Owning Containers - * `bit` is for this library, `BitArray` is for the popular C-based [BitArray library](https://github.com/noporpoise/BitArray), [dynamic_bitset](https://github.com/pinam45/dynamic_bitset) is a header-only library similar to Boost's dynamic_bitset, and`std` is the standard library operating on the infamous `vector`. -* (size) denotes the size of the container in bits. `small = 1 << 8`, `medium= 1 << 16`, `large = 1 << 24`, `huge = 1 << 31` -* (alignment-tags) refers to the memory alignment of the bit-iterators. `U` means the iterator does not fall on a word boundary, `R` means the iterator is placed at random, and `A` means the iterator is aligned with a word boundary. +## bit_array -For example, `bit::rotate (large) (ARA)` refers to our library's implementation of the `rotate` algorithm operating on a container of 65536 bits, where `first` and `last` are aligned but `n_first` is selected at random. +Provides compile-time or construction time container for an array of bits. +### Compile-time: +Storage is on the stack. The number of bytes is the nearest power of two integral size. +```c++ +bit_array<11> vec_11(0x123); // Will use a uint16_t to hold the data +bit_array<65> vec_65(); // Will use a uint64_t to hold the data ``` ---------------------------------------------------------------------------------------- -Benchmark Time CPU Iterations ---------------------------------------------------------------------------------------- -bit::set (large) 1.90 ns 1.90 ns 367974893 -dynamic_bitset::set (large) 2.37 ns 2.37 ns 296837879 -bitarray::set (large) 2.19 ns 2.19 ns 319133940 -std::set (large) 2.39 ns 2.39 ns 293135332 -bit::shift_left (small) 26.8 ns 26.8 ns 25929070 -bit::shift_left (small) (UU) 22.4 ns 22.4 ns 31233265 -dynamic_bitset::shift_left (small) 13.1 ns 13.1 ns 53627207 -bitarray::shift_left (small) 38.2 ns 38.2 ns 18339126 -std::shift_left (small) 345 ns 345 ns 2029283 -bit::shift_left (large) 371224 ns 371211 ns 1886 -bit::shift_left (large) (UU) 371536 ns 371530 ns 1880 -dynamic_bitset::shift_left (large) 638896 ns 638880 ns 1097 -bitarray::shift_left (large) 3156273 ns 3156003 ns 222 -std::shift_left (large) 105227752 ns 105223527 ns 7 -bit::shift_right (small) 26.9 ns 26.9 ns 25976563 -bit::shift_right (small) (UU) 39.3 ns 39.3 ns 17962533 -dynamic_bitset::shift_right (small) 12.2 ns 12.2 ns 57419526 -bitarray::shift_right (small) 38.1 ns 38.1 ns 18325350 -std::shift_right (small) 504 ns 504 ns 1386280 -bit::shift_right (large) 413297 ns 413269 ns 1693 -bit::shift_right (large) (UU) 413692 ns 413655 ns 1682 -dynamic_bitset::shift_right (large) 557287 ns 557305 ns 1257 -bitarray::shift_right (large) 3156463 ns 3156516 ns 222 -std::shift_right (large) 210100788 ns 210083631 ns 3 -bit::reverse (small) (UU) 43.4 ns 43.4 ns 16112098 -bitarray::reverse (small) (UU) 95.1 ns 95.1 ns 7387177 -std::reverse (small) 419 ns 419 ns 1677069 -bit::reverse (large) 1245260 ns 1245160 ns 563 -bit::reverse (large) (UU) 1800771 ns 1800680 ns 389 -bitarray::reverse (large) 16899481 ns 16898587 ns 41 -bitarray::reverse (large) (UU) 22719408 ns 22720393 ns 31 -std::reverse (large) 293563397 ns 293542850 ns 2 -bit::transform(UnaryOp) (small) 8.75 ns 8.75 ns 80079214 -bit::transform(UnaryOp) (small) (UU) 16.6 ns 16.6 ns 42254961 -dynamic_bitset::transform(UnaryOp) (small) 4.00 ns 4.00 ns 169219246 -bitarray::transform(UnaryOp) (small) 8.39 ns 8.39 ns 83877004 -std::transform(UnaryOp) (small) 763 ns 763 ns 917975 -bit::transform(UnaryOp) (large) 373982 ns 373950 ns 1853 -bit::transform(UnaryOp) (large) (UU) 2059234 ns 2059268 ns 339 -dynamic_bitset::transform(UnaryOp) (large) 379368 ns 379368 ns 1805 -bitarray::transform(UnaryOp) (large) 739552 ns 739544 ns 881 -std::transform(UnaryOp) (large) 197977698 ns 197969224 ns 4 -bit::transform(BinaryOp) (small) 4.38 ns 4.38 ns 160002060 -bit::transform(BinaryOp) (small) (UU) 42.1 ns 42.1 ns 16549758 -dynamic_bitset::transform(BinaryOp) (small) 4.36 ns 4.36 ns 160692979 -bitarray::transform(BinaryOp) (small) 10.7 ns 10.7 ns 66178974 -std::transform(BinaryOp) (small) 855 ns 855 ns 832115 -bit::transform(BinaryOp) (large) 763642 ns 763574 ns 912 -bit::transform(BinaryOp) (large) (UU) 10966202 ns 10966406 ns 64 -dynamic_bitset::transform(BinaryOp) (large) 758617 ns 758574 ns 906 -bitarray::transform(BinaryOp) (large) 518286 ns 518267 ns 1177 -std::transform(BinaryOp) (large) 802270688 ns 802303941 ns 1 -bit::rotate (small) 131 ns 131 ns 16525922 -std::rotate (small) 1782 ns 1782 ns 417293 -bit::rotate (large) 7333284 ns 7333170 ns 96 -std::rotate (large) 514697313 ns 514718779 ns 1 -bit::count (small) 8.14 ns 8.14 ns 86522765 -dynamic_bitset::count (small) 6.29 ns 6.29 ns 108878018 -bitarray::count (small) 5.47 ns 5.47 ns 133692569 -std::count (small) 234 ns 234 ns 2997782 -bit::count (large) 365194 ns 365159 ns 1919 -dynamic_bitset::count (large) 365279 ns 365269 ns 1919 -bitarray::count (large) 917302 ns 917185 ns 764 -std::count (large) 58934071 ns 58931785 ns 12 -bit::swap_ranges (small) 9.58 ns 9.57 ns 73128377 -bit::swap_ranges (small) (UU) 19.7 ns 19.7 ns 35498474 -std::swap_ranges (small) 756 ns 756 ns 912041 -bit::swap_ranges (large) 852205 ns 852241 ns 821 -bit::swap_ranges (large) (UU) 5691899 ns 5692145 ns 123 -std::swap_ranges (large) 522198664 ns 522161939 ns 1 -bit::copy (small) (UU) 25.0 ns 25.0 ns 28200772 -std::copy (small) 707 ns 707 ns 990757 -bit::copy (large) (UU) 5952278 ns 5951729 ns 116 -std::copy (large) 189551338 ns 189554366 ns 4 -bit::equal (small) (UU) 13.1 ns 13.1 ns 53616228 -std::equal (small) 886 ns 886 ns 790035 -bit::equal (large) (UU) 1960399 ns 1960375 ns 357 -std::equal (large) 234389098 ns 234398907 ns 3 -bit::move (small) (UU) 23.5 ns 23.5 ns 29764745 -std::move (small) 706 ns 706 ns 992054 -bit::move (large) (UU) 5135837 ns 5135619 ns 136 -std::move (large) 188961979 ns 188953500 ns 4 -bit::copy_backward (small) (UU) 39.0 ns 39.0 ns 17977387 -std::copy_backward (small) 527 ns 527 ns 1313265 -bit::copy_backward (large) (UU) 9163333 ns 9163038 ns 76 -std::copy_backward (large) 444362971 ns 444350668 ns 2 -bit::fill (small) (UU) 6.48 ns 6.48 ns 108934237 -dynamic_bitset::fill (small) 4.79 ns 4.79 ns 146205764 -bitarray::fill (small) 14.5 ns 14.5 ns 48030428 -std::fill (small) 9.15 ns 9.15 ns 76612702 -bit::fill (large) (UU) 440400 ns 440396 ns 1590 -dynamic_bitset::fill (large) 429375 ns 429359 ns 1631 -bitarray::fill (large) 369732 ns 369736 ns 1964 -std::fill (large) 356517 ns 356488 ns 1894 -bit::find (small) (UU) 3.10 ns 3.10 ns 228714994 -dynamic_bitset::find (small) 3.05 ns 3.05 ns 229830138 -bitarray::find (small) 7.38 ns 7.38 ns 99039746 -std::find (small) 110 ns 110 ns 6311725 -bit::find (large) (UU) 182002 ns 182006 ns 3850 -dynamic_bitset::find (large) 259896 ns 259908 ns 2696 -bitarray::find (large) 252434 ns 252445 ns 2774 -std::find (large) 28570723 ns 28567762 ns 25 +The storage word type can be specified: +```c++ +bit_array<65, uint8_t> vec_65_bytes(); // 9 bytes on stack +``` + +### Construction-time +A non-resizable construction-time storage is used when the N (aka Extent) +is equal to std::dynamic_extent (similar to std::span). +Since the N template parameter is by default std::dynamic_extent this is the +default template specialization `bit_array<>`. + +```c++ +bit_array<> vec_11(11, 0x123); // 8 bytes on stack for data, 8 bytes for size +bit_array<> vec_64(65); // same stack size as above + 16 bytes in heap +``` +The storage word size is by default uintptr_t. +The container will perform small buffer optimization +when the number of bits is equal or less than `bitsof()` typically 64. + +## bit_vector +# Non-Owning Views +## bit_array_ref +## bit_span +## mdspan with bit accessors + +The std::mdspan container (C++23) can be used with a custom accessors that +use proxy pointers and references. This makes it suitable for accessing multi-dimensional +bit dense data. + +There are three flavours of accessors: + - `bit_default_accessor` which povides individual `bit_value` access which behaves like a typical stl mdspan + - `bit_word_accessor` which provides compile-time bit array value type access + - `bit_word_accessor` which provides construction-time bit array value type access +> [!INFO] +> The dynamic_extent `bit_word_accessor` requires a non-default constructor to the accessor. +> The mdspan must use the mdspan constructor which takes the +> container pointer, extent instance and accessor instance. +bit_value: +```c++ +bit_array<7*8*9> bits(); +std::mdspan< + bit::bit_value, + std::extents, + std::layout_right, + bit::bit_default_accessor +> myspan ( + &bits[0] +); +myspan[6, 7, 8] = bit::bit1; // set last bit to one +assert(bits[7*8*9-1] == bit::bit1); +``` + +Compile-time: +```c++ +bit_array<7*8*9> bits(); +std::mdspan< + bit::bit_word_accessor<7>::element_type, + std::extents, + std::layout_right, + bit::bit_word_accessor<7> +> myspan ( + &bits[0] +); +myspan[7, 8] = 0x7'7F_b; // set last 7 bit word to all ones. +assert(bits(7*8*9-7, 7*8*9) == 0x7F); +``` + +Construction-time: +```c++ +bit_array<> bits(7*8*9); +std::mdspan< + bit::bit_word_accessor<>::element_type, + std::dextents, + std::layout_right, + bit::bit_word_accessor<> +> myspan ( + &bits[0], + std::dextents{8, 9}, + bit::bit_word_accessor<>(7) +); +myspan[7, 8] = 0x7'7F_b; // set last 7 bit word to all ones. +assert(bits(7*8*9-7, 7*8*9) == 0x7F); ``` +# Iterators and References +## bit_iterator +## bit_reference +## bit_word_pointer_adapter +## bit_word_reference_adapter +# Algorithms + - accumulate + - copy_backward + - copy + - count + - equal + - fill + - find + - move + - reverse + - rotate + - shift + - swap_ranges + - to_from_string + - transform + +# License + +This open source library is license under [BSD 3-Clause License](./LICENSE). + +> [!IMPORTANT] +> This library uses libpopcnt which has its own [BSD 2-Clause License](include/bitlib/bit-algorithms/libpopcnt.h) diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 709c3b21..3bcd87e9 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -1,3 +1,5 @@ +cmake_minimum_required(VERSION 3.31) + set(BENCHMARK_ENABLE_GTEST_TESTS OFF) include(FetchContent) diff --git a/doc/benchmarks.md b/doc/benchmarks.md new file mode 100644 index 00000000..e799d361 --- /dev/null +++ b/doc/benchmarks.md @@ -0,0 +1,118 @@ + +# Performance Benchmarks +I used Google's [benchmark](https://github.com/google/benchmark) library for computing benchmarks. Each benchmark is formatted as `{bit, BitArray, std}::function` (size) [(alignment-tags)]. + + * `bit` is for this library, `BitArray` is for the popular C-based [BitArray library](https://github.com/noporpoise/BitArray), [dynamic_bitset](https://github.com/pinam45/dynamic_bitset) is a header-only library similar to Boost's dynamic_bitset, and`std` is the standard library operating on the infamous `vector`. +* (size) denotes the size of the container in bits. `small = 1 << 8`, `medium= 1 << 16`, `large = 1 << 24`, `huge = 1 << 31` +* (alignment-tags) refers to the memory alignment of the bit-iterators. `U` means the iterator does not fall on a word boundary, `R` means the iterator is placed at random, and `A` means the iterator is aligned with a word boundary. + +For example, `bit::rotate (large) (ARA)` refers to our library's implementation of the `rotate` algorithm operating on a container of 65536 bits, where `first` and `last` are aligned but `n_first` is selected at random. + +``` +--------------------------------------------------------------------------------------- +Benchmark Time CPU Iterations +--------------------------------------------------------------------------------------- +bit::set (large) 1.90 ns 1.90 ns 367974893 +dynamic_bitset::set (large) 2.37 ns 2.37 ns 296837879 +bitarray::set (large) 2.19 ns 2.19 ns 319133940 +std::set (large) 2.39 ns 2.39 ns 293135332 +bit::shift_left (small) 26.8 ns 26.8 ns 25929070 +bit::shift_left (small) (UU) 22.4 ns 22.4 ns 31233265 +dynamic_bitset::shift_left (small) 13.1 ns 13.1 ns 53627207 +bitarray::shift_left (small) 38.2 ns 38.2 ns 18339126 +std::shift_left (small) 345 ns 345 ns 2029283 +bit::shift_left (large) 371224 ns 371211 ns 1886 +bit::shift_left (large) (UU) 371536 ns 371530 ns 1880 +dynamic_bitset::shift_left (large) 638896 ns 638880 ns 1097 +bitarray::shift_left (large) 3156273 ns 3156003 ns 222 +std::shift_left (large) 105227752 ns 105223527 ns 7 +bit::shift_right (small) 26.9 ns 26.9 ns 25976563 +bit::shift_right (small) (UU) 39.3 ns 39.3 ns 17962533 +dynamic_bitset::shift_right (small) 12.2 ns 12.2 ns 57419526 +bitarray::shift_right (small) 38.1 ns 38.1 ns 18325350 +std::shift_right (small) 504 ns 504 ns 1386280 +bit::shift_right (large) 413297 ns 413269 ns 1693 +bit::shift_right (large) (UU) 413692 ns 413655 ns 1682 +dynamic_bitset::shift_right (large) 557287 ns 557305 ns 1257 +bitarray::shift_right (large) 3156463 ns 3156516 ns 222 +std::shift_right (large) 210100788 ns 210083631 ns 3 +bit::reverse (small) (UU) 43.4 ns 43.4 ns 16112098 +bitarray::reverse (small) (UU) 95.1 ns 95.1 ns 7387177 +std::reverse (small) 419 ns 419 ns 1677069 +bit::reverse (large) 1245260 ns 1245160 ns 563 +bit::reverse (large) (UU) 1800771 ns 1800680 ns 389 +bitarray::reverse (large) 16899481 ns 16898587 ns 41 +bitarray::reverse (large) (UU) 22719408 ns 22720393 ns 31 +std::reverse (large) 293563397 ns 293542850 ns 2 +bit::transform(UnaryOp) (small) 8.75 ns 8.75 ns 80079214 +bit::transform(UnaryOp) (small) (UU) 16.6 ns 16.6 ns 42254961 +dynamic_bitset::transform(UnaryOp) (small) 4.00 ns 4.00 ns 169219246 +bitarray::transform(UnaryOp) (small) 8.39 ns 8.39 ns 83877004 +std::transform(UnaryOp) (small) 763 ns 763 ns 917975 +bit::transform(UnaryOp) (large) 373982 ns 373950 ns 1853 +bit::transform(UnaryOp) (large) (UU) 2059234 ns 2059268 ns 339 +dynamic_bitset::transform(UnaryOp) (large) 379368 ns 379368 ns 1805 +bitarray::transform(UnaryOp) (large) 739552 ns 739544 ns 881 +std::transform(UnaryOp) (large) 197977698 ns 197969224 ns 4 +bit::transform(BinaryOp) (small) 4.38 ns 4.38 ns 160002060 +bit::transform(BinaryOp) (small) (UU) 42.1 ns 42.1 ns 16549758 +dynamic_bitset::transform(BinaryOp) (small) 4.36 ns 4.36 ns 160692979 +bitarray::transform(BinaryOp) (small) 10.7 ns 10.7 ns 66178974 +std::transform(BinaryOp) (small) 855 ns 855 ns 832115 +bit::transform(BinaryOp) (large) 763642 ns 763574 ns 912 +bit::transform(BinaryOp) (large) (UU) 10966202 ns 10966406 ns 64 +dynamic_bitset::transform(BinaryOp) (large) 758617 ns 758574 ns 906 +bitarray::transform(BinaryOp) (large) 518286 ns 518267 ns 1177 +std::transform(BinaryOp) (large) 802270688 ns 802303941 ns 1 +bit::rotate (small) 131 ns 131 ns 16525922 +std::rotate (small) 1782 ns 1782 ns 417293 +bit::rotate (large) 7333284 ns 7333170 ns 96 +std::rotate (large) 514697313 ns 514718779 ns 1 +bit::count (small) 8.14 ns 8.14 ns 86522765 +dynamic_bitset::count (small) 6.29 ns 6.29 ns 108878018 +bitarray::count (small) 5.47 ns 5.47 ns 133692569 +std::count (small) 234 ns 234 ns 2997782 +bit::count (large) 365194 ns 365159 ns 1919 +dynamic_bitset::count (large) 365279 ns 365269 ns 1919 +bitarray::count (large) 917302 ns 917185 ns 764 +std::count (large) 58934071 ns 58931785 ns 12 +bit::swap_ranges (small) 9.58 ns 9.57 ns 73128377 +bit::swap_ranges (small) (UU) 19.7 ns 19.7 ns 35498474 +std::swap_ranges (small) 756 ns 756 ns 912041 +bit::swap_ranges (large) 852205 ns 852241 ns 821 +bit::swap_ranges (large) (UU) 5691899 ns 5692145 ns 123 +std::swap_ranges (large) 522198664 ns 522161939 ns 1 +bit::copy (small) (UU) 25.0 ns 25.0 ns 28200772 +std::copy (small) 707 ns 707 ns 990757 +bit::copy (large) (UU) 5952278 ns 5951729 ns 116 +std::copy (large) 189551338 ns 189554366 ns 4 +bit::equal (small) (UU) 13.1 ns 13.1 ns 53616228 +std::equal (small) 886 ns 886 ns 790035 +bit::equal (large) (UU) 1960399 ns 1960375 ns 357 +std::equal (large) 234389098 ns 234398907 ns 3 +bit::move (small) (UU) 23.5 ns 23.5 ns 29764745 +std::move (small) 706 ns 706 ns 992054 +bit::move (large) (UU) 5135837 ns 5135619 ns 136 +std::move (large) 188961979 ns 188953500 ns 4 +bit::copy_backward (small) (UU) 39.0 ns 39.0 ns 17977387 +std::copy_backward (small) 527 ns 527 ns 1313265 +bit::copy_backward (large) (UU) 9163333 ns 9163038 ns 76 +std::copy_backward (large) 444362971 ns 444350668 ns 2 +bit::fill (small) (UU) 6.48 ns 6.48 ns 108934237 +dynamic_bitset::fill (small) 4.79 ns 4.79 ns 146205764 +bitarray::fill (small) 14.5 ns 14.5 ns 48030428 +std::fill (small) 9.15 ns 9.15 ns 76612702 +bit::fill (large) (UU) 440400 ns 440396 ns 1590 +dynamic_bitset::fill (large) 429375 ns 429359 ns 1631 +bitarray::fill (large) 369732 ns 369736 ns 1964 +std::fill (large) 356517 ns 356488 ns 1894 +bit::find (small) (UU) 3.10 ns 3.10 ns 228714994 +dynamic_bitset::find (small) 3.05 ns 3.05 ns 229830138 +bitarray::find (small) 7.38 ns 7.38 ns 99039746 +std::find (small) 110 ns 110 ns 6311725 +bit::find (large) (UU) 182002 ns 182006 ns 3850 +dynamic_bitset::find (large) 259896 ns 259908 ns 2696 +bitarray::find (large) 252434 ns 252445 ns 2774 +std::find (large) 28570723 ns 28567762 ns 25 +``` + diff --git a/include/bitlib/bit-containers/bit_mdspan_accessor.hpp b/include/bitlib/bit-containers/bit_mdspan_accessor.hpp index 9b8cd82b..80076231 100644 --- a/include/bitlib/bit-containers/bit_mdspan_accessor.hpp +++ b/include/bitlib/bit-containers/bit_mdspan_accessor.hpp @@ -47,7 +47,9 @@ struct bit_word_accessor : private detail::container_size_storagesize(); } - constexpr bit_word_accessor() : detail::container_size_storage() {} + constexpr bit_word_accessor() + requires(N != std::dynamic_extent) + : detail::container_size_storage() {} constexpr bit_word_accessor(const size_t& size) requires(N == std::dynamic_extent) : detail::container_size_storage(size) {} From 212f3b7190eea49393f9884d878a79992d8d89c8 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sun, 6 Jul 2025 20:38:57 -0400 Subject: [PATCH 73/85] Rearrange TOC and fix links --- README.md | 80 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 816efc02..50cf7ddd 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,27 @@ ![Actions](https://github.com/PeterCDMcLean/BitLib/actions/workflows/cmake-multi-platform.yml/badge.svg?branch=master)[![Coverage Status](https://coveralls.io/repos/github/PeterCDMcLean/BitLib/badge.svg?branch=master)](https://coveralls.io/github/PeterCDMcLean/BitLib?branch=master) -# Overview +# Overview This project provides convenient and efficient stl-like containers and algorithms for bit access. +# Example + +```c++ + +auto fp_half_num = 0x10'3DAE_b; +auto exponent = fp_half_num(10, 15); +auto mantissa = fp_half_num(0, 10); +auto twos_complement_exponent = static_cast(exponent) - ((1 << 5)-1); + +``` + # Table of Contents -- [Contributors](#contributors) -- [Requirements](#requirements) +- [Overview](#overview) - [Example](#example) +- [Table of Contents](#table-of-contents) +- [Requirements](#requirements) - [CMake](#cmake) - [Sized Literal](#literal) - [Slice Operator](#slice-operator) @@ -29,23 +41,17 @@ This project provides convenient and efficient stl-like containers and algorithm - [bit_word_reference_adapter](#bit_word_reference_adapter) - [Algorithms](#algorithms) - [Testing](#testing) +- [Contributors](#contributors) - [License](#license) -# Contributors -- Vincent Reverdy -- Bryce Kille -- Peter McLean - -# Requirements +# Requirements - _gcc_ or _clang_ - `C++23` - cmake verison 3.20. * cmake version 3.31 if compiling bitlib's benchmark suite -# Example - -# CMake +# CMake ### options bitlib provides the following cmake options and their defaults ```cmake @@ -91,7 +97,7 @@ target_sources(example PRIVATE example.cpp) target_link_libraries(example PRIVATE bitlib::bitlib) ``` -# Literal +# Literal This provides a sized literal for compile-time bit_array. @@ -150,7 +156,7 @@ auto bin_lit = 0b11111'11110001001000000_b; //31 bit binary literal auto oct_lit = 037'361100_b; // 31 bit octal literal ``` -# Slice Operator +# Slice Operator All containers and views provide a slice operator which take a half-open range and return a [mutable view](#bit_array_ref) of the given range. @@ -166,7 +172,7 @@ assert(lit == 0x1F'1E2A0_b); assert(ref(1,4) == 0x5); // array_ref can be sliced further ``` -# Policy +# Policy Template class controlling behavior such as expansion (aka sign-extension) and truncation The containers can be specialized with a custom policy type to @@ -174,9 +180,13 @@ throwing an exception on loss of data or clamping instead of truncation The default policy truncates when necessary and sign extends for conversion to/from signed integrals -# Owning Containers +# Owning Containers + + - `bit::bit_array` is an alias for `bit::array` + - `bit::array` is a fixed-size and non-rebindable container with similar API to `std::array` + - `bit::bit_vector` is a resizable container with similar API to `std::vector` -## bit_array +## bit_array Provides compile-time or construction time container for an array of bits. @@ -205,13 +215,17 @@ The storage word size is by default uintptr_t. The container will perform small buffer optimization when the number of bits is equal or less than `bitsof()` typically 64. -## bit_vector -# Non-Owning Views -## bit_array_ref -## bit_span -## mdspan with bit accessors +## bit_vector +# Non-Owning Views +## bit_array_ref +## bit_span +## mdspan with bit accessors + +> [!TIP] +> When building bitlib unit tests/benchmark, +> cmake mdspan support can be enabled/disabled with cmake variable `ENABLE_MDSPAN` -The std::mdspan container (C++23) can be used with a custom accessors that +The `std::mdspan` container (C++23) can be used with a custom accessors that use proxy pointers and references. This makes it suitable for accessing multi-dimensional bit dense data. @@ -223,6 +237,7 @@ There are three flavours of accessors: > The dynamic_extent `bit_word_accessor` requires a non-default constructor to the accessor. > The mdspan must use the mdspan constructor which takes the > container pointer, extent instance and accessor instance. + bit_value: ```c++ bit_array<7*8*9> bits(); @@ -270,12 +285,12 @@ myspan[7, 8] = 0x7'7F_b; // set last 7 bit word to all ones. assert(bits(7*8*9-7, 7*8*9) == 0x7F); ``` -# Iterators and References -## bit_iterator -## bit_reference -## bit_word_pointer_adapter -## bit_word_reference_adapter -# Algorithms +# Iterators and References +## bit_iterator +## bit_reference +## bit_word_pointer_adapter +## bit_word_reference_adapter +# Algorithms - accumulate - copy_backward - copy @@ -291,7 +306,12 @@ assert(bits(7*8*9-7, 7*8*9) == 0x7F); - to_from_string - transform -# License +# Contributors +- Vincent Reverdy +- Bryce Kille +- Peter McLean + +# License This open source library is license under [BSD 3-Clause License](./LICENSE). From 47ad8d7057327e734bd6c59873beb115b5d80f7e Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sun, 6 Jul 2025 20:49:43 -0400 Subject: [PATCH 74/85] Add include to example --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 50cf7ddd..24d2a9a0 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ This project provides convenient and efficient stl-like containers and algorithm # Example ```c++ +#include "bitlib/bitlib.hpp" auto fp_half_num = 0x10'3DAE_b; auto exponent = fp_half_num(10, 15); From 53f3aacaa2646e3062fc2061fadda252456ca2a9 Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Fri, 11 Jul 2025 18:58:26 +0000 Subject: [PATCH 75/85] Remove unused functions from bit_details. Replace some with stl (#22) --- include/bitlib/bit-algorithms/count.hpp | 70 ++- include/bitlib/bit-algorithms/find.hpp | 30 +- include/bitlib/bit-iterator/bit_details.hpp | 501 ++---------------- include/bitlib/bit-iterator/bit_reference.hpp | 5 +- 4 files changed, 86 insertions(+), 520 deletions(-) diff --git a/include/bitlib/bit-algorithms/count.hpp b/include/bitlib/bit-algorithms/count.hpp index 321edec8..3d69a011 100644 --- a/include/bitlib/bit-algorithms/count.hpp +++ b/include/bitlib/bit-algorithms/count.hpp @@ -57,7 +57,7 @@ count( if (first.position() != 0) { word_type first_value = lsr(*first.base(), first.position()); - result = _popcnt(first_value); + result = std::popcount(static_cast>(first_value)); ++it; } // The SIMD implementation here is actually slower than the standard @@ -67,46 +67,44 @@ count( //{ //// Align to boundary //for (; it != last.base() && !is_aligned(&(*it), 64); ++it) { - //result += _popcnt(*it); - //} - - //// SIMD - //hn::ScalableTag d; - //for (; std::distance(it, last.base()) >= hn::Lanes(d); it += hn::Lanes(d)) - //{ - //const auto popcntV = hn::PopulationCount(hn::Load(d, &*it)); - //result += hn::ReduceSum(d, popcntV); - //} - - //// Remaining - //for (; it != last.base(); ++it) { - //result += _popcnt(*it); - //} - //} else -//#endif - { - // std:: version - //result += std::transform_reduce( - //it, - //last.base(), - //0, - //std::plus{}, - //[](word_type word) {return _popcnt(word); } - //); - - // libpopcnt - result += popcnt(&*it, (digits / 8) * std::distance(it, last.base())); - } + //result += std::popcount(*it); + //} + + //// SIMD + //hn::ScalableTag d; + //for (; std::distance(it, last.base()) >= hn::Lanes(d); it += hn::Lanes(d)) + //{ + //const auto popcntV = hn::PopulationCount(hn::Load(d, &*it)); + //result += hn::ReduceSum(d, popcntV); + //} + + //// Remaining + //for (; it != last.base(); ++it) { + //result += std::popcount(*it); + //} + //} else + //#endif + { + // std:: version + //result += std::transform_reduce( + //it, + //last.base(), + //0, + //std::plus{}, + //[](word_type word) {return std::popcount(word); } + //); + + // libpopcnt + result += popcnt(&*it, (digits / 8) * std::distance(it, last.base())); + } if (last.position() != 0) { word_type last_value = *last.base() << (digits - last.position()); - result += _popcnt(last_value); + result += std::popcount(static_cast>(last_value)); } // Computation when bits belong to the same underlying word } else { - result = _popcnt( - _bextr(*first.base(), first.position(), last.position() - - first.position()) - ); + result = std::popcount(static_cast>( + _bextr(*first.base(), first.position(), last.position() - first.position()))); } // Negates when the number of zero bits is requested diff --git a/include/bitlib/bit-algorithms/find.hpp b/include/bitlib/bit-algorithms/find.hpp index 0227d3d0..95b2c2be 100644 --- a/include/bitlib/bit-algorithms/find.hpp +++ b/include/bitlib/bit-algorithms/find.hpp @@ -9,6 +9,7 @@ // ============================== PREAMBLE ================================== // // C++ standard library +#include #include // Project sources #include "bitlib/bit-iterator/bit.hpp" @@ -37,6 +38,7 @@ constexpr bit_iterator find( ) { using word_type = typename bit_iterator::word_type; + using uword_type = std::make_unsigned_t; using size_type = typename bit_iterator::size_type; const std::size_t digits = binary_digits::value; @@ -48,8 +50,8 @@ constexpr bit_iterator find( if (!is_first_aligned) { word_type shifted_first = lsr(*first.base(), first.position()); size_type num_trailing_complementary_bits = (bv == bit0) - ? _tzcnt(static_cast(~shifted_first)) - : _tzcnt(static_cast(shifted_first)); + ? std::countr_zero(static_cast(~shifted_first)) + : std::countr_zero(static_cast(shifted_first)); if (std::next(first.base(), is_last_aligned) == last.base()) { return first + std::min(num_trailing_complementary_bits, static_cast(distance(first, last))); } else if (num_trailing_complementary_bits + first.position() < digits) { @@ -72,8 +74,8 @@ constexpr bit_iterator find( } size_type num_trailing_complementary_bits = (bv == bit0) - ? _tzcnt(static_cast(~*it)) - : _tzcnt(static_cast(*it)); + ? std::countr_zero(static_cast(~*it)) + : std::countr_zero(static_cast(*it)); return bit_iterator(it, (size_type) num_trailing_complementary_bits); } @@ -92,8 +94,8 @@ constexpr bit_iterator find( { it += hn::FindKnownFirstTrue(d, found); size_type num_trailing_complementary_bits = (bv == bit0) - ? _tzcnt(static_cast(~*it)) - : _tzcnt(static_cast(*it)); + ? std::countr_zero(static_cast(~*it)) + : std::countr_zero(static_cast(*it)); return bit_iterator(it, (size_type) num_trailing_complementary_bits); } } @@ -106,18 +108,18 @@ constexpr bit_iterator find( } if (it != last.base()) { - size_type num_trailing_complementary_bits = (bv == bit0) - ? _tzcnt(static_cast(~*it)) - : _tzcnt(static_cast(*it)); - return bit_iterator(it, static_cast(num_trailing_complementary_bits)); + size_type num_trailing_complementary_bits = (bv == bit0) + ? std::countr_zero(static_cast(~*it)) + : std::countr_zero(static_cast(*it)); + return bit_iterator(it, static_cast(num_trailing_complementary_bits)); } // Deal with any unaligned boundaries if (!is_last_aligned) { - size_type num_trailing_complementary_bits = (bv == bit0) - ? _tzcnt(static_cast(~*it)) - : _tzcnt(static_cast(*it)); - return bit_iterator(it, static_cast(std::min(num_trailing_complementary_bits, last.position()))); + size_type num_trailing_complementary_bits = (bv == bit0) + ? std::countr_zero(static_cast(~*it)) + : std::countr_zero(static_cast(*it)); + return bit_iterator(it, static_cast(std::min(num_trailing_complementary_bits, last.position()))); } return last; } diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index cac7b81f..150b1aaf 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -15,9 +15,15 @@ // ================================ PREAMBLE ================================ // // C++ standard library + +#if __has_include() #include +#else +#define NO_X86_INTRINSICS +#endif #include +#include #include #include #include @@ -121,170 +127,6 @@ struct _cv_iterator_traits }; /* ************************************************************************** */ -#if 0 -/* *********** IMPLEMENTATION DETAILS: NARROWEST AND WIDEST TYPES *********** */ -// Narrowest type structure declaration -template -struct _narrowest_type; - -// Narrowest type structure specialization: selects the only passed type -template -struct _narrowest_type -: std::common_type -{ - static_assert(binary_digits::value, ""); -}; - -// Narrowest type structure specialization: selects the type with less bits -template -struct _narrowest_type -: _narrowest_type< - typename std::conditional< - (binary_digits::value < binary_digits::value), - T, - typename std::conditional< - (binary_digits::value > binary_digits::value), - U, - typename std::common_type::type - >::type - >::type -> -{ -}; - -// Narrowest type structure specialization: recursively selects the right type -template -struct _narrowest_type -: _narrowest_type::type> -{ -}; - -// Narrowest type alias -template -using _narrowest_type_t = typename _narrowest_type::type; - -// Widest type structure declaration -template -struct _widest_type; - -// Widest type structure specialization: selects the only passed type -template -struct _widest_type -: std::common_type -{ - static_assert(binary_digits::value, ""); -}; - -// Widest type structure specialization: selects the type with more bits -template -struct _widest_type -: _widest_type< - typename std::conditional< - (binary_digits::value > binary_digits::value), - T, - typename std::conditional< - (binary_digits::value < binary_digits::value), - U, - typename std::common_type::type - >::type - >::type -> -{ -}; - -// Widest type structure specialization: recursively selects the right type -template -struct _widest_type -: _widest_type::type> -{ -}; - -// Widest type alias -template -using _widest_type_t = typename _widest_type::type; -/* ************************************************************************** */ - - - -/* ************ IMPLEMENTATION DETAILS: NARROWER AND WIDER TYPES ************ */ -// Narrower type structure definition -template -struct _narrower_type -{ - using tuple = std::tuple< - unsigned long long int, - unsigned long int, - unsigned int, - unsigned short int, - unsigned char - >; - using lhs_bits = binary_digits; - using rhs_bits = binary_digits::type>; - using type = typename std::conditional< - (lhs_bits::value > rhs_bits::value), - typename std::tuple_element::type, - typename std::conditional< - (I + 1 < std::tuple_size::value), - typename _narrower_type< - T, - (I + 1 < std::tuple_size::value ? I + 1 : -1) - >::type, - typename std::tuple_element::type - >::type - >::type; -}; - -// Narrower type structure specialization: not found -template -struct _narrower_type -{ - using type = T; -}; - -// Narrower type alias -template -using _narrower_type_t = typename _narrower_type::type; - -// Wider type structure definition -template -struct _wider_type -{ - using tuple = std::tuple< - unsigned char, - unsigned short int, - unsigned int, - unsigned long int, - unsigned long long int - >; - using lhs_bits = binary_digits; - using rhs_bits = binary_digits::type>; - using type = typename std::conditional< - (lhs_bits::value < rhs_bits::value), - typename std::tuple_element::type, - typename std::conditional< - (I + 1 < std::tuple_size::value), - typename _narrower_type< - T, - (I + 1 < std::tuple_size::value ? I + 1 : -1) - >::type, - typename std::tuple_element::type - >::type - >::type; -}; - -// Wider type structure specialization: not found -template -struct _wider_type -{ - using type = T; -}; - -// Wider type alias -template -using _wider_type_t = typename _wider_type::type; -/* ************************************************************************** */ -#endif - /* exact_floor_integral is used to determine the exact integral type that a proxy reference can be implicitly converted to. @@ -338,54 +180,12 @@ template constexpr bool _assert_range_viability(Iterator first, Iterator last); /* ************************************************************************** */ - - -/* ****************** IMPLEMENTATION DETAILS: INSTRUCTIONS ****************** */ -// Population count -template -constexpr T _popcnt(T src) noexcept; -template -constexpr T _popcnt(T src, X...) noexcept; - -// Leading zeros count -template -constexpr T _lzcnt(T src) noexcept; -template -constexpr T _lzcnt(T src, X...) noexcept; - -// Trailing zeros count -template -constexpr T _tzcnt(T src) noexcept; -template -constexpr T _tzcnt(T src, X...) noexcept; - // Bit field extraction template constexpr T _bextr(T src, T start, T len) noexcept; template constexpr T _bextr(T src, T start, T len, X...) noexcept; -#if 0 -// Parallel bits deposit -template -constexpr T _pdep(T src, T msk) noexcept; -template -constexpr T _pdep(T src, T msk, X...) noexcept; - -// Parallel bits extract -template -constexpr T _pext(T src, T msk) noexcept; -template -constexpr T _pext(T src, T msk, X...) noexcept; - -// Byte swap -template -constexpr T _byteswap(T src) noexcept; -template -constexpr T _byteswap(T src, X...) noexcept; - -#endif - // Bit swap template constexpr T _bitswap(T src) noexcept; @@ -402,10 +202,6 @@ constexpr void _bitexch(T& src0, T& src1, S start, S len) noexcept; template constexpr void _bitexch(T& src0, T& src1, S start0, S start1, S len) noexcept; -// Bit compare -template -constexpr T _bitcmp(T src0, T src1, T start0, T start1, T len) noexcept; - // Double precision shift left template constexpr T _shld(T dst, T src, T cnt) noexcept; @@ -414,26 +210,6 @@ constexpr T _shld(T dst, T src, T cnt) noexcept; template constexpr T _shrd(T dst, T src, T cnt) noexcept; -// Add carry -template -using _supports_adc = decltype(__builtin_ia32_addcarryx_u64(T()...)); -template > -constexpr C _addcarry(C carry, T src0, T src1, T* dst) noexcept; -template -constexpr C _addcarry(C carry, T src0, T src1, T* dst, X...) noexcept; - -// Sub borrow -template -using _supports_sbb = decltype(__builtin_ia32_sbb_u64(T()...)); -template -using _supports_sbb_alt = decltype(__builtin_ia32_subborrow_u64(T()...)); -template > -constexpr B _subborrow(B borrow, T src0, T src1, T* dst) noexcept; -template > -constexpr B _subborrow(const B& borrow, T src0, T src1, T* dst) noexcept; -template -constexpr B _subborrow(B borrow, T src0, T src1, T* dst, X...) noexcept; - // Multiword multiply template constexpr T _mulx(T src0, T src1, T* hi) noexcept; @@ -449,6 +225,9 @@ constexpr T lsr(const T val, const size_type shift) { return static_cast(static_cast>(val) >> shift); } +/* +Logic shift right when `val` operand is a proxy reference +*/ template constexpr exact_floor_integral_t lsr(const T val, const size_type shift) { static_assert(!std::is_same_v, void>, @@ -489,112 +268,6 @@ constexpr bool _assert_range_viability(Iterator first, Iterator last) { } // -------------------------------------------------------------------------- // -// --------- IMPLEMENTATION DETAILS: INSTRUCTIONS: POPULATION COUNT --------- // -// Counts the number of bits set to 1 with compiler intrinsics -template -constexpr T _popcnt(T src) noexcept { - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - if (digits <= std::numeric_limits::digits) { - src = __builtin_popcount(static_cast>(src)); - } else if (digits <= std::numeric_limits::digits) { - src = __builtin_popcountl(static_cast>(src)); - } else if (digits <= std::numeric_limits::digits) { - src = __builtin_popcountll(static_cast>(src)); - } else { - src = _popcnt(src, std::ignore); - } - return src; -} - -// Counts the number of bits set to 1 without compiler intrinsics -template -constexpr T _popcnt(T src, X...) noexcept { - static_assert(binary_digits::value, ""); - T dst = T(); - for (dst = T(); src; src = lsr(src, 1)) { - dst += src & 1; - } - return dst; -} -// -------------------------------------------------------------------------- // - -// ------- IMPLEMENTATION DETAILS: INSTRUCTIONS: LEADING ZEROS COUNT -------- // -// Counts the number of leading zeros with compiler intrinsics -template -constexpr T _lzcnt(T src) noexcept { - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - if (digits < std::numeric_limits::digits) { - dst = src ? __builtin_clz(src) - (std::numeric_limits::digits - digits) - : digits; - } else if (digits == std::numeric_limits::digits) { - dst = src ? __builtin_clz(src) : digits; - } else if (digits < std::numeric_limits::digits) { - dst = src ? __builtin_clzl(src) - (std::numeric_limits::digits - digits) - : digits; - } else if (digits == std::numeric_limits::digits) { - dst = src ? __builtin_clzl(src) : digits; - } else if (digits < std::numeric_limits::digits) { - dst = src ? __builtin_clzll(src) - (std::numeric_limits::digits - digits) - : digits; - } else if (digits == std::numeric_limits::digits) { - dst = src ? __builtin_clzll(src) : digits; - } else { - dst = _lzcnt(src, std::ignore); - } - return dst; -} - -// Counts the number of leading zeros without compiler intrinsics -template -constexpr T _lzcnt(T src, X...) noexcept { - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = src != T(); - while ((src = lsr(src, 1))) { - ++dst; - } - return digits - dst; -} -// -------------------------------------------------------------------------- // - -// ------- IMPLEMENTATION DETAILS: INSTRUCTIONS: TRAILING ZEROS COUNT ------- // -// Counts the number of trailing zeros with compiler intrinsics -template -constexpr T _tzcnt(T src) noexcept { - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - if (digits <= std::numeric_limits::digits) { - dst = src ? __builtin_ctz(src) : digits; - } else if (digits <= std::numeric_limits::digits) { - dst = src ? __builtin_ctzl(src) : digits; - } else if (digits <= std::numeric_limits::digits) { - dst = src ? __builtin_ctzll(src) : digits; - } else { - dst = _tzcnt(src, std::ignore); - } - return dst; -} - -// Counts the number of trailing zeros without compiler intrinsics -template -constexpr T _tzcnt(T src, X...) noexcept { - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = digits; - if (src) { - src = lsr((src ^ (src - 1)), 1); - for (dst = T(); src; dst++) { - src = lsr(src, 1); - } - } - return dst; -} -// -------------------------------------------------------------------------- // - // ------- IMPLEMENTATION DETAILS: INSTRUCTIONS: BIT FIELD EXTRACTION ------- // // Extacts to lsbs a field of contiguous bits with compiler intrinsics template @@ -623,128 +296,6 @@ constexpr T _bextr(T src, T start, T len, X...) noexcept { } // -------------------------------------------------------------------------- // -#if 0 -// ------- IMPLEMENTATION DETAILS: INSTRUCTIONS: PARALLEL BIT DEPOSIT ------- // -// Deposits bits according to a mask with compiler instrinsics -template -constexpr T _pdep(T src, T msk) noexcept { - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - if (digits <= std::numeric_limits::digits) { - dst = _pdep_u32(src, msk); - } else if (digits <= std::numeric_limits::digits) { - dst = _pdep_u64(src, msk); - } else { - dst = _pdep(src, msk, std::ignore); - } - return dst; -} - -// Deposits bits according to a mask without compiler instrinsics -template -constexpr T _pdep(T src, T msk, X...) noexcept { - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - T cnt = T(); - while (msk) { - dst = lsr(dst, 1); - if (msk & 1) { - dst |= src << (digits - 1); - src = lsr(src, 1); - } - msk = lsr(msk, 1); - ++cnt; - } - dst = lsr(dst, (digits - cnt) * (cnt > 0)); - return dst; -} -// -------------------------------------------------------------------------- // - -// ------- IMPLEMENTATION DETAILS: INSTRUCTIONS: PARALLEL BIT EXTRACT ------- // -// Extracts bits according to a mask with compiler instrinsics -template -constexpr T _pext(T src, T msk) noexcept { - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - if (digits <= std::numeric_limits::digits) { - dst = _pext_u32(src, msk); - } else if (digits <= std::numeric_limits::digits) { - dst = _pext_u64(src, msk); - } else { - dst = _pext(src, msk, std::ignore); - } - return dst; -} - -// Extracts bits according to a mask without compiler instrinsics -template -constexpr T _pext(T src, T msk, X...) noexcept { - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - T cnt = T(); - while (msk) { - if (msk & 1) { - dst = lsr(dst, 1); - dst |= src << (digits - 1); - ++cnt; - } - src = lsr(src, 1); - msk = lsr(msk, 1); - } - dst = lsr(dst, (digits - cnt) * (cnt > 0)); - return dst; -} -// -------------------------------------------------------------------------- // - -// ------------ IMPLEMENTATION DETAILS: INSTRUCTIONS: BYTE SWAP ------------- // -// Reverses the order of the underlying bytes with compiler intrinsics -template -constexpr T _byteswap(T src) noexcept { - static_assert(binary_digits::value, ""); - using byte_t = unsigned char; - constexpr T digits = sizeof(T) * std::numeric_limits::digits; - std::uint64_t tmp64 = 0; - std::uint64_t* ptr64 = nullptr; - if (std::is_same::value) { - ptr64 = reinterpret_cast(&src); - tmp64 = __builtin_bswap64(*ptr64); - *ptr64 = __builtin_bswap64(*(ptr64 + 1)); - *(ptr64 + 1) = tmp64; - } else if (digits == std::numeric_limits::digits) { - src = __builtin_bswap16(src); - } else if (digits == std::numeric_limits::digits) { - src = __builtin_bswap32(src); - } else if (digits == std::numeric_limits::digits) { - src = __builtin_bswap64(src); - } else if (digits > std::numeric_limits::digits) { - src = _byteswap(src, std::ignore); - } - return src; -} - -// Reverses the order of the underlying bytes without compiler intrinsics -template -constexpr T _byteswap(T src, X...) noexcept { - static_assert(binary_digits::value, ""); - using byte_t = unsigned char; - constexpr T half = sizeof(T) / 2; - constexpr T end = sizeof(T) - 1; - unsigned char* bytes = reinterpret_cast(&src); - unsigned char byte = 0; - for (T i = T(); i < half; ++i) { - byte = bytes[i]; - bytes[i] = bytes[end - i]; - bytes[end - i] = byte; - } - return src; -} -// -------------------------------------------------------------------------- // -#endif - // ------------- IMPLEMENTATION DETAILS: INSTRUCTIONS: BIT SWAP ------------- // // Reverses the order of the bits with or without of compiler intrinsics template @@ -760,7 +311,7 @@ constexpr T _bitswap(T src) noexcept { constexpr bool is_size1 = sizeof(T) == 1; constexpr bool is_byte = digits == std::numeric_limits::digits; constexpr bool is_octet = std::numeric_limits::digits == 8; - constexpr bool is_pow2 = _popcnt(digits, ignore) == 1; + constexpr bool is_pow2 = std::has_single_bit(static_cast>(digits)); T dst = src; T i = digits - 1; if (is_size1 && is_byte && is_octet) { @@ -908,15 +459,6 @@ constexpr void _bitexch(T& src0, T& src1, S start0, S start1, S len) noexcept // clang-format on // -------------------------------------------------------------------------- // -// ----------- IMPLEMENTATION DETAILS: INSTRUCTIONS: BIT COMPARE ------------ // -// Compares a subsequence of bits within src0 and src1 and returns 0 if equal -template -constexpr T _bitcmp(T src0, T src1, T start0, T start1, T len) noexcept { - static_assert(binary_digits::value, ""); - return _bextr(src0, start0, len) == _bextr(src1, start1, len); -} -// -------------------------------------------------------------------------- // - // --- IMPLEMENTATION DETAILS: INSTRUCTIONS: DOUBLE PRECISION SHIFT LEFT ---- // // Left shifts dst by cnt bits, filling the lsbs of dst by the msbs of src template @@ -947,6 +489,28 @@ constexpr T _shrd(T dst, T src, T cnt) noexcept { } // -------------------------------------------------------------------------- // +#if defined(NO_X86_INTRINSICS) +template +static inline unsigned char add_carry_sub_borrow(unsigned char c_in, U a, U b, U* out) noexcept { + static_assert(std::is_unsigned_v, "Only unsigned types are supported"); + + if constexpr (Add) { + U sum1 = a + b; + U sum2 = sum1 + static_cast(c_in); + *out = sum2; + + // Carry occurs if either sum1 overflows a+b or sum2 overflows sum1+carry + return static_cast((sum1 < a) || (sum2 < sum1)); + } else { + U diff1 = a - b; + U diff2 = diff1 - static_cast(c_in); + *out = diff2; + + // Borrow occurs if a < b or a - b < carry_in + return static_cast((a < b) || (diff1 < c_in)); + } +} +#else #if defined(__ADX__) template unsigned char ADDCARRYSUBBORROW32(unsigned char c, uint32_t a, uint32_t b, uint32_t* out) { @@ -1000,6 +564,7 @@ static inline unsigned char add_carry_sub_borrow(unsigned char c_in, U a, U b, U assert(((void)"add carry intrinsics support only support powers of 2 bits", false)); } } +#endif // NO_X86_INTRINSICS template static inline unsigned char add_carry(unsigned char c_in, U a, U b, U* out) noexcept { diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index 6a706c6a..87af4ba3 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -15,6 +15,7 @@ // ================================ PREAMBLE ================================ // // C++ standard library // Project sources +#include #include #include "bit_details.hpp" @@ -221,12 +222,12 @@ constexpr bit_reference::operator bool() const noexcept { // Gets a bit pointer from the bit reference template constexpr bit_pointer> bit_reference::operator&() const noexcept { - return bit_pointer(&_ref, _tzcnt(_mask)); + return bit_pointer(&_ref, std::countr_zero(_mask)); } template constexpr bit_pointer> bit_reference::operator&() noexcept { - return bit_pointer(&_ref, _tzcnt(_mask)); + return bit_pointer(&_ref, std::countr_zero(_mask)); } // -------------------------------------------------------------------------- // From 02a1d27a8528b612b0ccd1c21a21a3c41e153e8a Mon Sep 17 00:00:00 2001 From: Peter McLean <112978670+Peter-McLean-Altera@users.noreply.github.com> Date: Fri, 11 Jul 2025 22:43:11 -0400 Subject: [PATCH 76/85] Necessary changes to work with clang cl cross compile toolchain (#23) * Necessary changes to work with clang cl cross compile toolchain * Move is an alias of copy. Fill in the doc --------- Co-authored-by: Peter McLean --- README.md | 153 +++++++++++++++--- include/bitlib/bit-algorithms/copy.hpp | 29 +++- include/bitlib/bit-algorithms/move.hpp | 98 +++-------- .../bitlib/bit-algorithms/to_from_string.hpp | 5 +- include/bitlib/bit-iterator/bit_details.hpp | 94 +++++++---- include/bitlib/bit-iterator/bit_reference.hpp | 13 +- .../bit_word_reference_adapter.hpp | 12 +- 7 files changed, 249 insertions(+), 155 deletions(-) diff --git a/README.md b/README.md index 24d2a9a0..7e53d02c 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,8 @@ auto twos_complement_exponent = static_cast(exponent) - ((1 << 5)-1); - [Table of Contents](#table-of-contents) - [Requirements](#requirements) - [CMake](#cmake) -- [Sized Literal](#literal) +- [Sized Literal](#sized-literal) +- [Bit Endian](#endian) - [Slice Operator](#slice-operator) - [Policy](#policy) - [Owning Containers](#owning-containers) @@ -42,6 +43,7 @@ auto twos_complement_exponent = static_cast(exponent) - ((1 << 5)-1); - [bit_word_reference_adapter](#bit_word_reference_adapter) - [Algorithms](#algorithms) - [Testing](#testing) +- [Future Work](#future-work) - [Contributors](#contributors) - [License](#license) @@ -98,7 +100,7 @@ target_sources(example PRIVATE example.cpp) target_link_libraries(example PRIVATE bitlib::bitlib) ``` -# Literal +# Sized Literal This provides a sized literal for compile-time bit_array. @@ -157,6 +159,30 @@ auto bin_lit = 0b11111'11110001001000000_b; //31 bit binary literal auto oct_lit = 037'361100_b; // 31 bit octal literal ``` +# Bit Endian + +'Bit Endian' is the format of a sequence of bits: + - 'Big Bit Endian' (BBE) the left-most bit is the most-significant-bit and right-most bit is the least-significant-bit + - 'Little Bit Endian' (LBE) the left-most bit is the least-significant-bit and right-most is the most-significant-bit + +Typical bit integrals are represented as 'Bit Bit Endian' +```c++ + int bits = 0b10101000; + // MSB --| |-- LSB +``` + +Several parts of the API that take or output sequences of bits may be sensitive to bit endian +or use the unintuitive little bit endian. + - initializer list of `bit_value` is in little bit endian. + ex: `{bit1, bit0, bit1, bit1}` == `0b1101`; + - `to_string` can be templated with a programmable endian. By default it is big bit endian + - `shift_left` and `shift_right` mimic std::shift and work in _little bit endian_. +```c++ + auto bits = 0b1000'10101000_b; + bit::shift_left(bits.begin(), bits.end(), 3); // a left shift in _little bit endian_ + bits == 0b1000'00010101_b; // is actually a right shift in big bit endian +``` + # Slice Operator All containers and views provide a slice operator which take a half-open range and @@ -185,7 +211,7 @@ The default policy truncates when necessary and sign extends for conversion to/f - `bit::bit_array` is an alias for `bit::array` - `bit::array` is a fixed-size and non-rebindable container with similar API to `std::array` - - `bit::bit_vector` is a resizable container with similar API to `std::vector` + - `bit::bit_vector` is a resizable container with similar API to `std::vector` ## bit_array @@ -194,12 +220,12 @@ Provides compile-time or construction time container for an array of bits. ### Compile-time: Storage is on the stack. The number of bytes is the nearest power of two integral size. ```c++ -bit_array<11> vec_11(0x123); // Will use a uint16_t to hold the data -bit_array<65> vec_65(); // Will use a uint64_t to hold the data +bit::bit_array<11> vec_11(0x123); // Will use a uint16_t to hold the data +bit::bit_array<65> vec_65(); // Will use a uint64_t to hold the data ``` The storage word type can be specified: ```c++ -bit_array<65, uint8_t> vec_65_bytes(); // 9 bytes on stack +bit::bit_array<65, uint8_t> vec_65_bytes(); // 9 bytes on stack ``` ### Construction-time @@ -209,14 +235,29 @@ Since the N template parameter is by default std::dynamic_extent this is the default template specialization `bit_array<>`. ```c++ -bit_array<> vec_11(11, 0x123); // 8 bytes on stack for data, 8 bytes for size -bit_array<> vec_64(65); // same stack size as above + 16 bytes in heap +bit::bit_array<> vec_11(11, 0x123); // 8 bytes on stack for data, 8 bytes for size +bit::bit_array<> vec_64(65); // same stack size as above + 16 bytes in heap ``` The storage word size is by default uintptr_t. The container will perform small buffer optimization when the number of bits is equal or less than `bitsof()` typically 64. ## bit_vector + +bit_vector provides similar API to `std::vector`. It owns runtime reallocatable storage. +The first template parameter is the integral datatype of the underlying storage. + +```c++ +bit::bit_vector vec(199, bit::bit1); +vec.push_back(bit0); +vec.insert(98, bit0); +vec(0, 7) = 7'7_b; // supports slice operator +``` + +> [!IMPORTANT] +> bit_vector does not support construction from integral or implicit cast to integral +> bit_vector is also lacking bit operators such as ~, <<, >>, &, | ... + # Non-Owning Views ## bit_array_ref ## bit_span @@ -241,7 +282,7 @@ There are three flavours of accessors: bit_value: ```c++ -bit_array<7*8*9> bits(); +bit::bit_array<7*8*9> bits(); std::mdspan< bit::bit_value, std::extents, @@ -271,7 +312,7 @@ assert(bits(7*8*9-7, 7*8*9) == 0x7F); Construction-time: ```c++ -bit_array<> bits(7*8*9); +bit::bit_array<> bits(7*8*9); std::mdspan< bit::bit_word_accessor<>::element_type, std::dextents, @@ -291,21 +332,85 @@ assert(bits(7*8*9-7, 7*8*9) == 0x7F); ## bit_reference ## bit_word_pointer_adapter ## bit_word_reference_adapter + # Algorithms - - accumulate - - copy_backward - - copy - - count - - equal - - fill - - find - - move - - reverse - - rotate - - shift - - swap_ranges - - to_from_string - - transform +The algorithms again work in the same manner as the STL. +The functions provided here have the same interface as those in the STL. +However, they take advantage of bit-parallelism. +It should be noted that if there is an STL algorithm that is not supported yet by BitLib, +the STL implementation should work. For example: +```c++ +using WordType = uint64_t; +bit::bit_vector bvec1 ("011111010010"); +bit::bit_vector bvec2 = bvec1; +bit::equal(bvec1.begin(), bvec1.end(), bvec2.begin(), bvec1.end()); +std::equal(bvec1.begin(), bvec1.end(), bvec2.begin(), bvec1.end()); // Also works, but much slower as it works bit-by-bit +``` + +For algorithms which take a function (i.e. `bit::transform`), +the function should have `WordType` as the input types as well as the return type. +For example, to compute the intersection of two bitvectors: +```c++ +using WordType = uint64_t; +auto binary_op = std::bit_and(); + +// Store the AND of bitvec1 and bitvec2 in bitvec3 +auto bitret = bit::transform( + bitvec1.begin(), + bitvec1.end(), + bitvec2.begin(), + bitvec3.begin() + binary_op); +``` + #### accumulate +Order sensitive lambda reduction operation. + #### copy_backward + #### copy + + #### count +Count bit1 or bit0 + + #### equal +Compare two bit sequences + + #### fill +Fill with bit1 or bit0 + + #### find +Get the position of the first bit1 or bit0 + + #### move +Alias of copy + + #### reverse +Reverse the bit sequence in-place + + #### rotate +Rotate the bit sequence in-place + + #### shift + + #### swap_ranges + + #### to_from_string +Convert to string or from string + + #### transform +Word-by-word lambda operation on single or double operand + +# Future Work + +Some features in-mind for this library: + +- bit_integer + * A compile-time/construction-time integer with support for all numeric operators + +- bit_vector + * Alias to a `bit::vector<...>` class which will support any vector-like container (ex: folly:small_vector) + +- bit_float + * Arbitrary precision floating point type + # Contributors - Vincent Reverdy diff --git a/include/bitlib/bit-algorithms/copy.hpp b/include/bitlib/bit-algorithms/copy.hpp index 00913d6f..28aa3b65 100644 --- a/include/bitlib/bit-algorithms/copy.hpp +++ b/include/bitlib/bit-algorithms/copy.hpp @@ -24,12 +24,27 @@ namespace bit { struct copy_impl; -// Status: Does not work for Input/Output iterators due to distance call -template -constexpr bit_iterator copy( - const bit_iterator& first, - const bit_iterator& last, - const bit_iterator& d_first) { +template +constexpr bit_iterator copy( + const bit_iterator& first, + const bit_iterator& last, + const It2& d_first) { + return with_bit_iterator_adapter(first, last, d_first); +} + +template +constexpr bit_iterator copy( + const It1& first, + const It1& last, + const bit_iterator& d_first) { + return with_bit_iterator_adapter(first, last, d_first); +} + +template +constexpr bit_iterator copy( + const bit_iterator& first, + const bit_iterator& last, + const bit_iterator& d_first) { return with_bit_iterator_adapter(first, last, d_first); } @@ -116,4 +131,4 @@ struct copy_impl { // ========================================================================== // } // namespace bit #endif // _COPY_HPP_INCLUDED -// ========================================================================== // +// ========================================================================== // \ No newline at end of file diff --git a/include/bitlib/bit-algorithms/move.hpp b/include/bitlib/bit-algorithms/move.hpp index aa31813d..4b78ed20 100644 --- a/include/bitlib/bit-algorithms/move.hpp +++ b/include/bitlib/bit-algorithms/move.hpp @@ -14,90 +14,34 @@ #include #include // Project sources -#include "bitlib/bit-iterator/bit.hpp" -// Third-party libraries -// Miscellaneous +#include "bitlib/bit-algorithms/copy.hpp" namespace bit { // ========================================================================== // -// Status: Does not work for Input/Output iterators due to distance call -template -constexpr bit_iterator move(bit_iterator first, - bit_iterator last, - bit_iterator d_first -) -{ - // Types and constants - using dst_word_type = typename bit_iterator::word_type; - using src_word_type = typename bit_iterator::word_type; - using word_type = dst_word_type; - using size_type = typename bit_iterator::size_type; - constexpr size_type digits = binary_digits::value; - - // Assertions - _assert_range_viability(first, last); - static_assert(std::is_same::value, "Underlying word types must be equal"); - if (first == last) return d_first; - - - // Initialization - const bool is_d_first_aligned = d_first.position() == 0; - size_type total_bits_to_move = distance(first, last); - size_type remaining_bits_to_move = total_bits_to_move; - auto it = d_first.base(); - - - // d_first is not aligned. Copy partial word to align it - if (!is_d_first_aligned) { - size_type partial_bits_to_move = std::min( - remaining_bits_to_move, - digits - d_first.position() - ); - *it = _bitblend( - *it, - static_cast( - get_word(first, partial_bits_to_move) - << static_cast(d_first.position()) - ), - static_cast(d_first.position()), - static_cast(partial_bits_to_move) - ); - remaining_bits_to_move -= partial_bits_to_move; - advance(first, partial_bits_to_move); - it++; - } - - if (remaining_bits_to_move > 0) { - const bool is_first_aligned = first.position() == 0; - //size_type words_to_move = std::ceil(remaining_bits_to_move / static_cast(digits)); - // d_first will be aligned at this point - if (is_first_aligned && remaining_bits_to_move > digits) { - auto N = std::distance(first.base(), last.base()); - it = std::move(first.base(), last.base(), it); - first += digits * N; - remaining_bits_to_move -= digits * N; - } else { - // TODO benchmark if its faster to std::move the entire range then shift - while (remaining_bits_to_move >= digits) { - *it = get_word(first, digits); - remaining_bits_to_move -= digits; - it++; - advance(first, digits); - } - } - if (remaining_bits_to_move > 0) { - *it = _bitblend( - *it, - get_word(first, remaining_bits_to_move), - _mask(remaining_bits_to_move)); - } - } - return d_first + total_bits_to_move; +template +constexpr bit_iterator move( + const bit_iterator& first, + const bit_iterator& last, + const It2& d_first) { + return copy(first, last, d_first); } -// -------------------------------------------------------------------------- // +template +constexpr bit_iterator move( + const It1& first, + const It1& last, + const bit_iterator& d_first) { + return copy(first, last, d_first); +} +template +constexpr bit_iterator move( + const bit_iterator& first, + const bit_iterator& last, + const bit_iterator& d_first) { + return copy(first, last, d_first); +} // ========================================================================== // } // namespace bit diff --git a/include/bitlib/bit-algorithms/to_from_string.hpp b/include/bitlib/bit-algorithms/to_from_string.hpp index 4008e440..23e68788 100644 --- a/include/bitlib/bit-algorithms/to_from_string.hpp +++ b/include/bitlib/bit-algorithms/to_from_string.hpp @@ -79,7 +79,10 @@ constexpr std::string to_string(const bit_iterator& first, const int skip_leading_bits = meta.str_sign_extend_zeros ? 0 : count_msb(first, last, bit0); int str_len = (distance(first, last) - skip_leading_bits); - str_len += (0 != (str_len % base_bits)); + str_len = (str_len / base_bits) + ((0 != (str_len % base_bits)) ? 1 : 0); + if (0 == str_len) { + return prefix + "0"; + } std::string& str = prefix; str.resize(str.length() + str_len); diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index 150b1aaf..971fa850 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -55,30 +55,6 @@ class bit_word_pointer_adapter; // ========================================================================== // -/* ***************************** BINARY DIGITS ****************************** */ -// Binary digits structure definition -// Implementation template: only instantiates static_asserts for non-byte types. -template ::value> -struct binary_digits_impl : std::integral_constant>::digits> { - static_assert(std::is_integral::value, "Type must be integral"); - //static_assert(std::is_unsigned::value, "Type must be unsigned"); - static_assert(!std::is_same::value, "Type must not be bool"); - static_assert(!std::is_same::value, "Type must not be char"); -}; - -// Specialization for std::byte. -template <> -struct binary_digits_impl : std::integral_constant::digits> {}; - -// Public interface that removes cv-qualifiers. -template -struct binary_digits : binary_digits_impl> {}; - -// Binary digits value -template -constexpr std::size_t binary_digits_v = binary_digits::value; -/*************************************************************************** */ - template using ceil_integral = std::conditional_t< (N <= bitsof()), @@ -174,6 +150,30 @@ struct exact_floor_integral { template using exact_floor_integral_t = typename exact_floor_integral::type; +/* ***************************** BINARY DIGITS ****************************** */ +// Binary digits structure definition +// Implementation template: only instantiates static_asserts for non-byte types. +template ::value> +struct binary_digits_impl : std::integral_constant>>::digits> { + static_assert(std::is_integral>::value, "Type must be integral"); + //static_assert(std::is_unsigned::value, "Type must be unsigned"); + static_assert(!std::is_same, bool>::value, "Type must not be bool"); + static_assert(!std::is_same, char>::value, "Type must not be char"); +}; + +// Specialization for std::byte. +template <> +struct binary_digits_impl : std::integral_constant::digits> {}; + +// Public interface that removes cv-qualifiers. +template +struct binary_digits : binary_digits_impl> {}; + +// Binary digits value +template +constexpr std::size_t binary_digits_v = binary_digits::value; +/*************************************************************************** */ + /* ******************* IMPLEMENTATION DETAILS: UTILITIES ******************** */ // Assertions template @@ -627,11 +627,11 @@ constexpr T _mulx(T src0, T src1, T* hi, X...) noexcept } // -------------------------------------------------------------------------- // -template +template constexpr auto with_bit_iterator_adapter( - bit_iterator first, - bit_iterator last, - bit_iterator d_first) { + const bit_iterator& first, + const bit_iterator& last, + const bit_iterator& d_first) { using dst_word_type = typename bit_iterator::word_type; using src_word_type = typename bit_iterator::word_type; if constexpr (!std::is_same_v && bitsof() != bitsof()) { @@ -651,10 +651,26 @@ constexpr auto with_bit_iterator_adapter( } } -template +template constexpr auto with_bit_iterator_adapter( - bit_iterator first, - bit_iterator last) { + const SrcIt& first, + const SrcIt& last, + const bit_iterator& d_first) { + return with_bit_iterator_adapter(bit_iterator(first), bit_iterator(last), d_first); +} + +template +constexpr auto with_bit_iterator_adapter( + const bit_iterator& first, + const bit_iterator& last, + const DstIt& d_first) { + return with_bit_iterator_adapter(first, last, bit_iterator(d_first)); +} + +template +constexpr auto with_bit_iterator_adapter( + const bit_iterator& first, + const bit_iterator& last) { using dst_word_type = typename bit_iterator::word_type; using src_word_type = typename bit_iterator::word_type; if constexpr (!std::is_same_v && bitsof() != bitsof()) { @@ -672,6 +688,20 @@ constexpr auto with_bit_iterator_adapter( } } +template +constexpr auto with_bit_iterator_adapter( + const SrcIt& first, + const bit_iterator& last) { + return with_bit_iterator_adapter(bit_iterator(first), last); +} + +template +constexpr auto with_bit_iterator_adapter( + const bit_iterator& first, + const DstIt& last) { + return with_bit_iterator_adapter(first, bit_iterator(last)); +} + namespace detail { struct uninitialized_t { @@ -712,4 +742,4 @@ struct container_size_storage { // ========================================================================== // } // namespace bit #endif // _BIT_DETAILS_HPP_INCLUDED -// ========================================================================== // +// ========================================================================== // \ No newline at end of file diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index 87af4ba3..52bafad7 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -32,7 +32,8 @@ namespace bit { template class bit_reference { public: - using WordType = std::remove_reference_t; + using WordType = exact_floor_integral_t>; + using const_word_type = std::add_const_t; private: // Assertions @@ -46,7 +47,7 @@ class bit_reference { public: using word_type = WordType; using size_type = std::size_t; - using mask_type = std::make_unsigned_t>; + using mask_type = std::make_unsigned_t; // Lifecycle public: @@ -75,7 +76,7 @@ class bit_reference { // Access public: - constexpr bit_pointer operator&() const noexcept; + constexpr bit_pointer operator&() const noexcept; constexpr bit_pointer operator&() noexcept; // Swap members @@ -221,12 +222,12 @@ constexpr bit_reference::operator bool() const noexcept { // ------------------------- BIT REFERENCE: ACCESS -------------------------- // // Gets a bit pointer from the bit reference template -constexpr bit_pointer> bit_reference::operator&() const noexcept { - return bit_pointer(&_ref, std::countr_zero(_mask)); +constexpr bit_pointer::const_word_type> bit_reference::operator&() const noexcept { + return bit_pointer(&_ref, std::countr_zero(_mask)); } template -constexpr bit_pointer> bit_reference::operator&() noexcept { +constexpr bit_pointer::WordType> bit_reference::operator&() noexcept { return bit_pointer(&_ref, std::countr_zero(_mask)); } // -------------------------------------------------------------------------- // diff --git a/include/bitlib/bit-iterator/bit_word_reference_adapter.hpp b/include/bitlib/bit-iterator/bit_word_reference_adapter.hpp index a768d270..88b53b17 100644 --- a/include/bitlib/bit-iterator/bit_word_reference_adapter.hpp +++ b/include/bitlib/bit-iterator/bit_word_reference_adapter.hpp @@ -41,18 +41,14 @@ class bit_word_reference_adapter { requires(is_big_to_small) : _source(source), _index(index) { } - explicit constexpr bit_word_reference_adapter(const bit_word_reference_adapter& other) { - if constexpr (is_small_to_big) { - *reinterpret_cast(&_source) = *reinterpret_cast(&_source); - } else { - *(reinterpret_cast(&_source) + _index) = *(reinterpret_cast(&_source) + other._index); - } + explicit constexpr bit_word_reference_adapter(const bit_word_reference_adapter& other) + : _source(other._source), _index(other._index) { } constexpr bit_word_reference_adapter& operator=(const bit_word_reference_adapter& other) { if constexpr (is_small_to_big) { - *reinterpret_cast(&_source) = *reinterpret_cast(&_source); + *reinterpret_cast(&_source) = *reinterpret_cast(&other._source); } else { - *(reinterpret_cast(&_source) + _index) = *(reinterpret_cast(&_source) + other._index); + *(reinterpret_cast(&_source) + _index) = *(reinterpret_cast(&other._source) + other._index); } return *this; } From 079daa142b49c06e6e930c1d2b944520530e9f41 Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Fri, 25 Jul 2025 20:12:50 +0000 Subject: [PATCH 77/85] windows build and test with clang and msvc (#25) --- .github/actions/xwin_sdk_crt/action.yml | 106 ++++ .../toolchains/clangcl_linux_to_windows.cmake | 44 ++ .github/workflows/cmake-multi-platform.yml | 205 ++----- .github/workflows/cmake_build_and_test.yml | 266 +++++++++ .gitignore | 2 + CMakePresets.json | 187 +++++- benchmark/inc/benchmark_utils.hpp | 25 +- .../bit-algorithms/bit_algorithm_details.hpp | 2 +- include/bitlib/bit-algorithms/copy.hpp | 4 +- .../bitlib/bit-algorithms/copy_backward.hpp | 25 +- include/bitlib/bit-algorithms/count.hpp | 5 +- include/bitlib/bit-algorithms/shift.hpp | 549 +++++++++--------- .../bit_array_dynamic_extent.hpp | 15 +- include/bitlib/bit-containers/bit_vector.hpp | 13 +- include/bitlib/bit-iterator/bit_details.hpp | 25 +- include/bitlib/bit-iterator/bit_reference.hpp | 8 +- test/CMakeLists.txt | 59 +- test/inc/test_utils.hpp | 29 +- test/src/vector_test.cpp | 408 ++++++------- 19 files changed, 1271 insertions(+), 706 deletions(-) create mode 100644 .github/actions/xwin_sdk_crt/action.yml create mode 100644 .github/toolchains/clangcl_linux_to_windows.cmake create mode 100644 .github/workflows/cmake_build_and_test.yml create mode 100644 .gitignore diff --git a/.github/actions/xwin_sdk_crt/action.yml b/.github/actions/xwin_sdk_crt/action.yml new file mode 100644 index 00000000..f4db6286 --- /dev/null +++ b/.github/actions/xwin_sdk_crt/action.yml @@ -0,0 +1,106 @@ +# .github/actions/xwin_sdk_crt/action.yml +name: "xwin_sdk_crt" +description: "Build xwin, then download & cache Windows SDK & CRT via xwin on Linux runners" + +inputs: + # xwin inputs + xwin-repo: + description: "GitHub repo for xwin (owner/repo)" + required: false + default: "https://github.com/Jake-Shadle/xwin" + xwin-hash: + description: "Git sha to checkout for xwin" + required: false + default: "11d9c9f69a374985faf833fbb77f006d4eb49cd6" + cargo-home: + description: "CARGO_HOME path (where xwin will be installed)" + required: false + default: "${{ github.workspace }}/.cargo" + + # Windows SDK/CRT inputs + sdk-version: + description: "Windows SDK version (e.g. 10.0.22621.0)" + required: true + crt-version: + description: "Windows CRT version (e.g. 14.30.30705.0)" + required: true + cache-path: + description: "Where to install & cache the SDK/CRT" + required: false + default: ".xwin_sdk_crt" + +runs: + using: "composite" + steps: + # 1) Restore xwin cache (binary + cargo registry/git) + - name: Restore xwin cache + id: xwin-cache + uses: actions/cache@v4 + with: + path: | + ${{ inputs.cargo-home }}/bin/xwin + ${{ inputs.cargo-home }}/registry + ${{ inputs.cargo-home }}/git + key: xwin-${{ inputs.xwin-hash }} + + # 2) Build & install xwin if cache miss + - name: Build & install xwin + if: steps.xwin-cache.outputs.cache-hit != 'true' + shell: bash + env: + CARGO_HOME: ${{ inputs.cargo-home }} + run: | + cargo install xwin --git ${{ inputs.xwin-repo }} --rev ${{ inputs.xwin-hash }} --locked --root $CARGO_HOME + + # 3) Save xwin cache on miss + - name: Save xwin cache + if: steps.xwin-cache.outputs.cache-hit != 'true' + uses: actions/cache@v4 + with: + path: | + ${{ inputs.cargo-home }}/bin/xwin + ${{ inputs.cargo-home }}/registry + ${{ inputs.cargo-home }}/git + key: xwin-${{ inputs.xwin-hash }} + + # 4) Restore Windows SDK/CRT cache + - name: Restore SDK/CRT cache + id: sdkcache + uses: actions/cache@v4 + with: + path: ${{ inputs.cache-path }} + key: xwin_sdk-${{ inputs.sdk-version }}_crt-${{ inputs.crt-version }} + restore-keys: | + xwin_sdk-${{ inputs.sdk-version }}_crt-${{ inputs.crt-version }} + + # 5) Download SDK & CRT via xwin if cache miss + - name: Download SDK & CRT via xwin + id: xwin + if: steps.sdkcache.outputs.cache-hit != 'true' + shell: bash + run: | + set -euo pipefail + + mkdir -p ${{ inputs.cache-path }} + + "${{ inputs.cargo-home }}/bin/xwin" --accept-license \ + --cache-dir ./.xwin-cache \ + --log-level info \ + --sdk-version ${{ inputs.sdk-version }} \ + --crt-version ${{ inputs.crt-version }} \ + splat \ + --include-debug-libs \ + --output ${{ inputs.cache-path }} + + rm -rf ./.xwin-cache + + echo "result=success" >> "$GITHUB_OUTPUT" + + + # 6) Save SDK/CRT cache on miss + - name: Save SDK/CRT cache + if: steps.sdkcache.outputs.cache-hit != 'true' && steps.xwin.outputs.result == 'success' + uses: actions/cache@v4 + with: + path: ${{ inputs.cache-path }} + key: xwin_sdk-${{ inputs.sdk-version }}_crt-${{ inputs.crt-version }} diff --git a/.github/toolchains/clangcl_linux_to_windows.cmake b/.github/toolchains/clangcl_linux_to_windows.cmake new file mode 100644 index 00000000..4d8deb77 --- /dev/null +++ b/.github/toolchains/clangcl_linux_to_windows.cmake @@ -0,0 +1,44 @@ +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_PROCESSOR amd64) + +set(CMAKE_C_COMPILER clang-cl) +set(CMAKE_C_FLAGS "/winsdkdir:$ENV{WINSDKDIR} /vctoolsdir:$ENV{VCTOOLSDIR} --target=x86_64-pc-windows-msvc -fuse-ld=lld-link /imsvc $ENV{VCTOOLSDIR}/include /imsvc $ENV{WINSDKDIR}/include/ucrt /imsvc $ENV{WINSDKDIR}/include/um /imsvc $ENV{WINSDKDIR}/include/shared -Wno-unused-command-line-argument ${CMAKE_C_FLAGS}") +set(CMAKE_CXX_COMPILER clang-cl) +set(CMAKE_CXX_FLAGS "/winsdkdir:$ENV{WINSDKDIR} /vctoolsdir:$ENV{VCTOOLSDIR} --target=x86_64-pc-windows-msvc -fuse-ld=lld-link /imsvc $ENV{VCTOOLSDIR}/include /imsvc $ENV{WINSDKDIR}/include/ucrt /imsvc $ENV{WINSDKDIR}/include/um /imsvc $ENV{WINSDKDIR}/include/shared -Wno-unused-command-line-argument ${CMAKE_CXX_FLAGS}") + +set(CMAKE_AR llvm-lib) +set(CMAKE_LINKER lld-link) +set(CMAKE_MT llvm-mt) +set(CMAKE_RC_COMPILER llvm-rc) + +set(CMAKE_EXE_LINKER_FLAGS_INIT "/winsdkdir:$ENV{WINSDKDIR}") +set(CMAKE_EXE_LINKER_FLAGS_INIT "${CMAKE_EXE_LINKER_FLAGS_INIT} /vctoolsdir:$ENV{VCTOOLSDIR}") +set(CMAKE_EXE_LINKER_FLAGS_INIT "${CMAKE_EXE_LINKER_FLAGS_INIT} /libpath:$ENV{WINSDKDIR}/lib/um/x86_64") +set(CMAKE_EXE_LINKER_FLAGS_INIT "${CMAKE_EXE_LINKER_FLAGS_INIT} /libpath:$ENV{WINSDKDIR}/lib/ucrt/x86_64") +set(CMAKE_EXE_LINKER_FLAGS_INIT "${CMAKE_EXE_LINKER_FLAGS_INIT} /libpath:$ENV{VCTOOLSDIR}/lib/x86_64") + +set(CMAKE_SHARED_LINKER_FLAGS_INIT "/winsdkdir:$ENV{WINSDKDIR}") +set(CMAKE_SHARED_LINKER_FLAGS_INIT "${CMAKE_SHARED_LINKER_FLAGS_INIT} /vctoolsdir:$ENV{VCTOOLSDIR}") +set(CMAKE_SHARED_LINKER_FLAGS_INIT "${CMAKE_SHARED_LINKER_FLAGS_INIT} /libpath:$ENV{WINSDKDIR}/lib/um/x86_64") +set(CMAKE_SHARED_LINKER_FLAGS_INIT "${CMAKE_SHARED_LINKER_FLAGS_INIT} /libpath:$ENV{WINSDKDIR}/lib/ucrt/x86_64") +set(CMAKE_SHARED_LINKER_FLAGS_INIT "${CMAKE_SHARED_LINKER_FLAGS_INIT} /libpath:$ENV{VCTOOLSDIR}/lib/x86_64") + +set(CMAKE_MODULE_LINKER_FLAGS_INIT "/winsdkdir:$ENV{WINSDKDIR}") +set(CMAKE_MODULE_LINKER_FLAGS_INIT "${CMAKE_MODULE_LINKER_FLAGS_INIT} /vctoolsdir:$ENV{VCTOOLSDIR}") +set(CMAKE_MODULE_LINKER_FLAGS_INIT "${CMAKE_MODULE_LINKER_FLAGS_INIT} /libpath:$ENV{WINSDKDIR}/lib/um/x86_64") +set(CMAKE_MODULE_LINKER_FLAGS_INIT "${CMAKE_MODULE_LINKER_FLAGS_INIT} /libpath:$ENV{WINSDKDIR}/lib/ucrt/x86_64") +set(CMAKE_MODULE_LINKER_FLAGS_INIT "${CMAKE_MODULE_LINKER_FLAGS_INIT} /libpath:$ENV{VCTOOLSDIR}/lib/x86_64") + +set(CMAKE_STATIC_LINKER_FLAGS_INIT "${CMAKE_STATIC_LINKER_FLAGS_INIT} /libpath:$ENV{WINSDKDIR}/lib/um/x86_64") +set(CMAKE_STATIC_LINKER_FLAGS_INIT "${CMAKE_STATIC_LINKER_FLAGS_INIT} /libpath:$ENV{WINSDKDIR}/lib/ucrt/x86_64") +set(CMAKE_STATIC_LINKER_FLAGS_INIT "${CMAKE_STATIC_LINKER_FLAGS_INIT} /libpath:$ENV{VCTOOLSDIR}/lib/x86_64") + +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS_INIT}" CACHE STRING "Flags used by the linker for all executables." FORCE) +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS_INIT}" CACHE STRING "Flags used by the linker for all shared libraries." FORCE) +set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS_INIT}" CACHE STRING "Flags used by the linker for all modules." FORCE) +set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS_INIT}" CACHE STRING "Flags used by the linker for all static libraries." FORCE) + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) \ No newline at end of file diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 046edde5..66d25613 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -1,9 +1,7 @@ -# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. -# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml +# .github/workflows/main.yaml name: CMake on multiple platforms on: - workflow_dispatch: push: branches: [ "master" ] paths-ignore: @@ -13,11 +11,9 @@ on: paths-ignore: - '**/*.md' - jobs: - build: - runs-on: ${{ matrix.distro }} - + run-matrix: + uses: ./.github/workflows/cmake_build_and_test.yml strategy: # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. fail-fast: false @@ -30,159 +26,82 @@ jobs: # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. matrix: include: - - distro: ubuntu-latest - os: linux + - build_distro: windows-latest + build_os: windows + compiler: msvc + preset: tests + build_type: Release + + - build_distro: windows-latest + build_os: windows + compiler: msvc + preset: tests + + - build_distro: ubuntu-latest + build_os: linux compiler: gcc - c_compiler: gcc - cpp_compiler: g++ - stdlib: libstdcxx preset: benchmark - target: bitlib-bench - build_type: Release - - distro: ubuntu-latest - os: linux + + - build_distro: ubuntu-latest + build_os: linux compiler: clang - cpp_compiler: clang++-19 - c_compiler: clang-19 stdlib: libcxx preset: benchmark - target: bitlib-bench - build_type: Release - - distro: ubuntu-latest - os: linux + + - build_distro: ubuntu-latest + build_os: linux compiler: gcc - c_compiler: gcc - cpp_compiler: g++ - stdlib: libstdcxx preset: tests - target: bitlib-tests - build_type: Debug - - distro: ubuntu-latest - os: linux + + - build_distro: ubuntu-latest + build_os: linux compiler: gcc - c_compiler: gcc - cpp_compiler: g++ - stdlib: libstdcxx preset: warnings - target: bitlib-tests - build_type: Debug - - distro: ubuntu-latest - os: linux + + - build_distro: ubuntu-latest + build_os: linux compiler: clang - c_compiler: clang-19 - cpp_compiler: clang++-19 - stdlib: libcxx preset: warnings - target: bitlib-tests - build_type: Debug - - distro: ubuntu-latest - os: linux + + - build_distro: ubuntu-latest + build_os: linux compiler: clang - cpp_compiler: clang++-19 - c_compiler: clang-19 - stdlib: libcxx preset: tests - target: bitlib-tests - build_type: Debug - - distro: ubuntu-latest - os: linux + + - build_distro: ubuntu-latest + build_os: linux compiler: gcc - c_compiler: gcc - cpp_compiler: g++ - stdlib: libstdcxx preset: coverage - target: bitlib-tests - build_type: Debug - - distro: ubuntu-latest - os: linux + + - build_distro: ubuntu-latest + build_os: linux compiler: clang - cpp_compiler: clang++-19 - c_compiler: clang-19 - stdlib: libcxx preset: coverage - target: bitlib-tests - build_type: Debug - - steps: - - uses: actions/checkout@v4 - - - name: "Install Clang 19" - if: matrix.os == 'linux' && matrix.c_compiler == 'clang-19' - run: | - sudo apt-get update - sudo apt-get install -y wget gnupg lsb-release - wget https://apt.llvm.org/llvm.sh - chmod +x llvm.sh - sudo ./llvm.sh 19 - # Install libc++ and libc++abi for Clang 19 - sudo apt-get install -y libc++-19-dev libc++abi-19-dev - sudo ln -sf /usr/bin/llvm-cov-19 /usr/local/bin/llvm-cov - sudo ln -sf /usr/bin/llvm-profdata-19 /usr/local/bin/llvm-profdata - sudo ln -sf /usr/bin/clang-19 /usr/local/bin/clang - sudo ln -sf /usr/bin/clang++-19 /usr/local/bin/clang++ - sudo ln -sf /usr/bin/clang-format-19 /usr/local/bin/clang-format - - - name: Set reusable strings - # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. - id: strings - shell: bash - run: | - echo "build-output-dir=$(readlink -f "${{ github.workspace }}/../build")" >> "$GITHUB_OUTPUT" - - - name: Configure CMake - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: | - # Configure - cmake -B ${{ steps.strings.outputs.build-output-dir }} -S ${{ github.workspace }} \ - --preset=${{ matrix.preset }}_${{ matrix.os }}_${{ matrix.compiler }}_${{ matrix.stdlib }} \ - -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} \ - -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} \ - -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - - # Delete any coverage files created from CMake probing - find ${{ github.workspace }} \( -name '*.gcno' -o -name '*.gcda' \) -delete - find ${{ steps.strings.outputs.build-output-dir }} \( -name '*.gcno' -o -name '*.gcda' \) -delete - - name: Register Compiler Problem Matcher - run: echo "::add-matcher::.github/problem_matchers/${{ matrix.compiler}}_problem_matcher.json" - - - name: Build - # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). - run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --target ${{ matrix.target }} --config ${{ matrix.build_type }} --parallel - - - name: Unregister Compiler Problem Matcher - run: echo "::remove-matcher owner=${{ matrix.compiler}}::" - - - name: Run coverage for Clang - if: matrix.preset == 'coverage' && matrix.compiler == 'clang' - uses: ./.github/actions/coverage_clang - with: - build-output-dir: ${{ steps.strings.outputs.build-output-dir }} - GITHUB_TOKEN: ${{ secrets.github_token }} - - - name: Run coverage for GCC - if: matrix.preset == 'coverage' && matrix.compiler == 'gcc' - uses: ./.github/actions/coverage_gcc - with: - build-output-dir: ${{ steps.strings.outputs.build-output-dir }} - GITHUB_TOKEN: ${{ secrets.github_token }} + - build_distro: ubuntu-latest + build_os: linux + test_distro: windows-latest + test_os: windows + compiler: clangcl + preset: tests - - name: Test - if: matrix.preset != 'coverage' - # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --test-dir ${{ steps.strings.outputs.build-output-dir }} --build-config ${{ matrix.build_type }} --output-on-failure --parallel + - build_distro: ubuntu-latest + build_os: linux + test_distro: windows-latest + test_os: windows + compiler: clangcl + preset: tests + build_type: Release - - uses: ./.github/actions/benchmark_epilogue - if: matrix.preset == 'benchmark' - with: - workspace: ${{ github.workspace }} - cpp_compiler: ${{ matrix.cpp_compiler }} - c_compiler: ${{ matrix.c_compiler }} - build_type: ${{ matrix.build_type }} - base_ref: ${{ github.event.pull_request.base.ref }} - os: ${{ matrix.os }} - compiler: ${{ matrix.compiler }} - stdlib: ${{ matrix.stdlib }} - build-output-dir: ${{ steps.strings.outputs.build-output-dir }} + with: + build_distro: ${{ matrix.build_distro }} + build_os: ${{ matrix.build_os }} + test_distro: ${{ matrix.test_distro || matrix.build_distro }} + test_os: ${{ matrix.test_os || matrix.build_os }} + compiler: ${{ matrix.compiler }} + stdlib: ${{ matrix.stdlib || matrix.compiler == 'msvc' && 'msvcstl' || matrix.compiler == 'clangcl' && 'msvcstl' || matrix.compiler == 'clang' && 'libcxx' || 'libstdcxx' }} + preset: ${{ matrix.preset }} + build_type: ${{ matrix.build_type || matrix.preset == 'benchmark' && 'Release' || 'Debug' }} + head_ref: ${{ github.event.pull_request.head.ref }} + base_ref: ${{ github.event.pull_request.base.ref }} + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/cmake_build_and_test.yml b/.github/workflows/cmake_build_and_test.yml new file mode 100644 index 00000000..3e60346e --- /dev/null +++ b/.github/workflows/cmake_build_and_test.yml @@ -0,0 +1,266 @@ +# .github/workflows/reusable.yaml +name: Reusable Workflow + +on: + workflow_call: + inputs: + build_distro: + required: true + type: string + build_os: + required: true + type: string + test_distro: + required: true + type: string + test_os: + required: true + type: string + compiler: + required: true + type: string + stdlib: + required: true + type: string + preset: + required: true + type: string + build_type: + required: true + type: string + head_ref: + required: true + type: string + base_ref: + required: true + type: string + +jobs: + build: + runs-on: ${{ inputs.build_distro }} + steps: + - uses: actions/checkout@v4 + + - name: Download & Cache xwin + SDK/CRT + if: inputs.build_os == 'linux' && inputs.test_os == 'windows' && inputs.compiler == 'clangcl' + uses: ./.github/actions/xwin_sdk_crt + with: + sdk-version: '10.0.26100' + crt-version: '14.44.17.14' + + - name: "SDK/CRT Env Vars" + if: inputs.build_os == 'linux' && inputs.test_os == 'windows' && inputs.compiler == 'clangcl' + shell: bash + run: | + echo "WINSDKDIR=${{ github.workspace }}/.xwin_sdk_crt/sdk" >> "$GITHUB_ENV" + echo "VCTOOLSDIR=${{ github.workspace }}/.xwin_sdk_crt/crt" >> "$GITHUB_ENV" + + - name: "Install Clang 19" + if: inputs.build_os == 'linux' && (inputs.compiler == 'clang' || inputs.compiler == 'clangcl') + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y wget gnupg lsb-release + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 19 all + # Install libc++ and libc++abi for Clang 19 + sudo apt-get install -y libc++-19-dev libc++abi-19-dev + sudo ln -sf /usr/bin/llvm-cov-19 /usr/local/bin/llvm-cov + sudo ln -sf /usr/bin/llvm-profdata-19 /usr/local/bin/llvm-profdata + sudo ln -sf /usr/bin/clang-19 /usr/local/bin/clang + sudo ln -sf /usr/bin/clang++-19 /usr/local/bin/clang++ + sudo ln -sf /usr/bin/clang-format-19 /usr/local/bin/clang-format + sudo ln -sf /usr/bin/clang-cl-19 /usr/local/bin/clang-cl + sudo ln -sf /usr/bin/llvm-lib-19 /usr/local/bin/llvm-lib + sudo ln -sf /usr/bin/lld-link-19 /usr/local/bin/lld-link + sudo ln -sf /usr/bin/llvm-mt-19 /usr/local/bin/llvm-mt + sudo ln -sf /usr/bin/llvm-rc-19 /usr/local/bin/llvm-rc + + - name: Set reusable strings + id: strings + shell: bash + run: | + if [[ "${{ inputs.compiler }}" == "gcc" ]]; then + echo "c_compiler=gcc" >> "$GITHUB_OUTPUT" + echo "cpp_compiler=g++" >> "$GITHUB_OUTPUT" + elif [[ "${{ inputs.compiler }}" == "clang" ]]; then + echo "c_compiler=clang" >> "$GITHUB_OUTPUT" + echo "cpp_compiler=clang++" >> "$GITHUB_OUTPUT" + elif [[ "${{ inputs.compiler }}" == "clangcl" ]]; then + echo "c_compiler=clang-cl" >> "$GITHUB_OUTPUT" + echo "cpp_compiler=clang-cl" >> "$GITHUB_OUTPUT" + elif [[ "${{ inputs.compiler }}" == "msvc" ]]; then + echo "c_compiler=cl.exe" >> "$GITHUB_OUTPUT" + echo "cpp_compiler=cl.exe" >> "$GITHUB_OUTPUT" + fi + if [[ "${{ inputs.preset}}" == "benchmark" ]]; then + echo "cmake_target=bitlib-bench" >> "$GITHUB_OUTPUT" + else + # tests, warnings, coverage + echo "cmake_target=bitlib-tests" >> "$GITHUB_OUTPUT" + fi + if [[ "${{ inputs.build_os }}" != "${{ inputs.test_os }}" ]]; then + echo "toolchain_file=.github/toolchains/${{ inputs.compiler }}_${{ inputs.build_os }}_to_${{ inputs.test_os }}.cmake" >> "$GITHUB_OUTPUT" + else + echo "toolchain_file=" >> "$GITHUB_OUTPUT" + fi + echo "build-output-dir=$(readlink -f '${{ github.workspace }}/../build')" >> "$GITHUB_OUTPUT" + + - name: Configure CMake + shell: bash + env: + CMAKE_TOOLCHAIN_FILE: ${{ steps.strings.outputs.toolchain_file }} + run: | + # Configure + cmake \ + -B '${{ steps.strings.outputs.build-output-dir }}' \ + -S '${{ github.workspace }}' \ + --preset=${{ inputs.preset }}_${{ inputs.test_os }}_${{ inputs.compiler }}_${{ inputs.stdlib }} \ + ${{ inputs.build_type && format('-DCMAKE_BUILD_TYPE={0}', inputs.build_type) }} + + # Delete any coverage files created from CMake probing + find '${{ github.workspace }}' \( -name '*.gcno' -o -name '*.gcda' \) -delete + find '${{ steps.strings.outputs.build-output-dir }}' \( -name '*.gcno' -o -name '*.gcda' \) -delete + + - name: Register Compiler Problem Matcher + shell: bash + run: | + if [[ "${{ inputs.compiler }}" == "clangcl" ]]; then + echo "::add-matcher::.github/problem_matchers/msvc_problem_matcher.json" + else + echo "::add-matcher::.github/problem_matchers/${{ inputs.compiler}}_problem_matcher.json" + fi + + - name: Build + shell: bash + run: | + cmake --build '${{ steps.strings.outputs.build-output-dir }}' \ + --target ${{ steps.strings.outputs.cmake_target }} \ + --config ${{ inputs.build_type }} \ + --parallel + + - name: Unregister Compiler Problem Matcher + shell: bash + run: | + if [[ "${{ inputs.compiler }}" == "clangcl" ]]; then + echo "::remove-matcher owner=msvc::" + else + echo "::remove-matcher owner=${{ inputs.compiler}}::" + fi + + - name: Upload build dir + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.preset }}_${{ inputs.test_os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_${{ inputs.build_type }} + path: '${{ steps.strings.outputs.build-output-dir }}' + + test: + runs-on: ${{ inputs.test_distro }} + needs: build + steps: + - uses: actions/checkout@v4 + + - name: "Install LLVM 19 toolchain" + if: inputs.test_os == 'linux' && inputs.compiler == 'clang' + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y wget gnupg lsb-release + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 19 + sudo ln -sf /usr/bin/llvm-cov-19 /usr/local/bin/llvm-cov + sudo ln -sf /usr/bin/llvm-profdata-19 /usr/local/bin/llvm-profdata + + - name: "Install Clang 19 Libcxx" + if: inputs.test_os == 'linux' && inputs.stdlib == 'libcxx' + shell: bash + run: | + # Install libc++ and libc++abi for Clang 19 + sudo apt-get install -y libc++-19-dev libc++abi-19-dev + + - name: Download build dir + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.preset }}_${{ inputs.test_os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_${{ inputs.build_type }} + path: '${{ github.workspace }}/../build' + + - name: Set reusable strings + id: strings + shell: bash + run: | + if [[ "${{ inputs.preset }}" == "benchmark" ]]; then + chmod +x '${{ github.workspace }}/../build/benchmark/bitlib-bench'* + echo 'binary-sub-dir=benchmark' >> "$GITHUB_OUTPUT" + echo "cmake_target=bitlib-bench" >> "$GITHUB_OUTPUT" + else + # tests, warnings, coverage + chmod +x '${{ github.workspace }}/../build/test/bitlib-tests'* + echo 'binary-sub-dir=test' >> "$GITHUB_OUTPUT" + echo "cmake_target=bitlib-tests" >> "$GITHUB_OUTPUT" + fi + echo "build-output-dir=$(readlink -f '${{ github.workspace }}/../build')" >> "$GITHUB_OUTPUT" + + - name: Run coverage for Clang + if: inputs.preset == 'coverage' && inputs.compiler == 'clang' + uses: ./.github/actions/coverage_clang + with: + build-output-dir: ${{ steps.strings.outputs.build-output-dir }} + GITHUB_TOKEN: ${{ secrets.github_token }} + + - name: Run coverage for GCC + if: inputs.preset == 'coverage' && inputs.compiler == 'gcc' + uses: ./.github/actions/coverage_gcc + with: + build-output-dir: ${{ steps.strings.outputs.build-output-dir }} + GITHUB_TOKEN: ${{ secrets.github_token }} + + - name: Test + if: inputs.preset != 'coverage' + shell: bash + run: | + ctest --test-dir ${{ steps.strings.outputs.build-output-dir }} \ + --build-config ${{ inputs.build_type }} \ + --output-on-failure \ + --parallel + + - name: Cache benchmark json + if: inputs.preset == 'benchmark' + uses: actions/cache@v4 + with: + path: '${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result.json' + key: benchmark_${{ inputs.test_os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_${{ inputs.build_type }}_${{ github.event.pull_request.head.ref }} + + - name: Rename current benchmark json + if: inputs.preset == 'benchmark' + shell: bash + run: | + mv '${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result.json' '${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result_new.json' + + - name: Fetch base_ref benchmark json + if: inputs.preset == 'benchmark' + id: base_ref_json + uses: actions/cache@v4 + with: + path: '${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result.json' + key: benchmark_${{ inputs.test_os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_${{ inputs.build_type }}_${{ github.event.pull_request.base.ref }} + + - name: Compare Benchmarks + if: inputs.preset == 'benchmark' && steps.base_ref_json.outputs.cache-hit == 'true' + shell: bash + run: | + python3 '${{ steps.strings.outputs.build-output-dir }}/_deps/benchmark-src/tools/compare.py' benchmarks \ + '${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result.json' \ + '${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result_new.json' \ + | tee '${{ steps.strings.outputs.build-output-dir }}/benchmark/comparison.txt' + + - name: Archive benchmark results + if: inputs.preset == 'benchmark' && steps.base_ref_json.outputs.cache-hit == 'true' + uses: actions/upload-artifact@v4 + with: + name: benchmark_${{ inputs.os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_json + path: | + '${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result.json' + '${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result_new.json' + '${{ steps.strings.outputs.build-output-dir }}/benchmark/comparison.txt' diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..0d464ac5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +**/.claude/settings.local.json +.vs \ No newline at end of file diff --git a/CMakePresets.json b/CMakePresets.json index bfc042ee..70b2a690 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -14,9 +14,19 @@ "hidden": true, "inherits": "base", "condition": { - "type": "equals", - "lhs": "${hostSystemName}", - "rhs": "Windows" + "type": "anyOf", + "conditions": [ + { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + { + "type": "notEquals", + "lhs": "$env{CMAKE_TOOLCHAIN_FILE}", + "rhs": "" + } + ] } }, { @@ -32,9 +42,20 @@ { "name": "msvc_base", "hidden": true, + "environment": { + "CC_FLAGS": "/bigobj /EHsc /GL", + "CC_FLAGS_DEBUG": "/Od /Zi /MDd /D_DEBUG /D_ITERATOR_DEBUG_LEVEL=2" + }, "cacheVariables": { "CMAKE_C_COMPILER": "cl", - "CMAKE_CXX_COMPILER": "cl" + "CMAKE_CXX_COMPILER": "cl", + "CMAKE_C_FLAGS": "$env{MSVC_FLAGS} $env{CC_FLAGS} $env{TARGET_FLAGS}", + "CMAKE_CXX_FLAGS": "$env{MSVC_FLAGS} $env{CC_FLAGS} $env{STDLIB_FLAGS} $env{TARGET_FLAGS}", + "CMAKE_EXE_LINKER_FLAGS": "$env{STDLIB_FLAGS}", + "CMAKE_C_FLAGS_DEBUG": "$env{MSVC_FLAGS_DEBUG} $env{CC_FLAGS_DEBUG} $env{TARGET_FLAGS_DEBUG}", + "CMAKE_CXX_FLAGS_DEBUG": "$env{MSVC_FLAGS_DEBUG} $env{CC_FLAGS_DEBUG} $env{TARGET_FLAGS_DEBUG}", + "CMAKE_C_FLAGS_RELEASE": "$env{MSVC_FLAGS_RELEASE} $env{CC_FLAGS_RELEASE} $env{TARGET_FLAGS_RELEASE}", + "CMAKE_CXX_FLAGS_RELEASE": "$env{MSVC_FLAGS_RELEASE} $env{CC_FLAGS_RELEASE} $env{TARGET_FLAGS_RELEASE}" } }, { @@ -139,18 +160,40 @@ "name": "tests_windows_msvc_msvcstl", "inherits": [ "tests_base", - "windows_base" + "windows_base", + "msvc_base" ] }, { "name": "benchmark_windows_msvc_msvcstl", "inherits": [ "benchmark_base", - "windows_base" + "windows_base", + "msvc_base" + ] + }, + { + "name": "tests_windows_clangcl_msvcstl", + "inherits": [ + "tests_base", + "windows_base", + "msvc_base" + ], + "cacheVariables": { + "CMAKE_CXX_COMPILER": "clang-cl", + "CMAKE_C_COMPILER": "clang-cl" + } + }, + { + "name": "benchmark_windows_clangcl_msvcstl", + "inherits": [ + "benchmark_base", + "windows_base", + "msvc_base" ], "cacheVariables": { - "CMAKE_C_FLAGS": "$env{MSVC_FLAGS}", - "CMAKE_CXX_FLAGS": "$env{MSVC_FLAGS}" + "CMAKE_CXX_COMPILER": "clang-cl", + "CMAKE_C_COMPILER": "clang-cl" } }, { @@ -255,7 +298,7 @@ "configurePreset": "tests_windows_msvc_msvcstl", "targets": "bitlib-tests", "configuration": "Debug", - "jobs": 0, + "jobs": 1, "condition": { "type": "equals", "lhs": "${hostSystemName}", @@ -267,13 +310,57 @@ "configurePreset": "benchmark_windows_msvc_msvcstl", "targets": "bitlib-bench", "configuration": "Release", - "jobs": 0, + "jobs": 1, "condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows" } }, + { + "name": "tests_windows_clangcl_msvcstl", + "configurePreset": "tests_windows_clangcl_msvcstl", + "targets": "bitlib-tests", + "configuration": "Debug", + "jobs": 0, + "condition": { + "type": "anyOf", + "conditions": [ + { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + { + "type": "notEquals", + "lhs": "$env{CMAKE_TOOLCHAIN_FILE}", + "rhs": "" + } + ] + } + }, + { + "name": "benchmark_windows_clangcl_msvcstl", + "configurePreset": "benchmark_windows_clangcl_msvcstl", + "targets": "bitlib-bench", + "configuration": "Release", + "jobs": 0, + "condition": { + "type": "anyOf", + "conditions": [ + { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + { + "type": "notEquals", + "lhs": "$env{CMAKE_TOOLCHAIN_FILE}", + "rhs": "" + } + ] + } + }, { "name": "tests_linux_gcc_libstdcxx", "configurePreset": "tests_linux_gcc_libstdcxx", @@ -412,6 +499,52 @@ "rhs": "Windows" } }, + { + "name": "tests_windows_clangcl_msvcstl", + "configurePreset": "tests_windows_clangcl_msvcstl", + "configuration": "Debug", + "condition": { + "type": "anyOf", + "conditions": [ + { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + { + "type": "notEquals", + "lhs": "$env{CMAKE_TOOLCHAIN_FILE}", + "rhs": "" + } + ] + } + }, + { + "name": "benchmark_windows_clangcl_msvcstl", + "configurePreset": "benchmark_windows_clangcl_msvcstl", + "configuration": "Release", + "execution": { + "jobs": 1 + }, + "output": { + "verbosity": "verbose" + }, + "condition": { + "type": "anyOf", + "conditions": [ + { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + { + "type": "notEquals", + "lhs": "$env{CMAKE_TOOLCHAIN_FILE}", + "rhs": "" + } + ] + } + }, { "name": "tests_linux_gcc_libstdcxx", "configurePreset": "tests_linux_gcc_libstdcxx", @@ -532,6 +665,40 @@ } ] }, + { + "name": "tests_windows_clangcl_msvcstl", + "steps": [ + { + "type": "configure", + "name": "tests_windows_clangcl_msvcstl" + }, + { + "type": "build", + "name": "tests_windows_clangcl_msvcstl" + }, + { + "type": "test", + "name": "tests_windows_clangcl_msvcstl" + } + ] + }, + { + "name": "benchmark_windows_clangcl_msvcstl", + "steps": [ + { + "type": "configure", + "name": "benchmark_windows_clangcl_msvcstl" + }, + { + "type": "build", + "name": "benchmark_windows_clangcl_msvcstl" + }, + { + "type": "test", + "name": "benchmark_windows_clangcl_msvcstl" + } + ] + }, { "name": "tests_linux_gcc_libstdcxx", "steps": [ diff --git a/benchmark/inc/benchmark_utils.hpp b/benchmark/inc/benchmark_utils.hpp index ed4e7351..4ba4d833 100644 --- a/benchmark/inc/benchmark_utils.hpp +++ b/benchmark/inc/benchmark_utils.hpp @@ -29,6 +29,27 @@ Container make_random_container( return c; } +// clang-format off +template + requires std::integral +struct uniform_dist_type { + static constexpr bool is_signed = std::is_signed_v; + static constexpr std::size_t size = sizeof(T); + + using type = std::conditional_t, + std::conditional_t, + std::conditional_t, + void // fallback (shouldn't happen for standard integral types) + >>>; +}; +// clang-format on + +template +using uniform_dist_type_t = typename uniform_dist_type::type; + template std::vector get_random_vec( unsigned long long int size, @@ -36,10 +57,10 @@ std::vector get_random_vec( WordType max = std::numeric_limits::max()) { std::random_device device; std::mt19937 mersenne_engine(device()); - std::uniform_int_distribution dist{min, max}; + std::uniform_int_distribution> dist{min, max}; auto gen = [&dist, &mersenne_engine]() { - return dist(mersenne_engine); + return static_cast(dist(mersenne_engine)); }; std::vector vec(size); generate(begin(vec), end(vec), gen); diff --git a/include/bitlib/bit-algorithms/bit_algorithm_details.hpp b/include/bitlib/bit-algorithms/bit_algorithm_details.hpp index a0466141..f23abea3 100644 --- a/include/bitlib/bit-algorithms/bit_algorithm_details.hpp +++ b/include/bitlib/bit-algorithms/bit_algorithm_details.hpp @@ -110,7 +110,7 @@ T get_word(const bit_iterator& first, size_t len = binary_digits::va assert(digits >= len); using non_const_T = std::remove_cvref_t; non_const_T offset = digits - first.position(); - non_const_T ret_word = lsr(*first.base(), first.position()); + non_const_T ret_word = lsr(*first.base(), first.position()); // We've already assigned enough bits if (len <= offset) { diff --git a/include/bitlib/bit-algorithms/copy.hpp b/include/bitlib/bit-algorithms/copy.hpp index 28aa3b65..adc55f5b 100644 --- a/include/bitlib/bit-algorithms/copy.hpp +++ b/include/bitlib/bit-algorithms/copy.hpp @@ -84,7 +84,7 @@ struct copy_impl { size_type partial_bits_to_copy = ::std::min( remaining_bits_to_copy, digits - d_first.position()); - *it = _bitblend( + *it = _bitblend( *it, static_cast( get_word(first, partial_bits_to_copy) @@ -115,7 +115,7 @@ struct copy_impl { } } if (remaining_bits_to_copy > 0) { - *it = _bitblend( + *it = _bitblend( *it, get_word(first, remaining_bits_to_copy), _mask(remaining_bits_to_copy)); diff --git a/include/bitlib/bit-algorithms/copy_backward.hpp b/include/bitlib/bit-algorithms/copy_backward.hpp index 365ade22..6c255a08 100644 --- a/include/bitlib/bit-algorithms/copy_backward.hpp +++ b/include/bitlib/bit-algorithms/copy_backward.hpp @@ -70,22 +70,21 @@ constexpr bit_iterator copy_backward(bit_iterator(digits)); // d_last will be aligned at this point - if (is_last_aligned && remaining_bits_to_copy > digits) { - auto N = ::std::distance(first.base(), last.base()) - 1; - it = ::std::copy_backward(first.base() + 1, last.base(), it); - last -= digits * N; - remaining_bits_to_copy -= digits * N; - it--; + if (is_last_aligned && remaining_bits_to_copy >= digits) { + auto N = ::std::distance(first.base(), last.base()) - 1; + it = ::std::copy_backward(first.base() + 1, last.base(), it); + last -= digits * N; + remaining_bits_to_copy -= digits * N; } else { - // TODO benchmark if its faster to ::std::copy the entire range then shift - while (remaining_bits_to_copy >= digits) { - *(--it) = get_word(last - digits, digits); - remaining_bits_to_copy -= digits; - advance(last, -digits); - } - it--; + // TODO benchmark if its faster to ::std::copy the entire range then shift + while (remaining_bits_to_copy >= digits) { + *(--it) = get_word(last - digits, digits); + remaining_bits_to_copy -= digits; + advance(last, -digits); + } } if (remaining_bits_to_copy > 0) { + it--; *it = _bitblend( *it, static_cast(get_word(last - remaining_bits_to_copy, remaining_bits_to_copy) diff --git a/include/bitlib/bit-algorithms/count.hpp b/include/bitlib/bit-algorithms/count.hpp index 3d69a011..4bcc5b49 100644 --- a/include/bitlib/bit-algorithms/count.hpp +++ b/include/bitlib/bit-algorithms/count.hpp @@ -95,7 +95,10 @@ count( //); // libpopcnt - result += popcnt(&*it, (digits / 8) * std::distance(it, last.base())); + auto words = (digits / 8) * std::distance(it, last.base()); + if (0 != words) { + result += popcnt(&*it, words); + } } if (last.position() != 0) { word_type last_value = *last.base() << (digits - last.position()); diff --git a/include/bitlib/bit-algorithms/shift.hpp b/include/bitlib/bit-algorithms/shift.hpp index c93da4ea..ab36dd92 100644 --- a/include/bitlib/bit-algorithms/shift.hpp +++ b/include/bitlib/bit-algorithms/shift.hpp @@ -9,7 +9,6 @@ #define _SHIFT_HPP_INCLUDED // ========================================================================== // - // ================================ PREAMBLE ================================ // // C++ standard library #include @@ -22,7 +21,7 @@ HWY_BEFORE_NAMESPACE(); #endif // Miscellaneous #define is_aligned(POINTER, BYTE_COUNT) \ - (((uintptr_t)(const void *)(POINTER)) % (BYTE_COUNT) == 0) + (((uintptr_t)(const void *)(POINTER)) % (BYTE_COUNT) == 0) #if __cplusplus >= 202002L #define STD_SHIFT_RIGHT(FIRST, LAST, N) std::shift_right(FIRST, LAST, N) @@ -39,41 +38,40 @@ namespace hn = hwy::HWY_NAMESPACE; #endif // ========================================================================== // - - // --------------------------- Shift Algorithms ----------------------------- // template bit_iterator shift_left( - bit_iterator first, - bit_iterator last, - typename bit_iterator::difference_type n -) { - // Assertions - _assert_range_viability(first, last); - - // Types and constants - using word_type = typename bit_iterator::word_type; - using size_type = typename bit_iterator::size_type; - using difference_type = typename bit_iterator::difference_type; - constexpr size_type digits = binary_digits::value; - - // Initialization - auto d = bit::distance(first, last); - const bool is_first_aligned = first.position() == 0; - const bool is_last_aligned = last.position() == 0; - auto middle = first + n; - - // Out of range cases - if (n <= 0) return last; - if (n >= d) { - //bit::fill(first, last, bit::bit0); - return first; - } + bit_iterator first, + bit_iterator last, + typename bit_iterator::difference_type n) { + // Assertions + _assert_range_viability(first, last); + + // Types and constants + using word_type = typename bit_iterator::word_type; + using size_type = typename bit_iterator::size_type; + using difference_type = typename bit_iterator::difference_type; + constexpr size_type digits = binary_digits::value; + + // Initialization + auto d = bit::distance(first, last); + const bool is_first_aligned = first.position() == 0; + const bool is_last_aligned = last.position() == 0; + auto middle = first + n; + + // Out of range cases + if (n <= 0) { + return last; + } + if (n >= d) { + //bit::fill(first, last, bit::bit0); + return first; + } - // Single word case - // Triggered if all relevant bits are in first.base() - // clang-format off + // Single word case + // Triggered if all relevant bits are in first.base() + // clang-format off if (std::next(first.base(), is_last_aligned) == last.base()) { *first.base() = _bitblend( *first.base(), @@ -89,283 +87,274 @@ bit_iterator shift_left( ); return first + d - n; } - // clang-format on - - // Triggered if all remaining bits can fit in a word - if (d - n <= digits) - { - word_type new_word = get_word(middle, d - n); - write_word(new_word, first, d - n); - return first + d - n; - } - - // Align first - if (!is_first_aligned) { - if (first.position() >= middle.position()) { - *first.base() = _bitblend( - *first.base(), - static_cast((*middle.base()) << (first.position() - middle.position())), - first.position(), - digits - first.position()); - } else { - const int n1 = digits - middle.position(); - const int n2 = digits - first.position() - n1; - *first.base() = _bitblend( - *first.base(), - lsr(*middle.base(), (middle.position() - first.position())), - first.position(), - n1); - *first.base() = _bitblend( - *first.base(), - static_cast((*std::next(middle.base())) << (digits - n2)), - first.position() + n1, - n2); - } - const int shifted = std::min(d - n, (digits - first.position())); - first += shifted; - middle += shifted; - } - if (middle.base() == last.base()) - { - const int bits_left = last.position() - middle.position(); - if (bits_left > 0) - { - *first.base() = _bitblend( - *first.base(), - lsr(*middle.base(), middle.position()), - 0, - bits_left); - first += bits_left; - } - // https://en.cppreference.com/w/cpp/algorithm/shift - // "Elements that are in the original range but not the new range - // are left in a valid but unspecified state." - // - //bit::fill(first, last, bit::bit0); - return first; - } - - // More initialization - d = bit::distance(first, last); - const size_type word_shifts = n / digits; - const size_type offset = middle.position(); - - // At this point, first is aligned - if (offset == 0) - { - first = bit::bit_iterator( - STD_SHIFT_LEFT(first.base(), - last.base(), - word_shifts), - 0); - if (!is_last_aligned) { - write_word(*last.base(), first, last.position()); - first += last.position(); - } - // https://en.cppreference.com/w/cpp/algorithm/shift - // "Elements that are in the original range but not the new range - // are left in a valid but unspecified state." - // - //bit::fill(first, last, bit::bit0); - return first; - } - - // Shift bit sequence to the lsb -#ifdef BITLIB_HWY - // Align to 64 bit boundary - while (std::next(middle.base()) < last.base() && !is_aligned(&*first.base(), 64)) { - *first.base() = _shrd(*middle.base(), *std::next(middle.base()), offset); - first += digits; - middle += digits; - } - - const hn::ScalableTag d_tag; - while (std::distance(middle.base(), last.base()) >= hn::Lanes(d_tag) + 10 + !is_last_aligned) - { - const auto v = hn::ShiftRightSame(hn::LoadU(d_tag, &*middle.base()), offset); - const auto v_plus1 = hn::ShiftLeftSame(hn::LoadU(d_tag, &*(middle.base()+1)), digits - offset); - hn::Store(v | v_plus1, d_tag, &*first.base()); - first += hn::Lanes(d_tag)*digits; - middle += hn::Lanes(d_tag)*digits; - } -#endif - auto first_base = first.base(); - auto middle_base = middle.base(); - - while (std::next(middle_base) < last.base()) { - *first_base = _shrd(*middle_base, *std::next(middle_base), offset); - first_base++; - middle_base++;; + // clang-format on + + // Triggered if all remaining bits can fit in a word + if (d - n <= digits) { + word_type new_word = get_word(middle, d - n); + write_word(new_word, first, d - n); + return first + d - n; + } + + // Align first + if (!is_first_aligned) { + if (first.position() >= middle.position()) { + *first.base() = _bitblend( + *first.base(), + static_cast((*middle.base()) << (first.position() - middle.position())), + first.position(), + digits - first.position()); + } else { + const int n1 = digits - middle.position(); + const int n2 = digits - first.position() - n1; + *first.base() = _bitblend( + *first.base(), + lsr(*middle.base(), (middle.position() - first.position())), + first.position(), + n1); + *first.base() = _bitblend( + *first.base(), + static_cast((*std::next(middle.base())) << (digits - n2)), + first.position() + n1, + n2); } - first = bit_iterator(first_base, 0); - middle = bit_iterator(middle_base, middle.position()); - - // If middle is now penultimate word - if (std::next(middle.base()) == last.base()) - { + const int shifted = std::min(d - n, (digits - first.position())); + first += shifted; + middle += shifted; + } + if (middle.base() == last.base()) { + const int bits_left = last.position() - middle.position(); + if (bits_left > 0) { *first.base() = _bitblend( *first.base(), - lsr(*middle.base(), offset), + lsr(*middle.base(), middle.position()), 0, - digits - offset); - first += digits - offset; - middle += digits - offset; + bits_left); + first += bits_left; } - - if (!is_last_aligned) - { - const difference_type bits_left = last.position() - middle.position(); - const word_type new_word = get_word(middle, bits_left); - write_word(new_word, first, bits_left); - first += bits_left; + // https://en.cppreference.com/w/cpp/algorithm/shift + // "Elements that are in the original range but not the new range + // are left in a valid but unspecified state." + // + //bit::fill(first, last, bit::bit0); + return first; + } + + // More initialization + d = bit::distance(first, last); + const size_type word_shifts = n / digits; + const size_type offset = middle.position(); + + // At this point, first is aligned + if (offset == 0) { + first = bit::bit_iterator( + STD_SHIFT_LEFT(first.base(), + last.base(), + word_shifts), + 0); + if (!is_last_aligned) { + write_word(*last.base(), first, last.position()); + first += last.position(); } - + // https://en.cppreference.com/w/cpp/algorithm/shift + // "Elements that are in the original range but not the new range + // are left in a valid but unspecified state." + // //bit::fill(first, last, bit::bit0); return first; + } + + // Shift bit sequence to the lsb +#ifdef BITLIB_HWY + // Align to 64 bit boundary + while (std::next(middle.base()) < last.base() && !is_aligned(&*first.base(), 64)) { + *first.base() = _shrd(*middle.base(), *std::next(middle.base()), offset); + first += digits; + middle += digits; + } + + const hn::ScalableTag d_tag; + while (std::distance(middle.base(), last.base()) >= hn::Lanes(d_tag) + 10 + !is_last_aligned) { + const auto v = hn::ShiftRightSame(hn::LoadU(d_tag, &*middle.base()), offset); + const auto v_plus1 = hn::ShiftLeftSame(hn::LoadU(d_tag, &*(middle.base() + 1)), digits - offset); + hn::Store(v | v_plus1, d_tag, &*first.base()); + first += hn::Lanes(d_tag) * digits; + middle += hn::Lanes(d_tag) * digits; + } +#endif + auto first_base = first.base(); + auto middle_base = middle.base(); + + while (std::next(middle_base) < last.base()) { + *first_base = _shrd(*middle_base, *std::next(middle_base), offset); + first_base++; + middle_base++; + ; + } + first = bit_iterator(first_base, 0); + middle = bit_iterator(middle_base, middle.position()); + + // If middle is now penultimate word + if (std::next(middle.base()) == last.base()) { + *first.base() = _bitblend( + *first.base(), + lsr(*middle.base(), offset), + 0, + digits - offset); + first += digits - offset; + middle += digits - offset; + } + + if (!is_last_aligned) { + const difference_type bits_left = last.position() - middle.position(); + const word_type new_word = get_word(middle, bits_left); + write_word(new_word, first, bits_left); + first += bits_left; + } + + //bit::fill(first, last, bit::bit0); + return first; } template bit_iterator shift_right( - bit_iterator first, - bit_iterator last, - typename bit_iterator::difference_type n -) { - // Types and constants - using word_type = typename bit_iterator::word_type; - using size_type = typename bit_iterator::size_type; - - // Initialization - const bool is_last_aligned = last.position() == 0; - constexpr auto digits = binary_digits::value; - auto d = bit::distance(first, last); - bit_iterator middle = last - n; - - // Out of range cases - if (n <= 0) return first; - else if (n >= d) return last; - - // Single word case - if (std::next(first.base(), is_last_aligned) == last.base()) { - *first.base() = _bitblend( - *first.base(), - static_cast( - (*first.base() & (static_cast(-1) << first.position())) - << n), - static_cast(first.position()), - static_cast((is_last_aligned ? digits : last.position()) - first.position())); + bit_iterator first, + bit_iterator last, + typename bit_iterator::difference_type n) { + // Types and constants + using word_type = typename bit_iterator::word_type; + using size_type = typename bit_iterator::size_type; + + // Initialization + const bool is_last_aligned = last.position() == 0; + constexpr auto digits = binary_digits::value; + auto d = bit::distance(first, last); + + // Out of range cases + if (n <= 0) { + return first; + } else if (n >= d) { + return last; + } + + bit_iterator middle = last - n; + + // Single word case + if (std::next(first.base(), is_last_aligned) == last.base()) { + *first.base() = _bitblend( + *first.base(), + static_cast( + (*first.base() & (static_cast(-1) << first.position())) + << n), + static_cast(first.position()), + static_cast((is_last_aligned ? digits : last.position()) - first.position())); + return first + n; + } + + // Align last + if (last.position() != 0) { + const size_type bits_to_align = std::min( + last.position(), + bit::distance(first, middle)); + const word_type word_to_write = get_word( + middle - bits_to_align, + bits_to_align); + write_word( + word_to_write, + last - bits_to_align, + bits_to_align); + middle -= bits_to_align; + last -= bits_to_align; + + // Nothing left to do + if (middle == first) { return first + n; } + } + + // More initialization + const size_type word_shifts = n / digits; + const size_type offset = middle.position(); + + // Shift bit sequence to the msb + if (offset == 0) { + /*auto new_first = */ + static_cast(bit::bit_iterator( + STD_SHIFT_RIGHT( + first.base(), + last.base(), + word_shifts), + first.position())); + // https://en.cppreference.com/w/cpp/algorithm/shift + // "Elements that are in the original range but not the new range + // are left in a valid but unspecified state." + // + //bit::fill(first, new_first, bit::bit0); + return first + n; + } - // Align last - if (last.position() != 0) - { - const size_type bits_to_align = std::min( - last.position(), - bit::distance(first, middle)); - const word_type word_to_write = get_word( - middle - bits_to_align, - bits_to_align); - write_word( - word_to_write, - last - bits_to_align, - bits_to_align); - middle -= bits_to_align; - last -= bits_to_align; - - // Nothing left to do - if (middle == first) - return first + n; + if (bit::distance(first, middle) >= digits) { +#ifdef BITLIB_HWY + // Align to 64 bit boundary + const hn::ScalableTag d; + while (std::prev(middle.base()) > first.base() && !is_aligned(&*(last.base() - hn::Lanes(d)), 64)) { + *std::prev(last.base()) = _shrd(*std::prev(middle.base()), *middle.base(), offset); + last -= digits; + middle -= digits; } - // More initialization - const size_type word_shifts = n / digits; - const size_type offset = middle.position(); - - // Shift bit sequence to the msb - if (offset == 0) { - /*auto new_first = */ - static_cast(bit::bit_iterator( - STD_SHIFT_RIGHT( - first.base(), - last.base(), - word_shifts), - first.position())); - // https://en.cppreference.com/w/cpp/algorithm/shift - // "Elements that are in the original range but not the new range - // are left in a valid but unspecified state." - // - //bit::fill(first, new_first, bit::bit0); - return first + n; - } + while (std::distance(first.base(), middle.base()) > hn::Lanes(d) + 1) { + const auto v = hn::ShiftRightSame( + hn::LoadU(d, &*(middle.base() - hn::Lanes(d))), + offset); + const auto v_plus1 = hn::ShiftLeftSame( + hn::LoadU(d, &*(middle.base() - hn::Lanes(d) + 1)), + digits - offset); + hn::Store(v | v_plus1, d, &*(last.base() - hn::Lanes(d))); - if (bit::distance(first, middle) >= digits) - { -#ifdef BITLIB_HWY - // Align to 64 bit boundary - const hn::ScalableTag d; - while (std::prev(middle.base()) > first.base() && !is_aligned(&*(last.base() - hn::Lanes(d)), 64)) { - *std::prev(last.base()) = _shrd(*std::prev(middle.base()), *middle.base(), offset); - last -= digits; - middle -= digits; - } - - while (std::distance(first.base(), middle.base()) > hn::Lanes(d) + 1) - { - const auto v = hn::ShiftRightSame( - hn::LoadU(d, &*(middle.base() - hn::Lanes(d))), - offset); - const auto v_plus1 = hn::ShiftLeftSame( - hn::LoadU(d, &*(middle.base() - hn::Lanes(d) + 1)), - digits - offset); - hn::Store(v | v_plus1, d, &*(last.base() - hn::Lanes(d))); - - last -= digits * hn::Lanes(d); - middle -= digits * hn::Lanes(d); - } + last -= digits * hn::Lanes(d); + middle -= digits * hn::Lanes(d); + } #endif - auto last_base_prev = std::prev(last.base()); - auto middle_base_prev = std::prev(middle.base()); - - while (middle_base_prev > first.base()) { - *last_base_prev = _shrd(*middle_base_prev, *std::next(middle_base_prev), offset); - last_base_prev--; - middle_base_prev--; - } + auto last_base_prev = std::prev(last.base()); + auto middle_base_prev = std::prev(middle.base()); - if (first.position() <= middle.position()) - { - *last_base_prev = _shrd(*middle_base_prev, *std::next(middle_base_prev), offset); - last_base_prev--; - middle_base_prev--; - } - - last = bit_iterator(std::next(last_base_prev), last.position()); - middle = bit_iterator(std::next(middle_base_prev), middle.position()); + while (middle_base_prev > first.base()) { + *last_base_prev = _shrd(*middle_base_prev, *std::next(middle_base_prev), offset); + last_base_prev--; + middle_base_prev--; } - if (first.position() != middle.position()) - { - const size_type bits_to_align = bit::distance(first, middle); - const word_type word_to_write = get_word( - first, - bits_to_align); - write_word( - word_to_write, - last - bits_to_align, - bits_to_align); + if (first.position() <= middle.position()) { + *last_base_prev = _shrd(*middle_base_prev, *std::next(middle_base_prev), offset); + last = bit_iterator(last_base_prev, last.position()); + middle = bit_iterator(middle_base_prev, middle.position()); + } else { + last = bit_iterator(std::next(last_base_prev), last.position()); + middle = bit_iterator(std::next(middle_base_prev), middle.position()); } - - return first + n; + } + + if (first.position() != middle.position()) { + const size_type bits_to_align = bit::distance(first, middle); + const word_type word_to_write = get_word( + first, + bits_to_align); + write_word( + word_to_write, + last - bits_to_align, + bits_to_align); + } + + return first + n; } // -------------------------------------------------------------------------- // - - // ========================================================================== // -} // namespace bit +} // namespace bit #ifdef BITLIB_HWY HWY_AFTER_NAMESPACE(); #endif -#endif // _SHIFT_HPP_INCLUDED +#endif // _SHIFT_HPP_INCLUDED // ========================================================================== // diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index 3c334dbc..c0447b06 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -59,10 +59,14 @@ class array private: using word_type_ptr = word_type*; - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wsizeof-pointer-div" +#if defined(__clang__) || defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsizeof-pointer-div" +#endif static const size_type FixedWords = sizeof(word_type_ptr) / sizeof(word_type); - #pragma GCC diagnostic pop +#if defined(__clang__) || defined(__GNUC__) +#pragma GCC diagnostic pop +#endif static const size_type FixedBits = FixedWords * bitsof(); struct Storage { @@ -142,9 +146,10 @@ class array ~array() { if (size() > FixedBits) { storage.m_allocator.deallocate(storage.pointer, Words(size())); - } else { + } else if constexpr (!std::is_fundamental_v) { for (size_type i = 0; i < Words(size()); ++i) { - storage.fixed[i].~word_type(); + // W is 'word_type', but MSVC is unhappy with using an alias + storage.fixed[i].~W(); } } } diff --git a/include/bitlib/bit-containers/bit_vector.hpp b/include/bitlib/bit-containers/bit_vector.hpp index cb5703d2..f65f8acb 100644 --- a/include/bitlib/bit-containers/bit_vector.hpp +++ b/include/bitlib/bit-containers/bit_vector.hpp @@ -582,12 +582,13 @@ bit_vector::insert( template constexpr typename bit_vector::iterator bit_vector::erase(iterator pos) { - shift_left(pos, begin() + length_, 1); - length_ -= 1; - if (length_ % digits == 0) { - word_vector.pop_back(); - } - return pos; + difference_type d = distance(begin(), pos); + shift_left(pos, begin() + length_, 1); + length_ -= 1; + if (length_ % digits == 0) { + word_vector.pop_back(); + } + return begin() + d; } template diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index 971fa850..f56725be 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -15,12 +15,25 @@ // ================================ PREAMBLE ================================ // // C++ standard library - -#if __has_include() -#include +// clang-format off +#if defined(_MSC_VER) + #if __has_include() + #include + #else + #define NO_X86_INTRINSICS + #endif #else -#define NO_X86_INTRINSICS -#endif + #if __has_include() + #include + #else + #if __has_include() + #include + #else + #define NO_X86_INTRINSICS + #endif + #endif +#endif // defined(_MSC_VER) +// clang-format on #include #include @@ -742,4 +755,4 @@ struct container_size_storage { // ========================================================================== // } // namespace bit #endif // _BIT_DETAILS_HPP_INCLUDED -// ========================================================================== // \ No newline at end of file + // ========================================================================== // \ No newline at end of file diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index 52bafad7..68844324 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -52,8 +52,8 @@ class bit_reference { // Lifecycle public: constexpr bit_reference(const bit_reference& other) noexcept; - explicit constexpr bit_reference(WordRef ref) noexcept; - constexpr bit_reference(WordRef ref, size_type pos); + explicit constexpr bit_reference(const WordRef& ref) noexcept; + constexpr bit_reference(const WordRef& ref, size_type pos); // Assignment public: @@ -129,13 +129,13 @@ constexpr bit_reference::bit_reference(const bit_reference& ot // Explicitly constructs an aligned bit reference template -constexpr bit_reference::bit_reference(WordRef ref) noexcept +constexpr bit_reference::bit_reference(const WordRef& ref) noexcept : _ref(ref), _mask(1) { } // Explicitly constructs an unaligned bit reference template -constexpr bit_reference::bit_reference(WordRef ref, size_type pos) +constexpr bit_reference::bit_reference(const WordRef& ref, size_type pos) : _ref(ref), _mask(static_cast(1) << pos) { assert(pos < binary_digits::value); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f50e47ae..2e4bd50e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -22,32 +22,35 @@ endif() add_executable(bitlib-tests) -target_sources(bitlib-tests PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/src/fixtures.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-array.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-bitwise.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-copy_backward.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-copy.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-count.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-count_leading.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-equal.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-fill.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-find.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-iterator_adapter.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-literal.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-move.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-reverse.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-rotate.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-shift.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-span.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-swap_ranges.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-to_string.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-transform.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-usecase.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/test-array_ref.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/vector_test.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/inc/test_utils.hpp +set(TEST_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/src/fixtures.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-array.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-bitwise.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-copy_backward.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-copy.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-count.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-count_leading.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-equal.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-fill.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-find.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-iterator_adapter.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-literal.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-move.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-reverse.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-rotate.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-shift.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-span.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-swap_ranges.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-to_string.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-transform.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-usecase.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-array_ref.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/vector_test.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/inc/test_utils.hpp" ) + +target_sources(bitlib-tests PRIVATE ${TEST_SOURCES}) + if (BITLIB_MDSPAN) target_sources(bitlib-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/test-mdspan.cpp) endif() @@ -69,6 +72,10 @@ if (NOT BITLIB_GTEST_REPEAT) set(BITLIB_GTEST_REPEAT 1) endif() -gtest_discover_tests( +if (CMAKE_CROSSCOMPILING) + gtest_add_tests("./$" ${TEST_SOURCES}) +else() + gtest_discover_tests( bitlib-tests EXTRA_ARGS --gtest_repeat=${BITLIB_GTEST_REPEAT}) +endif() diff --git a/test/inc/test_utils.hpp b/test/inc/test_utils.hpp index 3eead5cf..46f1effe 100644 --- a/test/inc/test_utils.hpp +++ b/test/inc/test_utils.hpp @@ -93,16 +93,37 @@ inline unsigned long long generate_random_number(size_t min, size_t max) { return dist(mersenne_engine); } +// clang-format off +template + requires std::integral +struct uniform_dist_type { + static constexpr bool is_signed = std::is_signed_v; + static constexpr std::size_t size = sizeof(T); + + using type = std::conditional_t, + std::conditional_t, + std::conditional_t, + void // fallback (shouldn't happen for standard integral types) + >>>; +}; +// clang-format on + +template +using uniform_dist_type_t = typename uniform_dist_type::type; + template std::array get_random_arr( WordType min = std::numeric_limits::min(), WordType max = std::numeric_limits::max()) { // Specify the engine and distribution. std::mt19937 mersenne_engine = GetSeededRNGFromTestName(); - std::uniform_int_distribution dist{min, max}; + std::uniform_int_distribution> dist{min, max}; auto gen = [&dist, &mersenne_engine]() { - return dist(mersenne_engine); + return static_cast(dist(mersenne_engine)); }; std::array arr{}; generate(begin(arr), end(arr), gen); @@ -116,10 +137,10 @@ std::vector get_random_vec( WordType max = std::numeric_limits::max() ) { std::mt19937 mersenne_engine = GetSeededRNGFromTestName(); - std::uniform_int_distribution dist{min, max}; + std::uniform_int_distribution> dist{min, max}; auto gen = [&dist, &mersenne_engine]() { - return dist(mersenne_engine); + return static_cast(dist(mersenne_engine)); }; std::vector vec(size); generate(begin(vec), end(vec), gen); diff --git a/test/src/vector_test.cpp b/test/src/vector_test.cpp index 78c5e241..d78a603d 100644 --- a/test/src/vector_test.cpp +++ b/test/src/vector_test.cpp @@ -166,7 +166,7 @@ TYPED_TEST(VectorTest, StringConstructor) { TYPED_TEST(VectorTest, CopyAssignment) { for (auto _ = 0; _ < 128; ++_) { this->empty_vec = this->v2_; - EXPECT_NE(this->empty_vec.begin().base(), this->v2_.begin().base()); + EXPECT_NE(std::to_address(this->empty_vec.begin().base()), std::to_address(this->v2_.begin().base())); EXPECT_TRUE(std::equal(this->empty_vec.begin(), this->empty_vec.end(), this->v2_.begin())); EXPECT_EQ(this->empty_vec.size(), this->v2_.size()); this->v2_.insert(this->v2_.end(), bit::bit1); @@ -174,59 +174,58 @@ TYPED_TEST(VectorTest, CopyAssignment) { this->v2_.insert(this->v2_.end(), bit::bit1); } for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { - auto& bitvec = this->random_bitvecs[vec_idx]; - auto& boolvec = this->random_boolvecs[vec_idx]; - this->empty_vec = bitvec; - EXPECT_NE(this->empty_vec.begin().base(), bitvec.begin().base()); - EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - boolvec.begin(), - boolvec.end(), - comparator)); - if (bitvec.size() > 0) { - bitvec[0] = ~bitvec[0]; - EXPECT_NE(bitvec[0], this->empty_vec[0]); - } + auto& bitvec = this->random_bitvecs[vec_idx]; + auto& boolvec = this->random_boolvecs[vec_idx]; + this->empty_vec = bitvec; + EXPECT_NE(std::to_address(this->empty_vec.begin().base()), std::to_address(bitvec.begin().base())); + EXPECT_TRUE(std::equal( + this->empty_vec.begin(), + this->empty_vec.end(), + boolvec.begin(), + boolvec.end(), + comparator)); + if (bitvec.size() > 0) { + bitvec[0] = ~bitvec[0]; + EXPECT_NE(bitvec[0], this->empty_vec[0]); + } } } // Test the move c'tor TYPED_TEST(VectorTest, MoveAssignment) { - bit::bit_vector v2_copy = this->v2_; - auto v2_data = this->v2_.begin().base(); - this->empty_vec = std::move(this->v2_); - EXPECT_EQ(this->empty_vec.begin().base(), v2_data); - EXPECT_TRUE(std::equal(this->empty_vec.begin(), this->empty_vec.end(), v2_copy.begin())); - EXPECT_EQ(this->empty_vec.size(), v2_copy.size()); - for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { - auto& bitvec = this->random_bitvecs[vec_idx]; - auto& boolvec = this->random_boolvecs[vec_idx]; - auto bitvec_base = bitvec.begin().base(); - this->empty_vec = std::move(bitvec); - EXPECT_EQ(this->empty_vec.begin().base(), bitvec_base); - EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - boolvec.begin(), - boolvec.end(), - comparator)); - } + bit::bit_vector v2_copy = this->v2_; + auto v2_data = this->v2_.begin().base(); + this->empty_vec = std::move(this->v2_); + EXPECT_EQ(std::to_address(this->empty_vec.begin().base()), std::to_address(v2_data)); + EXPECT_TRUE(std::equal(this->empty_vec.begin(), this->empty_vec.end(), v2_copy.begin())); + EXPECT_EQ(this->empty_vec.size(), v2_copy.size()); + for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { + auto& bitvec = this->random_bitvecs[vec_idx]; + auto& boolvec = this->random_boolvecs[vec_idx]; + auto bitvec_base = bitvec.begin().base(); + this->empty_vec = std::move(bitvec); + EXPECT_EQ(std::to_address(this->empty_vec.begin().base()), std::to_address(bitvec_base)); + EXPECT_TRUE(std::equal( + this->empty_vec.begin(), + this->empty_vec.end(), + boolvec.begin(), + boolvec.end(), + comparator)); + } } - // Test the initializer list c'tor TYPED_TEST(VectorTest, InitializerListConstructor) { - bit::bit_vector v2_copy = this->v2_; - using VecType = typename TestFixture::vec_type; - std::vector boolvec {true, false, true, true, true, false, false, true, false, true, true, false}; - VecType test{true, false, true, true, true, false, false, true, false, true, true, false}; - EXPECT_TRUE(std::equal( - test.begin(), - test.end(), - boolvec.begin(), - boolvec.end(), - comparator)); + bit::bit_vector v2_copy = this->v2_; + using VecType = typename TestFixture::vec_type; + std::vector boolvec{true, false, true, true, true, false, false, true, false, true, true, false}; + VecType test{true, false, true, true, true, false, false, true, false, true, true, false}; + EXPECT_TRUE(std::equal( + test.begin(), + test.end(), + boolvec.begin(), + boolvec.end(), + comparator)); } /* @@ -235,34 +234,34 @@ TYPED_TEST(VectorTest, InitializerListConstructor) { // Test read TYPED_TEST(VectorTest, BracketRead) { - EXPECT_EQ(this->v3_[0], bit::bit0); - for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { - auto& bitvec = this->random_bitvecs[vec_idx]; - auto& boolvec = this->random_boolvecs[vec_idx]; - for (unsigned int i = 0; i < boolvec.size(); i++) { - EXPECT_TRUE(comparator(bitvec[i], boolvec[i])); - } + EXPECT_EQ(this->v3_[0], bit::bit0); + for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { + auto& bitvec = this->random_bitvecs[vec_idx]; + auto& boolvec = this->random_boolvecs[vec_idx]; + for (unsigned int i = 0; i < boolvec.size(); i++) { + EXPECT_TRUE(comparator(bitvec[i], boolvec[i])); } + } } // Test write TYPED_TEST(VectorTest, BracketWrite) { - this->v3_[0] = bit::bit1; - EXPECT_EQ(this->v3_[0], bit::bit1); - for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { - auto& bitvec = this->random_bitvecs[vec_idx]; - auto& boolvec = this->random_boolvecs[vec_idx]; - for (unsigned int i = 0; i < boolvec.size(); i++) { - bitvec[i] = ~bitvec[i]; - boolvec[i] = !boolvec[i]; - } - EXPECT_TRUE(std::equal( - bitvec.begin(), - bitvec.end(), - boolvec.begin(), - boolvec.end(), - comparator)); -} + this->v3_[0] = bit::bit1; + EXPECT_EQ(this->v3_[0], bit::bit1); + for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { + auto& bitvec = this->random_bitvecs[vec_idx]; + auto& boolvec = this->random_boolvecs[vec_idx]; + for (unsigned int i = 0; i < boolvec.size(); i++) { + bitvec[i] = ~bitvec[i]; + boolvec[i] = !boolvec[i]; + } + EXPECT_TRUE(std::equal( + bitvec.begin(), + bitvec.end(), + boolvec.begin(), + boolvec.end(), + comparator)); + } } // Test at @@ -283,40 +282,38 @@ TYPED_TEST(VectorTest, AtRead) { } } - /* * Iterators */ //TODO - /* * Capacity */ // Test empty TYPED_TEST(VectorTest, Empty) { - EXPECT_TRUE(this->empty_vec.empty()); - EXPECT_FALSE(this->v2_.empty()); - this->v2_.clear(); - EXPECT_TRUE(this->v2_.empty()); + EXPECT_TRUE(this->empty_vec.empty()); + EXPECT_FALSE(this->v2_.empty()); + this->v2_.clear(); + EXPECT_TRUE(this->v2_.empty()); } // Test size TYPED_TEST(VectorTest, Size) { - EXPECT_EQ(this->empty_vec.size(), 0); - EXPECT_EQ(this->v2_.size(), 18); - EXPECT_EQ(this->v3_.size(), 9); + EXPECT_EQ(this->empty_vec.size(), 0); + EXPECT_EQ(this->v2_.size(), 18); + EXPECT_EQ(this->v3_.size(), 9); } // Test reserve and capacity TYPED_TEST(VectorTest, ReserveAndCapacity) { - this->empty_vec.reserve(1); - EXPECT_GE(this->empty_vec.capacity(), 1); - this->empty_vec.reserve(9); - EXPECT_GE(this->empty_vec.capacity(), 9); - this->empty_vec.reserve(64); - EXPECT_GE(this->empty_vec.capacity(), 64); + this->empty_vec.reserve(1); + EXPECT_GE(this->empty_vec.capacity(), 1); + this->empty_vec.reserve(9); + EXPECT_GE(this->empty_vec.capacity(), 9); + this->empty_vec.reserve(64); + EXPECT_GE(this->empty_vec.capacity(), 64); } // Test shrink_to_fit @@ -345,22 +342,22 @@ TYPED_TEST(VectorTest, ShrinkToFit) { // Test clear TYPED_TEST(VectorTest, Clear) { - this->empty_vec.clear(); - EXPECT_EQ(this->empty_vec.size(), 0); - this->empty_vec.reserve(1); - EXPECT_EQ(this->empty_vec.size(), 0); - this->empty_vec.clear(); - EXPECT_EQ(this->empty_vec.size(), 0); - this->v2_.clear(); - EXPECT_EQ(this->v2_.size(), 0); - this->v3_.clear(); - EXPECT_EQ(this->v3_.size(), 0); - for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { - auto& bitvec = this->random_bitvecs[vec_idx]; - bitvec.clear(); - EXPECT_EQ(bitvec.size(), 0); - EXPECT_THROW(bitvec.at(0), std::out_of_range); - } + this->empty_vec.clear(); + EXPECT_EQ(this->empty_vec.size(), 0); + this->empty_vec.reserve(1); + EXPECT_EQ(this->empty_vec.size(), 0); + this->empty_vec.clear(); + EXPECT_EQ(this->empty_vec.size(), 0); + this->v2_.clear(); + EXPECT_EQ(this->v2_.size(), 0); + this->v3_.clear(); + EXPECT_EQ(this->v3_.size(), 0); + for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { + auto& bitvec = this->random_bitvecs[vec_idx]; + bitvec.clear(); + EXPECT_EQ(bitvec.size(), 0); + EXPECT_THROW(bitvec.at(0), std::out_of_range); + } } // Test insert @@ -519,125 +516,130 @@ TYPED_TEST(VectorTest, InsertAtRand2) { } TYPED_TEST(VectorTest, InsertAtBegin3) { - // Third signature - for (auto _ = 16; _--;) { - auto to_insert = generate_random_number(0, this->random_bitvecs.size() - 1); - auto insert_bitvec = this->random_bitvecs[to_insert]; - auto insert_boolvec = this->random_boolvecs[to_insert]; - auto bitret = this->empty_vec.insert( - this->empty_vec.begin(), - insert_bitvec.begin(), - insert_bitvec.end()); - auto boolret = this->empty_vec_bool.insert( - this->empty_vec_bool.begin(), - insert_boolvec.begin(), - insert_boolvec.end()); - EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), - comparator)); - EXPECT_EQ( - std::distance(this->empty_vec.begin(), bitret), - std::distance(this->empty_vec_bool.begin(), boolret)); - } + // Third signature + for (auto _ = 16; _--;) { + auto to_insert = generate_random_number(0, this->random_bitvecs.size() - 1); + auto insert_bitvec = this->random_bitvecs[to_insert]; + auto insert_boolvec = this->random_boolvecs[to_insert]; + auto bitret = this->empty_vec.insert( + this->empty_vec.begin(), + insert_bitvec.begin(), + insert_bitvec.end()); + auto boolret = this->empty_vec_bool.insert( + this->empty_vec_bool.begin(), + insert_boolvec.begin(), + insert_boolvec.end()); + EXPECT_TRUE(std::equal( + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), + comparator)); + EXPECT_EQ( + std::distance(this->empty_vec.begin(), bitret), + std::distance(this->empty_vec_bool.begin(), boolret)); + } } TYPED_TEST(VectorTest, InsertAtEnd3) { - // Third signature - for (auto _ = 16; _--;) { - auto to_insert = generate_random_number(0, this->random_bitvecs.size() - 1); - auto insert_bitvec = this->random_bitvecs[to_insert]; - auto insert_boolvec = this->random_boolvecs[to_insert]; - auto bitret = this->empty_vec.insert( - this->empty_vec.end(), - insert_bitvec.begin(), - insert_bitvec.end()); - auto boolret = this->empty_vec_bool.insert( - this->empty_vec_bool.end(), - insert_boolvec.begin(), - insert_boolvec.end()); - EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), - comparator)); - EXPECT_EQ( - std::distance(this->empty_vec.begin(), bitret), - std::distance(this->empty_vec_bool.begin(), boolret)); - } + // Third signature + for (auto _ = 16; _--;) { + auto to_insert = generate_random_number(0, this->random_bitvecs.size() - 1); + auto insert_bitvec = this->random_bitvecs[to_insert]; + auto insert_boolvec = this->random_boolvecs[to_insert]; + auto bitret = this->empty_vec.insert( + this->empty_vec.end(), + insert_bitvec.begin(), + insert_bitvec.end()); + auto boolret = this->empty_vec_bool.insert( + this->empty_vec_bool.end(), + insert_boolvec.begin(), + insert_boolvec.end()); + EXPECT_TRUE(std::equal( + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), + comparator)); + EXPECT_EQ( + std::distance(this->empty_vec.begin(), bitret), + std::distance(this->empty_vec_bool.begin(), boolret)); + } } TYPED_TEST(VectorTest, InsertAtRand3) { - // Third signature - for (auto _ = 16; _--;) { - auto to_insert = generate_random_number(0, this->random_bitvecs.size() - 1); - auto insert_bitvec = this->random_bitvecs[to_insert]; - auto insert_boolvec = this->random_boolvecs[to_insert]; - auto insert_location = generate_random_number(0, this->empty_vec.size()); - auto bitret = this->empty_vec.insert( - this->empty_vec.begin() + insert_location, - insert_bitvec.begin(), - insert_bitvec.end()); - auto boolret = this->empty_vec_bool.insert( - this->empty_vec_bool.begin() + insert_location, - insert_boolvec.begin(), - insert_boolvec.end()); - EXPECT_TRUE(std::equal( - this->empty_vec.begin(), - this->empty_vec.end(), - this->empty_vec_bool.begin(), - this->empty_vec_bool.end(), - comparator)); - EXPECT_EQ( - std::distance(this->empty_vec.begin(), bitret), - std::distance(this->empty_vec_bool.begin(), boolret)); - } + // Third signature + for (auto _ = 16; _--;) { + auto to_insert = generate_random_number(0, this->random_bitvecs.size() - 1); + auto insert_bitvec = this->random_bitvecs[to_insert]; + auto insert_boolvec = this->random_boolvecs[to_insert]; + auto insert_location = generate_random_number(0, this->empty_vec.size()); + auto bitret = this->empty_vec.insert( + this->empty_vec.begin() + insert_location, + insert_bitvec.begin(), + insert_bitvec.end()); + auto boolret = this->empty_vec_bool.insert( + this->empty_vec_bool.begin() + insert_location, + insert_boolvec.begin(), + insert_boolvec.end()); + EXPECT_TRUE(std::equal( + this->empty_vec.begin(), + this->empty_vec.end(), + this->empty_vec_bool.begin(), + this->empty_vec_bool.end(), + comparator)); + EXPECT_EQ( + std::distance(this->empty_vec.begin(), bitret), + std::distance(this->empty_vec_bool.begin(), boolret)); + } } // Test erase TYPED_TEST(VectorTest, EraseAtBegin1) { - // First signature - for (auto _ = 16; _--;) { - auto vec_idx = generate_random_number(0, this->random_bitvecs.size() - 1); - auto& bitvec = this->random_bitvecs[vec_idx]; - auto& boolvec = this->random_boolvecs[vec_idx]; - if (boolvec.size() == 0) { continue; } - auto bitret = bitvec.erase(bitvec.begin()); - auto boolret = boolvec.erase(boolvec.begin()); - EXPECT_TRUE(std::equal( - bitvec.begin(), - bitvec.end(), - boolvec.begin(), - boolvec.end(), - comparator)); - EXPECT_EQ( - std::distance(bitvec.begin(), bitret), - std::distance(boolvec.begin(), boolret)); + // First signature + for (auto _ = 16; _--;) { + auto vec_idx = generate_random_number(0, this->random_bitvecs.size() - 1); + auto& bitvec = this->random_bitvecs[vec_idx]; + auto& boolvec = this->random_boolvecs[vec_idx]; + if (boolvec.size() == 0) { + continue; } + auto bitret = bitvec.erase(bitvec.begin()); + auto boolret = boolvec.erase(boolvec.begin()); + EXPECT_TRUE(std::equal( + bitvec.begin(), + bitvec.end(), + boolvec.begin(), + boolvec.end(), + comparator)); + EXPECT_EQ(bitvec.size(), boolvec.size()); + EXPECT_EQ( + std::distance(bitvec.begin(), bitret), + std::distance(boolvec.begin(), boolret)); + } } TYPED_TEST(VectorTest, EraseAtEnd1) { - // First signature - for (auto _ = 16; _--;) { - auto vec_idx = generate_random_number(0, this->random_bitvecs.size() - 1); - auto& bitvec = this->random_bitvecs[vec_idx]; - auto& boolvec = this->random_boolvecs[vec_idx]; - if (boolvec.size() == 0) { continue; } - auto bitret = bitvec.erase(bitvec.end() - 1); - auto boolret = boolvec.erase(boolvec.end() - 1); - EXPECT_TRUE(std::equal( - bitvec.begin(), - bitvec.end(), - boolvec.begin(), - boolvec.end(), - comparator)); - EXPECT_EQ( - std::distance(bitvec.begin(), bitret), - std::distance(boolvec.begin(), boolret)); + // First signature + for (auto _ = 16; _--;) { + auto vec_idx = generate_random_number(0, this->random_bitvecs.size() - 1); + auto& bitvec = this->random_bitvecs[vec_idx]; + auto& boolvec = this->random_boolvecs[vec_idx]; + if (boolvec.size() == 0) { + continue; } + auto bitret = bitvec.erase(bitvec.end() - 1); + auto boolret = boolvec.erase(boolvec.end() - 1); + EXPECT_TRUE(std::equal( + bitvec.begin(), + bitvec.end(), + boolvec.begin(), + boolvec.end(), + comparator)); + EXPECT_EQ( + std::distance(bitvec.begin(), bitret), + std::distance(boolvec.begin(), boolret)); + } } TYPED_TEST(VectorTest, EraseAtRand1) { From fd1dcf45de61adc3e4a5dff459756ae238815a3a Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Sat, 2 Aug 2025 02:24:03 +0000 Subject: [PATCH 78/85] From string (#18) * Fix size-copy construction for bitwise ops * Primitive from_string * Add iterator-argument flavors of typical policy * Allow copy to take native iterators and auto-convert * Use a single type / value to template string conversion * Rebase cleanup * Replace auto and fix namespace issues in to_from_string.hpp * Use proper aggregate initialization for array * clang being naughty * Fix to string length calculation * Some scalar Addition and Multiplication algorithms * transform must mask operands to functor * Don't try to convert before bitsof * Fix carry out for sub words * Division by scalar * Add define for checking undefined behavior * Basic from_string working * Base 10 to string * Non-templated to/from string functions * to_string can be bounded by string iterators * Reorder to_string arguments for in->out order * Add streaming operator to array_base * Fix error C2361 case variable initialization * Add step to download and extract clang compiler rt libs for windows * Replace instrinsic with std * Give up on directory flag to tar * Simplify cache step * Add new line to license. seems reasonable * Don't use the iterator below begin() * Allow examples to be built as if they were the root. Add a dynamic bit-aggregate type example * Optimize to string by change zero check to reduce computation size --- .../toolchains/clangcl_linux_to_windows.cmake | 27 +- .github/workflows/cmake_build_and_test.yml | 51 +++ CMakePresets.json | 2 +- LICENSE | 1 + README.md | 27 ++ example/CMakeLists.txt | 13 + example/src/example3.cpp | 30 ++ include/bitlib/bit-algorithms/addition.hpp | 60 +++ .../bitlib/bit-algorithms/bit_algorithm.hpp | 4 + include/bitlib/bit-algorithms/division.hpp | 61 +++ .../bitlib/bit-algorithms/multiplication.hpp | 58 +++ .../bitlib/bit-algorithms/to_from_string.hpp | 385 +++++++++++++++--- include/bitlib/bit-algorithms/to_string.hpp | 74 ---- include/bitlib/bit-algorithms/transform.hpp | 53 ++- .../bit-algorithms/transform_accumulate.hpp | 154 +++++++ include/bitlib/bit-containers/bit_array.hpp | 4 + .../bitlib/bit-containers/bit_array_base.hpp | 115 ++++-- .../bit_array_dynamic_extent.hpp | 4 + include/bitlib/bit-containers/bit_literal.hpp | 23 +- include/bitlib/bit-containers/bit_policy.hpp | 108 ++++- include/bitlib/bit-containers/bit_vector.hpp | 8 +- include/bitlib/bit-iterator/bit.hpp | 2 +- include/bitlib/bit-iterator/bit_details.hpp | 245 +++++++++-- .../bit-iterator/bit_word_pointer_adapter.hpp | 2 + test/CMakeLists.txt | 5 +- test/src/test-addition.cpp | 16 + test/src/test-copy.cpp | 8 + test/src/test-division.cpp | 22 + test/src/test-multiplication.cpp | 22 + test/src/test-to_from_string.cpp | 53 +++ test/src/test-to_string.cpp | 28 -- 31 files changed, 1403 insertions(+), 262 deletions(-) create mode 100644 example/src/example3.cpp create mode 100644 include/bitlib/bit-algorithms/addition.hpp create mode 100644 include/bitlib/bit-algorithms/division.hpp create mode 100644 include/bitlib/bit-algorithms/multiplication.hpp delete mode 100644 include/bitlib/bit-algorithms/to_string.hpp create mode 100644 include/bitlib/bit-algorithms/transform_accumulate.hpp create mode 100644 test/src/test-addition.cpp create mode 100644 test/src/test-division.cpp create mode 100644 test/src/test-multiplication.cpp create mode 100644 test/src/test-to_from_string.cpp delete mode 100644 test/src/test-to_string.cpp diff --git a/.github/toolchains/clangcl_linux_to_windows.cmake b/.github/toolchains/clangcl_linux_to_windows.cmake index 4d8deb77..f01d0e0a 100644 --- a/.github/toolchains/clangcl_linux_to_windows.cmake +++ b/.github/toolchains/clangcl_linux_to_windows.cmake @@ -33,10 +33,29 @@ set(CMAKE_STATIC_LINKER_FLAGS_INIT "${CMAKE_STATIC_LINKER_FLAGS_INIT} /libpath:$ set(CMAKE_STATIC_LINKER_FLAGS_INIT "${CMAKE_STATIC_LINKER_FLAGS_INIT} /libpath:$ENV{WINSDKDIR}/lib/ucrt/x86_64") set(CMAKE_STATIC_LINKER_FLAGS_INIT "${CMAKE_STATIC_LINKER_FLAGS_INIT} /libpath:$ENV{VCTOOLSDIR}/lib/x86_64") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS_INIT}" CACHE STRING "Flags used by the linker for all executables." FORCE) -set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS_INIT}" CACHE STRING "Flags used by the linker for all shared libraries." FORCE) -set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS_INIT}" CACHE STRING "Flags used by the linker for all modules." FORCE) -set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS_INIT}" CACHE STRING "Flags used by the linker for all static libraries." FORCE) +# Check if the CLANGRTLIB environment variable is set and not empty +if(DEFINED ENV{CLANGRTLIB} AND NOT "$ENV{CLANGRTLIB}" STREQUAL "") + set(CLANGRTLIB_DIR "$ENV{CLANGRTLIB}") + + # Check if it is a valid directory + if(IS_DIRECTORY "${CLANGRTLIB_DIR}") + set(CLANGRTLIB_FILE "${CLANGRTLIB_DIR}/clang_rt.builtins-x86_64.lib") + + # Check if the target lib file exists + if(EXISTS "${CLANGRTLIB_FILE}") + message(STATUS "Found clang_rt.builtins-x86_64.lib in ${CLANGRTLIB_DIR}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /libpath:${CLANGRTLIB_DIR} ${CLANGRTLIB_FILE}") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /libpath:${CLANGRTLIB_DIR} ${CLANGRTLIB_FILE}") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /libpath:${CLANGRTLIB_DIR} ${CLANGRTLIB_FILE}") + set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} /libpath:${CLANGRTLIB_DIR} ${CLANGRTLIB_FILE}") + endif() + endif() +endif() + +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS_INIT} ${CMAKE_EXE_LINKER_FLAGS}" CACHE STRING "Flags used by the linker for all executables." FORCE) +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS_INIT} ${CMAKE_SHARED_LINKER_FLAGS}" CACHE STRING "Flags used by the linker for all shared libraries." FORCE) +set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS_INIT} ${CMAKE_MODULE_LINKER_FLAGS}" CACHE STRING "Flags used by the linker for all modules." FORCE) +set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS_INIT} ${CMAKE_STATIC_LINKER_FLAGS}" CACHE STRING "Flags used by the linker for all static libraries." FORCE) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) diff --git a/.github/workflows/cmake_build_and_test.yml b/.github/workflows/cmake_build_and_test.yml index 3e60346e..b56d2469 100644 --- a/.github/workflows/cmake_build_and_test.yml +++ b/.github/workflows/cmake_build_and_test.yml @@ -56,6 +56,7 @@ jobs: echo "VCTOOLSDIR=${{ github.workspace }}/.xwin_sdk_crt/crt" >> "$GITHUB_ENV" - name: "Install Clang 19" + id: install_clang if: inputs.build_os == 'linux' && (inputs.compiler == 'clang' || inputs.compiler == 'clangcl') shell: bash run: | @@ -76,6 +77,55 @@ jobs: sudo ln -sf /usr/bin/lld-link-19 /usr/local/bin/lld-link sudo ln -sf /usr/bin/llvm-mt-19 /usr/local/bin/llvm-mt sudo ln -sf /usr/bin/llvm-rc-19 /usr/local/bin/llvm-rc + LLVM_VERSION=$(clang-cl --version | head -n1 | grep -oP '\d+\.\d+\.\d+') + echo "LLVM_VERSION=${LLVM_VERSION}" >> "$GITHUB_OUTPUT" + + - name: Restore clang-cl rt libs from cache + id: cache_clang_cl_rt_libs + if: inputs.build_os != 'windows' && inputs.compiler == 'clangcl' + uses: actions/cache@v4 + with: + path: '${{ github.workspace }}/.windows-rt-libs' + key: clangcl_windows_rt_libs_${{ steps.install_clang.outputs.LLVM_VERSION }} + + - name: "Get clang-cl rt libs" + if: inputs.build_os != 'windows' && inputs.compiler == 'clangcl' && steps.cache_clang_cl_rt_libs.outputs.cache-hit != 'true' + env: + LLVM_VERSION: ${{ steps.install_clang.outputs.LLVM_VERSION }} + OUTPUT_DIR: '${{ github.workspace }}/.windows-rt-libs' + shell: bash + run: | + # Download and extract the clang-cl runtime libraries + LLVM_TAG="llvmorg-${LLVM_VERSION}" + TAR_NAME="clang+llvm-${LLVM_VERSION}-x86_64-pc-windows-msvc.tar.xz" + LLVM_URL="https://github.com/llvm/llvm-project/releases/download/${LLVM_TAG}/${TAR_NAME}" + + # === Download === + echo "[*] Downloading LLVM Windows tarball: $LLVM_URL" + curl -L -o "${TAR_NAME}" "${LLVM_URL}" + + # === Prepare output dir === + rm -rf "${OUTPUT_DIR}" + mkdir -p "${OUTPUT_DIR}" + TAR_DIR=$(pwd) + pushd "${OUTPUT_DIR}" + + # === Extract only clang_rt.*.lib files === + echo "[*] Extracting clang_rt.*.lib files to '${OUTPUT_DIR}'..." + tar -xf "${TAR_DIR}/${TAR_NAME}" \ + --strip-components=6 \ + --wildcards '*/lib/clang/*/lib/windows/clang_rt.*.lib' + + popd + + ls -al "${OUTPUT_DIR}" + ls -al . + + # === Cleanup === + echo "[*] Cleaning up..." + rm -f "${TAR_NAME}" + + echo "[āœ“] Done. Runtime libraries copied to: ${OUTPUT_DIR}" - name: Set reusable strings id: strings @@ -111,6 +161,7 @@ jobs: shell: bash env: CMAKE_TOOLCHAIN_FILE: ${{ steps.strings.outputs.toolchain_file }} + CLANGRTLIB: '${{ github.workspace }}/.windows-rt-libs' run: | # Configure cmake \ diff --git a/CMakePresets.json b/CMakePresets.json index 70b2a690..ec4a866e 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -84,7 +84,7 @@ "name": "gnu_base", "hidden": true, "environment": { - "GNU_FLAGS_DEBUG": "-DPOPCNT_NO_UNALIGNED -g3 -fno-omit-frame-pointer -fstack-protector-all -fsanitize=address -fsanitize=undefined -finstrument-functions -fno-inline", + "GNU_FLAGS_DEBUG": "-DPOPCNT_NO_UNALIGNED -DBITLIB_DETECT_UNDEFINED_SHIFT -g3 -fno-omit-frame-pointer -fstack-protector-all -fsanitize=address -fsanitize=undefined -finstrument-functions -fno-inline", "GNU_FLAGS_RELEASE": "-O3 -DNDEBUG -march=native" }, "cacheVariables": { diff --git a/LICENSE b/LICENSE index e0e49caf..8952a658 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ BitLib BSD 3-Clause License Copyright (c) 2022, Bryce Kille +Copyright (c) 2025, Peter McLean All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index 7e53d02c..5a69101a 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,33 @@ auto exponent = fp_half_num(10, 15); auto mantissa = fp_half_num(0, 10); auto twos_complement_exponent = static_cast(exponent) - ((1 << 5)-1); +struct fp_dynamic : bit::bit_array<> { + const size_t mantissa_bits; + fp_dynamic() = delete; + fp_dynamic( + size_t exponent_bits, + size_t mantissa_bits) : + bit::bit_array<>(1+exponent_bits+mantissa_bits), + mantissa_bits(mantissa_bits) {} + bit::bit_reference<> sign() { + return (*this).back(); + } + auto exponent() { + return (*this)(mantissa_bits, this->size()-1); + } + auto mantissa() { + return (*this)(0, mantissa_bits); + } +}; + +int main(int argn, char* argv[]) { + fp_dynamic fp(6u, 7u); + fp.sign() = bit::bit1; + fp.exponent() = 6'5_b; + fp.mantissa() = 0x7'3F_b; + std::cout << "fp: " << fp << "\n"; +} + ``` # Table of Contents diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 11206377..570cee0b 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,6 +1,19 @@ +project(BitLib_Examples) +cmake_minimum_required(VERSION 3.20) + +if(NOT TARGET bitlib::bitlib) + add_subdirectory(../ bitlib_dir) +endif() + add_executable(BitLib_Example1 EXCLUDE_FROM_ALL) target_sources(BitLib_Example1 PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/example1.cpp" ) target_link_libraries(BitLib_Example1 PRIVATE bitlib::bitlib) + +add_executable(BitLib_Example3 EXCLUDE_FROM_ALL) +target_sources(BitLib_Example3 PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/src/example3.cpp" +) +target_link_libraries(BitLib_Example3 PRIVATE bitlib::bitlib) \ No newline at end of file diff --git a/example/src/example3.cpp b/example/src/example3.cpp new file mode 100644 index 00000000..a2cf2ee3 --- /dev/null +++ b/example/src/example3.cpp @@ -0,0 +1,30 @@ +#include "bitlib/bitlib.hpp" + +struct fp_dynamic : bit::bit_array<> { + const size_t mantissa_bits; + fp_dynamic() = delete; + fp_dynamic( + size_t exponent_bits, + size_t mantissa_bits) : + bit::bit_array<>(1+exponent_bits+mantissa_bits), + mantissa_bits(mantissa_bits) {} + bit::bit_reference<> sign() { + return (*this).back(); + } + auto exponent() { + return (*this)(mantissa_bits, this->size()-1); + } + auto mantissa() { + return (*this)(0, mantissa_bits); + } +}; + +#include + +int main(int argn, char* argv[]) { + fp_dynamic fp(6u, 7u); + fp.sign() = bit::bit1; + fp.exponent() = 6'5_b; + fp.mantissa() = 0x7'3F_b; + std::cout << "fp: " << fp << "\n"; +} diff --git a/include/bitlib/bit-algorithms/addition.hpp b/include/bitlib/bit-algorithms/addition.hpp new file mode 100644 index 00000000..3a4f2766 --- /dev/null +++ b/include/bitlib/bit-algorithms/addition.hpp @@ -0,0 +1,60 @@ +// ================================== MOVE ================================== // +// Project: The Experimental Bit Algorithms Library +// Name: addition.hpp +// Description: bit range multiplication +// Contributor(s): +// License: BSD 3-Clause License +// ========================================================================== // +#ifndef _ADDITION_HPP_INCLUDED +#define _ADDITION_HPP_INCLUDED +// ========================================================================== // + +// ============================== PREAMBLE ================================== // +// C++ standard library +#include +#include +// Project sources +#include "bitlib/bit-algorithms/transform.hpp" +#include "bitlib/bit-containers/bit_bitsof.hpp" +#include "bitlib/bit-iterator/bit_details.hpp" +#include "bitlib/bit-iterator/bit_iterator.hpp" + +namespace bit { + +template + requires( + (is_static_castable_v::word_type>) && + (bitsof() <= bitsof::word_type>())) +constexpr unsigned char addition( + const bit_iterator& first, + const bit_iterator& last, + const bit_iterator& d_first, + const U& integral_operand) { + using word_type = typename bit_iterator::word_type; + unsigned char carry = 0; + transform(first, last, d_first, + [&carry, integral_operand](auto word, auto bits = bitsof()) -> word_type { + word_type result_word; + carry = add_carry(carry, static_cast(integral_operand), word, &result_word); + if (bits < bitsof()) { + carry = carry | (0 != lsr(result_word, bits)); + } + return result_word; + }); + return carry; +} + +template + requires( + (is_static_castable_v::word_type>) && + (bitsof() <= bitsof::word_type>())) +constexpr unsigned char addition( + const bit_iterator& first, + const bit_iterator& last, + const U& integral_operand) { + return addition(first, last, first, integral_operand); +} + +} // namespace bit + +#endif // _ADDITION_HPP_INCLUDED diff --git a/include/bitlib/bit-algorithms/bit_algorithm.hpp b/include/bitlib/bit-algorithms/bit_algorithm.hpp index af07236a..3ba67fc6 100644 --- a/include/bitlib/bit-algorithms/bit_algorithm.hpp +++ b/include/bitlib/bit-algorithms/bit_algorithm.hpp @@ -20,21 +20,25 @@ #include "debug_utils.hpp" //TODO does this belong somewhere else? #include "bit_algorithm_details.hpp" // overloads +#include "addition.hpp" #include "bit_algorithm_details.hpp" #include "copy.hpp" #include "copy_backward.hpp" #include "count.hpp" #include "debug_utils.hpp" +#include "division.hpp" #include "equal.hpp" #include "fill.hpp" #include "find.hpp" #include "move.hpp" +#include "multiplication.hpp" #include "reverse.hpp" #include "rotate.hpp" #include "shift.hpp" #include "swap_ranges.hpp" #include "to_from_string.hpp" #include "transform.hpp" +#include "transform_accumulate.hpp" #include "type_traits.hpp" // ========================================================================== // diff --git a/include/bitlib/bit-algorithms/division.hpp b/include/bitlib/bit-algorithms/division.hpp new file mode 100644 index 00000000..98ff58c3 --- /dev/null +++ b/include/bitlib/bit-algorithms/division.hpp @@ -0,0 +1,61 @@ +// ================================== MOVE ================================== // +// Project: The Experimental Bit Algorithms Library +// Name: division.hpp +// Description: bit range division +// Contributor(s): +// License: BSD 3-Clause License +// ========================================================================== // +#ifndef _DIVISION_HPP_INCLUDED +#define _DIVISION_HPP_INCLUDED +// ========================================================================== // + +// ============================== PREAMBLE ================================== // +// C++ standard library +#include +#include +// Project sources +#include "bitlib/bit-algorithms/transform.hpp" +#include "bitlib/bit-containers/bit_bitsof.hpp" +#include "bitlib/bit-iterator/bit_iterator.hpp" + +namespace bit { + +// 'Schoolbook' division by scalar +template + requires( + (std::is_same_v>, + std::remove_cvref_t>>) && + (is_static_castable_v::word_type>) && + (bitsof() <= bitsof::word_type>())) +constexpr typename bit_iterator::word_type division( + const bit_iterator& first, + const bit_iterator& last, + const bit_iterator& d_first, + const U& integral_operand) { + using word_type = typename bit_iterator::word_type; + word_type remainder = 0; + auto long_div = [integral_operand]( + const word_type& remainder, + const word_type& word, + size_t bits = bitsof()) { + word_type next_remainder; + word_type result_word = _divx(remainder, word, static_cast(integral_operand), &next_remainder); + return std::make_pair(result_word, next_remainder); + }; + return transform_accumulate_backward(first, last, d_first, d_first + distance(first, last), remainder, long_div, long_div); +} + +template + requires( + (is_static_castable_v::word_type>) && + (bitsof() <= bitsof::word_type>())) +constexpr typename bit_iterator::word_type division( + const bit_iterator& first, + const bit_iterator& last, + const U& integral_operand) { + return division(first, last, first, integral_operand); +} + +} // namespace bit + +#endif // _DIVISION_HPP_INCLUDED diff --git a/include/bitlib/bit-algorithms/multiplication.hpp b/include/bitlib/bit-algorithms/multiplication.hpp new file mode 100644 index 00000000..01b32340 --- /dev/null +++ b/include/bitlib/bit-algorithms/multiplication.hpp @@ -0,0 +1,58 @@ +// ================================== MOVE ================================== // +// Project: The Experimental Bit Algorithms Library +// Name: multiplication.hpp +// Description: bit range multiplication +// Contributor(s): +// License: BSD 3-Clause License +// ========================================================================== // +#ifndef _MULTIPLICATION_HPP_INCLUDED +#define _MULTIPLICATION_HPP_INCLUDED +// ========================================================================== // + +// ============================== PREAMBLE ================================== // +// C++ standard library +#include +#include +// Project sources +#include "bitlib/bit-algorithms/transform.hpp" +#include "bitlib/bit-containers/bit_bitsof.hpp" +#include "bitlib/bit-iterator/bit_iterator.hpp" + +namespace bit { + +template + requires( + (is_static_castable_v::word_type>) && + (bitsof() <= bitsof::word_type>())) +constexpr typename bit_iterator::word_type multiplication( + const bit_iterator& first, + const bit_iterator& last, + const bit_iterator& d_first, + const U& integral_operand) { + using word_type = typename bit_iterator::word_type; + word_type carry = 0; + transform(first, last, d_first, + [&carry, integral_operand](auto word, auto bits = bitsof()) -> word_type { + word_type result_word = (carry + _mulx(static_cast(integral_operand), word, &carry)); + if (bits < bitsof()) { + carry = (carry << (bitsof() - bits)) | lsr(result_word, bits); + } + return result_word; + }); + return carry; +} + +template + requires( + (is_static_castable_v::word_type>) && + (bitsof() <= bitsof::word_type>())) +constexpr typename bit_iterator::word_type multiplication( + const bit_iterator& first, + const bit_iterator& last, + const U& integral_operand) { + return multiplication(first, last, first, integral_operand); +} + +} // namespace bit + +#endif // _MULTIPLICATION_HPP_INCLUDED diff --git a/include/bitlib/bit-algorithms/to_from_string.hpp b/include/bitlib/bit-algorithms/to_from_string.hpp index 23e68788..25472819 100644 --- a/include/bitlib/bit-algorithms/to_from_string.hpp +++ b/include/bitlib/bit-algorithms/to_from_string.hpp @@ -10,10 +10,17 @@ #ifndef _BIT_TO_STRING_HPP_INCLUDED #define _BIT_TO_STRING_HPP_INCLUDED +#include #include +#include +#include +#include #include "bitlib/bit-algorithms/accumulate.hpp" #include "bitlib/bit-algorithms/count.hpp" +#include "bitlib/bit-algorithms/division.hpp" +#include "bitlib/bit-algorithms/multiplication.hpp" +#include "bitlib/bit-containers/bit_policy.hpp" #include "bitlib/bit_concepts.hpp" namespace bit { @@ -25,19 +32,50 @@ constexpr auto make_digit_map() { static_assert((Base >= 2) && ((Base & (Base - 1)) == 0), "Base must be power of 2 >= 2"); static_assert(Base <= 64, "Base too large for simple char mapping"); - std::array map = {}; + ::std::array map{}; for (std::size_t i = 0; i < Base; ++i) { map[i] = (i < 10) ? ('0' + i) : ('A' + (i - 10)); } return map; } +constexpr std::span make_digit_map(std::size_t Base) { + switch (Base) { + case 2: { + static constexpr auto map = make_digit_map<2>(); + return std::span(static_cast(map.data()), map.size()); + } + case 4: { + static constexpr auto map = make_digit_map<4>(); + return std::span(static_cast(map.data()), map.size()); + } + case 8: { + static constexpr auto map = make_digit_map<8>(); + return std::span(static_cast(map.data()), map.size()); + } + case 16: { + static constexpr auto map = make_digit_map<16>(); + return std::span(static_cast(map.data()), map.size()); + } + case 32: { + static constexpr auto map = make_digit_map<32>(); + return std::span(static_cast(map.data()), map.size()); + } + case 64: { + static constexpr auto map = make_digit_map<64>(); + return std::span(static_cast(map.data()), map.size()); + } + default: + return {}; // or throw, or abort + } +} + template constexpr auto make_from_digit_map() { static_assert((Base >= 2) && ((Base & (Base - 1)) == 0), "Base must be power of 2 >= 2"); static_assert(Base <= 64, "Base too large for simple char mapping"); - std::array map = {}; + ::std::array map{}; for (std::size_t i = 0; i < 128; ++i) { map[i] = ~0; if (i >= '0' && i <= '9') { @@ -53,11 +91,43 @@ constexpr auto make_from_digit_map() { return map; } +constexpr auto make_from_digit_map(std::size_t Base) { + switch (Base) { + case 2: { + static constexpr auto map2 = make_from_digit_map<2>(); + return map2; + } + case 4: { + static constexpr auto map4 = make_from_digit_map<4>(); + return map4; + } + case 8: { + static constexpr auto map8 = make_from_digit_map<8>(); + return map8; + } + case 16: { + static constexpr auto map16 = make_from_digit_map<16>(); + return map16; + } + case 32: { + static constexpr auto map32 = make_from_digit_map<32>(); + return map32; + } + case 64: { + static constexpr auto map64 = make_from_digit_map<64>(); + return map64; + } + default: + throw std::runtime_error("Base not implemented"); + } +} + struct metadata_t { size_t base; bool is_signed; std::endian endian; bool str_sign_extend_zeros; + char fill; }; constexpr metadata_t typical(size_t base = 10, bool str_sign_extend_zeros = false) { @@ -65,76 +135,249 @@ constexpr metadata_t typical(size_t base = 10, bool str_sign_extend_zeros = fals .base = base, .is_signed = false, .endian = std::endian::big, - .str_sign_extend_zeros = str_sign_extend_zeros}; + .str_sign_extend_zeros = str_sign_extend_zeros, + .fill = '\0'}; } } // namespace string -template -constexpr std::string to_string(const bit_iterator& first, const bit_iterator& last, std::string prefix = "") { - static_assert(meta.endian == std::endian::big, "Only bit big endian support (MSB on the left)"); - if constexpr (std::has_single_bit(meta.base)) { - constexpr const auto base_bits = std::bit_width(meta.base - 1); - - int skip_leading_bits = meta.str_sign_extend_zeros ? 0 : count_msb(first, last, bit0); - - int str_len = (distance(first, last) - skip_leading_bits); - str_len = (str_len / base_bits) + ((0 != (str_len % base_bits)) ? 1 : 0); - if (0 == str_len) { - return prefix + "0"; - } - std::string& str = prefix; - str.resize(str.length() + str_len); - - static constexpr auto base_digits = string::make_digit_map(); +template +constexpr CharIt to_string( + const bit_iterator& bit_first, + const bit_iterator& bit_last, + const CharIt str_first, + const CharIt str_last, + string::metadata_t meta = string::typical()) { + if (std::has_single_bit(meta.base)) { + const auto base_bits = std::bit_width(meta.base - 1); + const auto base_digits = string::make_digit_map(meta.base); - return accumulate( + CharIt cursor = accumulate_while( policy::AccumulateNoInitialSubword{}, - first, last - skip_leading_bits, (str.data() + str_len), - [](char* acc, auto word, const size_t bits = bitsof()) { + bit_first, bit_last, str_last, + [meta, base_bits, base_digits, str_first](CharIt cursor, auto word, const size_t bits = bitsof()) { const int characters = ((bits + base_bits - 1) / base_bits); - acc -= characters; for (int i = characters - 1; i >= 0; i--) { - acc[i] = base_digits[word & (meta.base - 1)]; + if (cursor == str_first) { + return std::make_pair(false, cursor); + } + *(--cursor) = base_digits[word & (meta.base - 1)]; word >>= base_bits; } - return acc; + return std::make_pair(cursor != str_first, cursor); }); + if (cursor != str_first) { + return std::copy(cursor, str_last, str_first); + } else { + return str_last; + } + } else { + using word_type = typename bit_iterator::word_type; + size_t store_bits = distance(bit_first, bit_last); + std::vector vec((store_bits + bitsof() - 1) / bitsof()); + vec.back() = 0; // Ensure last word is zeroed + bit_iterator bit_it(vec.data()); + + const unsigned char base = static_cast(meta.base); + auto remainder = ::bit::division(bit_first, bit_last, bit_it, base); + CharIt cursor = str_last; + *(--cursor) = static_cast(remainder + '0'); + + while ((cursor != str_first) && (store_bits -= ::bit::count_msb(bit_it, bit_it + store_bits, bit0))) { + remainder = ::bit::division(bit_it, bit_it + store_bits, bit_it, base); + *(--cursor) = static_cast(remainder + '0'); + } + if (cursor != str_first) { + return std::copy(cursor, str_last, str_first); + } + return str_last; + } +} + +template +constexpr size_t estimate_length( + const bit_iterator& first, + const bit_iterator& last, + const size_t base, + const bool str_sign_extend_zeros) { + if (std::has_single_bit(base)) { + const auto base_bits = std::bit_width(base - 1); + + int skip_leading_bits = str_sign_extend_zeros ? 0 : count_msb(first, last, bit0); + + int str_len = (distance(first, last) - skip_leading_bits); + str_len = (str_len + base_bits - 1) / base_bits; // Round up to nearest base digit + return static_cast(std::max(1, str_len)); } else { - return "not_implented_yet"; + const uint32_t LOG2BASE = std::ceil(1 / std::logbf(base) * (1 << 16)); + int skip_leading_bits = str_sign_extend_zeros ? 0 : count_msb(first, last, bit0); + const auto bits = distance(first, last) - skip_leading_bits; + const auto fixed_point = (bits * LOG2BASE); + const auto max_len = (fixed_point >> 16) + ((fixed_point & ((1 << 16) - 1)) != 0); + return static_cast(std::max(max_len, static_cast(1))); } } +template +constexpr std::string to_string( + const bit_iterator& first, + const bit_iterator& last, + string::metadata_t meta = string::typical()) { + std::string buffer(estimate_length(first, last, meta.base, meta.str_sign_extend_zeros), meta.fill); + if (meta.fill) { + std::fill(to_string(first, last, buffer.begin(), buffer.end(), meta), buffer.end(), meta.fill); + } else { + buffer.resize(to_string(first, last, buffer.begin(), buffer.end(), meta) - buffer.begin()); + } + return buffer; +} + +template +constexpr std::string to_string( + const bit_iterator& first, + const bit_iterator& last) { + static_assert(meta.endian == std::endian::big, "Only bit big endian support (MSB on the left)"); + return to_string(first, last, meta); +} + +constexpr std::string to_string(const bit_sized_range auto& bits, string::metadata_t meta = string::typical()) { + return to_string(bits.begin(), bits.end(), meta); +} + template -constexpr std::string to_string(const bit_sized_range auto& bits, std::string prefix = "") { - return to_string(bits.begin(), bits.end(), prefix); +constexpr std::string to_string(const bit_sized_range auto& bits) { + return to_string(bits, meta); } -/* -Commenting this out temporarily as the reference to bit_vector/bit_array messes up include dependency DAG +template +constexpr CharIt to_string( + const bit_iterator& first, + const bit_iterator& last, + const CharIt str_first, + const CharIt str_last) { + static_assert(meta.endian == std::endian::big, "Only bit big endian support (MSB on the left)"); + return to_string(first, last, str_first, str_last, meta); +} -template , typename RandomAccessIt> +template +constexpr CharIt to_string( + const bit_sized_range auto& bits, + const CharIt str_first, + const CharIt str_last, + string::metadata_t meta = string::typical()) { + return to_string(str_first, str_last, bits.begin(), bits.end(), meta); +} + +template +constexpr CharIt to_string( + const bit_sized_range auto& bits, + const CharIt str_first, + const CharIt str_last) { + return to_string(bits, str_first, str_last, meta); +} + +template > constexpr void from_string( - Policy, - const char* str_first, const char* str_last, - bit_iterator bit_first, bit_iterator bit_last) { - const auto str_len = str_last - str_first; + const CharIt str_first, const CharIt str_last, + const bit_iterator& bit_first, const bit_iterator& bit_last, + string::metadata_t meta = string::typical()) { + // TODO: This should be a policy + if (str_first == str_last) { + return; // Nothing to do + } + if (std::has_single_bit(meta.base)) { + const auto base_bits = std::bit_width(meta.base - 1); + const auto base_from_digits = string::make_from_digit_map(meta.base); + using word_type = uint64_t; + std::vector vec; + size_t store_bits = distance(bit_first, bit_last); + + bit_iterator bit_it = bit_first; + auto cursor = std::distance(str_first, str_last) - 1; + while ((cursor >= 0) && store_bits) { + word_type work = 0; + size_t bits = 0; + for (; (bits < bitsof()) && (cursor >= 0); cursor--) { + char c = str_first[cursor]; + // TODO: This should be a policy + if (c >= base_from_digits.size()) { + continue; + } + auto digit = base_from_digits[c]; + // TODO: This should be a policy + if (~0 == digit) { + continue; + } + work |= (digit << bits); + bits += base_bits; + } + if (store_bits < bits) { + Policy::truncation::template from_integral( + bit_it, bit_last, work); + return; + } else if ((store_bits > bits) && (cursor < 0)) { + const bit_iterator p_integral(&work); + bit_it = ::bit::copy(p_integral, p_integral + bits, bit_it); + Policy::extension::template from_integral( + bit_it, bit_last, work); + } else if (store_bits >= bits) { + const bit_iterator p_integral(&work); + bit_it = ::bit::copy(p_integral, p_integral + bits, bit_it); + } + } + } else { + if (meta.base != 10) { + throw std::runtime_error("Base not implemented"); + } + using word_type = typename bit_iterator::word_type; + std::vector vec; + size_t store_bits = distance(bit_first, bit_last); + + // TODO: template with uninitialized_t + ::bit::fill(bit_first, bit_last, bit0); // Clear the bits first + + CharIt cursor = str_first; + while (cursor != str_last) { + unsigned char c = (*cursor - '0'); + if (c <= 9) { + auto overflow_mult = ::bit::multiplication(bit_first, bit_last, word_type{10}); + auto overflow_add = ::bit::addition(bit_first, bit_last, c); + if (overflow_mult || overflow_add) { + //Policy::truncation::template overflow(bit_first, bit_last); + return; + } + } + cursor++; + } + //Policy::extension::template extend(bit_first, bit_last); + } } -template -constexpr bit_vector<> from_string(const char* first, const char* last) { +template > +constexpr void from_string( + const CharIt str_first, const CharIt str_last, + const bit_iterator& bit_first, const bit_iterator& bit_last) { static_assert(meta.endian == std::endian::big, "Only bit big endian support (MSB on the left)"); - if constexpr (std::has_single_bit(meta.base)) { - constexpr const auto base_bits = std::bit_width(meta.base - 1); - static constexpr auto base_from_digits = string::make_from_digit_map(); + from_string(str_first, str_last, bit_first, bit_last, meta); +} + +template +constexpr std::vector from_string( + const CharIt first, const CharIt last, string::metadata_t meta = string::typical()) { + if (std::has_single_bit(meta.base)) { + const auto base_bits = std::bit_width(meta.base - 1); + const auto base_from_digits = string::make_from_digit_map(meta.base); - bit_vector<> vec; + std::vector vec; last--; while (last >= first) { - uint64_t work = 0; + uintptr_t work = 0; size_t bits = 0; - for (; (bits < bitsof()) && (last >= first); last--) { + for (; (bits < bitsof()) && (last >= first); last--) { char c = *last; // TODO: This should be a policy if (c >= base_from_digits.size()) { @@ -149,20 +392,64 @@ constexpr bit_vector<> from_string(const char* first, const char* last) { bits += base_bits; } if (bits) { - vec.append_range(bit_array<>(bits, work)); + vec.push_back(work); } } return vec; } else { //from_string base 10 not implemented yet; + return {}; } } -template -constexpr bit_vector<> from_string(const std::string& str) { - return from_string(str.c_str(), str.c_str() + str.length()); +template +constexpr std::vector from_string( + const CharIt first, const CharIt last) { + static_assert(meta.endian == std::endian::big, "Only bit big endian support (MSB on the left)"); + return from_string(first, last, meta); +} + +template > +constexpr void from_string( + const std::string& str, + const bit_iterator& bit_first, const bit_iterator& bit_last) { + static_assert(meta.endian == std::endian::big, "Only bit big endian support (MSB on the left)"); + from_string(str.c_str(), str.c_str() + str.length(), bit_first, bit_last); +} + +template ::value_type>> +constexpr void from_string( + const std::string& str, + RangeT& bits) { + using range_iterator_t = std::ranges::iterator_t; + using RandomAccessIt = typename range_iterator_t::iterator_type; + from_string(str.begin(), str.end(), bits.begin(), bits.end()); +} + +template > +constexpr void from_string( + const std::string& str, + const bit_iterator& bit_first, const bit_iterator& bit_last, + string::metadata_t meta = string::typical()) { + from_string( + str.begin(), str.end(), + bit_first, bit_last, + meta); +} + +template ::value_type>> +constexpr void from_string( + const std::string& str, + RangeT& bits, + string::metadata_t meta = string::typical()) { + using range_iterator_t = std::ranges::iterator_t; + using RandomAccessIt = typename range_iterator_t::iterator_type; + from_string( + str.begin(), str.end(), + bits.begin(), bits.end(), + meta); } -*/ } // namespace bit diff --git a/include/bitlib/bit-algorithms/to_string.hpp b/include/bitlib/bit-algorithms/to_string.hpp deleted file mode 100644 index be9050d3..00000000 --- a/include/bitlib/bit-algorithms/to_string.hpp +++ /dev/null @@ -1,74 +0,0 @@ -// ================================= array_REF =================================== // -// Project: The Experimental Bit Algorithms Library -// \file to_string.hpp -// Description: Implementation of array_ref -// Creator: Vincent Reverdy -// Contributor: Peter McLean [2025] -// License: BSD 3-Clause License -// ========================================================================== // - -#ifndef _BIT_TO_STRING_HPP_INCLUDED -#define _BIT_TO_STRING_HPP_INCLUDED - -#include -#include - -#include "bitlib/bit-algorithms/accumulate.hpp" -#include "bitlib/bit-algorithms/count.hpp" -#include "bitlib/bit_concepts.hpp" - -namespace bit { - -namespace detail { - -template -constexpr auto make_digit_map() { - static_assert((Base >= 2) && ((Base & (Base - 1)) == 0), "Base must be power of 2 >= 2"); - static_assert(Base <= 64, "Base too large for simple char mapping"); - - std::array map = {}; - for (std::size_t i = 0; i < Base; ++i) { - map[i] = (i < 10) ? ('0' + i) : ('A' + (i - 10)); - } - return map; -} - -} // namespace detail - -template -constexpr std::string to_string(const bit_iterator& first, const bit_iterator& last, std::string prefix = "") { - if constexpr (std::has_single_bit(base)) { - constexpr const auto base_bits = std::bit_width(base - 1); - - int skip_leading_bits = prepend_zeros ? 0 : count_msb(first, last, bit0); - - int str_len = (distance(first, last) - skip_leading_bits); - str_len += (0 != (str_len % base_bits)); - std::string& str = prefix; - str.resize(str.length() + str_len); - - static constexpr auto base_digits = detail::make_digit_map(); - - return accumulate( - policy::AccumulateNoInitialSubword{}, - first, last - skip_leading_bits, (str.data() + str_len), - [](char* acc, auto word, const size_t bits = bitsof()) { - const int characters = ((bits + base_bits - 1) / base_bits); - acc -= characters; - for (int i = characters - 1; i >= 0; i--) { - acc[i] = base_digits[word & (base - 1)]; - word >>= base_bits; - } - return acc; - }); - } -} - -template -constexpr std::string to_string(const bit_sized_range auto& bits, std::string prefix = "") { - return to_string(bits.begin(), bits.end(), prefix); -} - -} // namespace bit - -#endif // _BIT_TO_STRING_HPP_INCLUDED \ No newline at end of file diff --git a/include/bitlib/bit-algorithms/transform.hpp b/include/bitlib/bit-algorithms/transform.hpp index e6f93c61..e343ffc3 100644 --- a/include/bitlib/bit-algorithms/transform.hpp +++ b/include/bitlib/bit-algorithms/transform.hpp @@ -32,6 +32,9 @@ namespace bit { //return d_first; //} template + requires( + (std::is_same_v>, + std::remove_cvref_t>>)) constexpr bit_iterator transform( bit_iterator first, bit_iterator last, @@ -58,12 +61,22 @@ constexpr bit_iterator transform( size_type partial_bits_to_op = ::std::min( remaining_bits_to_op, digits - d_first.position()); + word_type result; + if constexpr (std::is_invocable_v) { + result = unary_op( + static_cast( + get_masked_word(first, partial_bits_to_op) + << static_cast(d_first.position())), + partial_bits_to_op); + } else { + result = unary_op( + static_cast( + get_masked_word(first, partial_bits_to_op) + << static_cast(d_first.position()))); + } *it = _bitblend( *it, - unary_op( - static_cast( - get_word(first, partial_bits_to_op) - << static_cast(d_first.position()))), + result, static_cast(d_first.position()), static_cast(partial_bits_to_op)); remaining_bits_to_op -= partial_bits_to_op; @@ -101,22 +114,40 @@ constexpr bit_iterator transform( } #endif size_t std_dist = ::std::distance(firstIt, last.base()); - it = std::transform(firstIt, last.base(), it, unary_op); + for (auto in_it = firstIt; in_it != last.base(); std::advance(in_it, 1), std::advance(it, 1)) { + if constexpr (std::is_invocable_v) { + *it = unary_op(*in_it, digits); + } else { + *it = unary_op(*in_it); + } + } + //it = std::transform(firstIt, last.base(), it, unary_op); firstIt += std_dist; first = bit_iterator(firstIt); remaining_bits_to_op -= digits * N; } else { while (remaining_bits_to_op >= digits) { - *it = unary_op(get_word(first, digits)); + if constexpr (std::is_invocable_v) { + *it = unary_op(get_word(first, digits), digits); + } else { + *it = unary_op(get_word(first, digits)); + } + remaining_bits_to_op -= digits; it++; advance(first, digits); } } if (remaining_bits_to_op > 0) { + word_type result; + if constexpr (std::is_invocable_v) { + result = unary_op(get_masked_word(first, remaining_bits_to_op), remaining_bits_to_op); + } else { + result = unary_op(get_masked_word(first, remaining_bits_to_op)); + } *it = _bitblend( *it, - unary_op(get_word(first, remaining_bits_to_op)), + result, _mask(remaining_bits_to_op)); } } @@ -155,10 +186,10 @@ constexpr bit_iterator transform( *it, binary_op( static_cast( - get_word(first1, partial_bits_to_op) + get_masked_word(first1, partial_bits_to_op) << static_cast(d_first.position())), static_cast( - get_word(first2, partial_bits_to_op) + get_masked_word(first2, partial_bits_to_op) << static_cast(d_first.position()))), static_cast(d_first.position()), static_cast(partial_bits_to_op)); @@ -193,8 +224,8 @@ constexpr bit_iterator transform( *it = _bitblend( *it, binary_op( - get_word(first1, remaining_bits_to_op), - get_word(first2, remaining_bits_to_op)), + get_masked_word(first1, remaining_bits_to_op), + get_masked_word(first2, remaining_bits_to_op)), _mask(remaining_bits_to_op)); } } diff --git a/include/bitlib/bit-algorithms/transform_accumulate.hpp b/include/bitlib/bit-algorithms/transform_accumulate.hpp new file mode 100644 index 00000000..80b7ef34 --- /dev/null +++ b/include/bitlib/bit-algorithms/transform_accumulate.hpp @@ -0,0 +1,154 @@ +// ================================= array_REF =================================== // +// Project: The Experimental Bit Algorithms Library +// \file accumulate.hpp +// Description: Implementation of accumulate +// Creator: Vincent Reverdy +// Contributor: Peter McLean [2025] +// License: BSD 3-Clause License +// ========================================================================== // + +#ifndef _BIT_TRANSFORM_ACCUMULATE_HPP_INCLUDED +#define _BIT_TRANSFORM_ACCUMULATE_HPP_INCLUDED + +#include "bitlib/bit-iterator/bit_iterator.hpp" +#include "bitlib/bit-iterator/bit_details.hpp" + +namespace bit { + +template < + bool forward, + bool initial_sub_word, + typename RandomAccessItIn, + typename RandomAccessItOut, + typename T, + typename BinaryOperation, + typename BinaryOperationSubword> + requires( + (std::is_same_v>, + std::remove_cvref_t>>)) +constexpr auto transform_accumulate( + bit_iterator first, + bit_iterator last, + bit_iterator d_first, + bit_iterator d_last, + T acc, + BinaryOperation binary_op, + BinaryOperationSubword binary_op_subword) { + using word_type = typename bit_iterator::word_type; + using size_type = typename bit_iterator::size_type; + constexpr size_type digits = bitsof(); + + size_type total_bits_to_op = distance(first, last); + + RandomAccessItOut d_it; + if constexpr (forward) { + d_it = d_first.base(); + } else { + d_it = d_last.base(); + } + + if constexpr (forward) { + if (d_first.position() != 0) { + size_type partial_bits_to_op = ::std::min( + total_bits_to_op, + digits - d_first.position()); + if (partial_bits_to_op != 0) { + word_type word; + std::tie(word, acc) = binary_op_subword(std::move(acc), get_masked_word(first, partial_bits_to_op), partial_bits_to_op); + *d_it = _bitblend( + *d_it, + word, + static_cast(d_first.position()), + static_cast(partial_bits_to_op)); + total_bits_to_op -= partial_bits_to_op; + advance(first, partial_bits_to_op); + advance(d_it, 1); + } + } + } else { + if (d_last.position() != 0) { + size_type partial_bits_to_op = ::std::min( + total_bits_to_op, + d_last.position()); + if (partial_bits_to_op != 0) { + advance(last, -partial_bits_to_op); + word_type word; + std::tie(word, acc) = binary_op_subword(std::move(acc), get_masked_word(last, partial_bits_to_op), partial_bits_to_op); + *d_it = _bitblend( + *d_it, + word, + static_cast(d_first.position()), + static_cast(partial_bits_to_op)); + total_bits_to_op -= partial_bits_to_op; + } + } + } + + + const size_type whole_words_to_op = total_bits_to_op / digits; + const size_type remaining_bits_to_op = total_bits_to_op % digits; + + for (size_t i = 0; i < whole_words_to_op; i ++) { + if constexpr (forward) { + word_type word; + std::tie(word, acc) = binary_op(std::move(acc), get_word(first)); + *d_it = word; + advance(first, digits); + std::advance(d_it, 1); + } else { + advance(last, -digits); + std::advance(d_it, -1); + word_type word; + std::tie(word, acc) = binary_op(std::move(acc), get_word(last)); + *d_it = word; + } + } + if (remaining_bits_to_op > 0) { + if constexpr (forward) { + word_type word; + std::tie(word, acc) = binary_op_subword(std::move(acc), get_masked_word(first, remaining_bits_to_op), remaining_bits_to_op); + *d_it = _bitblend( + *d_it, + word, + 0, + remaining_bits_to_op); + + } else { + advance(last, -remaining_bits_to_op); + std::advance(d_it, -1); + word_type word; + std::tie(word, acc) = binary_op_subword(std::move(acc), get_masked_word(last, remaining_bits_to_op), remaining_bits_to_op); + *d_it = _bitblend( + *d_it, + word, + 0, + remaining_bits_to_op); + } + } + + return acc; +} + +template < + typename RandomAccessItIn, + typename RandomAccessItOut, + typename T, + typename BinaryOperation, + typename BinaryOperationSubword> + requires( + (std::is_same_v>, + std::remove_cvref_t>>)) +constexpr auto transform_accumulate_backward( + const bit_iterator& first, + const bit_iterator& last, + const bit_iterator& d_first, + const bit_iterator& d_last, + const T& acc, + BinaryOperation binary_op, + BinaryOperationSubword binary_op_subword) { + return transform_accumulate(first, last, d_first, d_last, acc, binary_op, binary_op_subword); +} + +} // namespace bit + +#endif // _BIT_TRANSFORM_ACCUMULATE_HPP_INCLUDED diff --git a/include/bitlib/bit-containers/bit_array.hpp b/include/bitlib/bit-containers/bit_array.hpp index a6e87d67..aeded261 100644 --- a/include/bitlib/bit-containers/bit_array.hpp +++ b/include/bitlib/bit-containers/bit_array.hpp @@ -104,6 +104,10 @@ class array : public array_base, T, N, W, false, Policy, this->fill(bit_val); } + constexpr array(detail::uninitialized_t, const size_t& size) { + assert(size == N); + } + template constexpr array(const U& integral) { this->from_integral(integral); diff --git a/include/bitlib/bit-containers/bit_array_base.hpp b/include/bitlib/bit-containers/bit_array_base.hpp index 0fe78af2..fa82a0e4 100644 --- a/include/bitlib/bit-containers/bit_array_base.hpp +++ b/include/bitlib/bit-containers/bit_array_base.hpp @@ -62,6 +62,7 @@ class array_base : public detail::container_size_storage::size; using word_type = W; using value_type = T; using size_type = std::size_t; @@ -74,9 +75,9 @@ class array_base : public detail::container_size_storage() {} - constexpr array_base(const size_type& size) noexcept + constexpr array_base(const size_type& extent) noexcept requires(N == std::dynamic_extent) - : detail::container_size_storage(size) {} + : detail::container_size_storage(extent) {} // Element access constexpr reference operator[](size_type pos) { @@ -88,7 +89,7 @@ class array_base : public detail::container_size_storage explicit constexpr operator U() const noexcept { - assert(derived().size() <= bitsof()); + assert(size() <= bitsof()); U integral; if constexpr (N == std::dynamic_extent) { - if (derived().size() > bitsof()) { + if (size() > bitsof()) { Policy::truncation::template to_integral(derived(), integral); } else { - ::bit::copy(derived().begin(), end(), bit_pointer(&integral)); + ::bit::copy(derived().begin(), end(), &integral); } - if (derived().size() < bitsof()) { - Policy::extension::template to_integral(derived(), integral, detail::uninitialized); + if (size() < bitsof()) { + Policy::extension::template to_integral(detail::uninitialized, derived(), integral); } } else { if constexpr (N > bitsof()) { Policy::truncation::template to_integral(derived(), integral); } else { - ::bit::copy(derived().begin(), end(), bit_pointer(&integral)); + ::bit::copy(derived().begin(), end(), &integral); } if constexpr (N < bitsof()) { - Policy::extension::template to_integral(derived(), integral, detail::uninitialized); + Policy::extension::template to_integral(detail::uninitialized, derived(), integral); } } @@ -217,73 +218,117 @@ class array_base : public detail::container_size_storage; constexpr compatible_bitarray operator~() { - compatible_bitarray result(derived().size()); + compatible_bitarray result(detail::uninitialized, size()); transform(derived().begin(), derived().end(), result.begin(), [](const word_type& bits) -> word_type { return ~bits; }); return result; } constexpr compatible_bitarray operator|(const bit_sized_range auto& other) const { - assert(other.size() == derived().size()); - compatible_bitarray result(derived().size()); + assert(other.size() == size()); + compatible_bitarray result(detail::uninitialized, size()); transform(derived().begin(), derived().end(), other.begin(), result.begin(), [](const word_type& a, const word_type& b) -> word_type { return a | b; }); return result; } constexpr Derived& operator|=(bit_sized_range auto& other) { - assert(other.size() == derived().size()); + assert(other.size() == size()); transform(derived().begin(), derived().end(), other.begin(), derived().begin(), [](const word_type& a, const word_type& b) -> word_type { return a | b; }); return derived(); } constexpr compatible_bitarray operator&(const bit_sized_range auto& other) const { - assert(other.size() == derived().size()); - compatible_bitarray result(derived().size()); + assert(other.size() == size()); + compatible_bitarray result(detail::uninitialized, size()); transform(derived().begin(), derived().end(), other.begin(), result.begin(), [](const word_type& a, const word_type& b) -> word_type { return a & b; }); return result; } constexpr Derived& operator&=(bit_sized_range auto& other) { - assert(other.size() == derived().size()); + assert(other.size() == size()); transform(derived().begin(), derived().end(), other.begin(), derived().begin(), [](const word_type& a, const word_type& b) -> word_type { return a & b; }); return derived(); } constexpr compatible_bitarray operator^(const bit_sized_range auto& other) const { - assert(other.size() == derived().size()); - compatible_bitarray result(derived().size()); + assert(other.size() == size()); + compatible_bitarray result(detail::uninitialized, size()); transform(derived().begin(), derived().end(), other.begin(), result.begin(), [](const word_type& a, const word_type& b) -> word_type { return a ^ b; }); return result; } constexpr Derived& operator^=(bit_sized_range auto& other) { - assert(other.size() == derived().size()); + assert(other.size() == size()); transform(derived().begin(), derived().end(), other.begin(), derived().begin(), [](const word_type& a, const word_type& b) -> word_type { return a ^ b; }); return derived(); } + friend std::ostream& operator<<(std::ostream& os, const array_base& cv) { + // Save stream formatting settings + std::ios_base::fmtflags flags = os.flags(); + char fill = os.fill(); + std::streamsize width = os.width(); + + std::string content; + switch (flags & std::ios_base::basefield) { + case std::ios_base::hex: + content = ::bit::to_string(cv.derived(), ::bit::string::typical(16)); + break; + case std::ios_base::oct: + content = ::bit::to_string(cv.derived(), ::bit::string::typical(8)); + break; + case std::ios_base::dec: + content = ::bit::to_string(cv.derived(), ::bit::string::typical(10)); + break; + default: + content = ::bit::to_string(cv.derived(), ::bit::string::typical(2)); + break; + } + + // Clear width to avoid affecting next output + os.width(0); + + // Apply padding manually if needed + if (width > static_cast(content.size())) { + std::streamsize pad = width - content.size(); + bool left = (flags & std::ios_base::adjustfield) == std::ios_base::left; + + if (!left) { + os << std::string(pad, fill); + } + + os << content; + + if (left) { + os << std::string(pad, fill); + } + } else { + os << content; + } + + return os; + } + protected: template constexpr void from_integral(const U& integral) { if constexpr (N == std::dynamic_extent) { - if ((derived().size() * bitsof()) < bitsof()) { + if ((size() * bitsof()) < bitsof()) { Policy::truncation::template from_integral(derived(), integral); } else { - bit_pointer integral_ptr(&integral); - ::bit::copy(integral_ptr, integral_ptr + bitsof(), derived().begin()); + ::bit::copy(&integral, &integral + 1, derived().begin()); } - if (bitsof() < (derived().size() * bitsof())) { - Policy::extension::template from_integral(derived(), integral, detail::uninitialized); + if (bitsof() < (size() * bitsof())) { + Policy::extension::template from_integral(detail::uninitialized, derived(), integral); } } else { if constexpr ((N * bitsof()) < bitsof()) { Policy::truncation::template from_integral(derived(), integral); } else { - bit_pointer integral_ptr(&integral); - ::bit::copy(integral_ptr, integral_ptr + bitsof(), derived().begin()); + ::bit::copy(&integral, &integral + 1, derived().begin()); } if constexpr (bitsof() < (N * bitsof())) { - Policy::extension::template from_integral(derived(), integral, detail::uninitialized); + Policy::extension::template from_integral(detail::uninitialized, derived(), integral); } } } diff --git a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp index c0447b06..67b428e9 100644 --- a/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp +++ b/include/bitlib/bit-containers/bit_array_dynamic_extent.hpp @@ -163,6 +163,10 @@ class array : base(extent), storage(Words(extent), allocator) { } + constexpr array(detail::uninitialized_t, const size_type extent, const Allocator& allocator = Allocator()) + : base(extent), storage(Words(extent), allocator, detail::uninitialized) { + } + template constexpr array(const size_type extent, const U& integral, const Allocator& allocator = Allocator()) : base(extent), storage(Words(extent), allocator, detail::uninitialized) { diff --git a/include/bitlib/bit-containers/bit_literal.hpp b/include/bitlib/bit-containers/bit_literal.hpp index cc579939..53b20258 100644 --- a/include/bitlib/bit-containers/bit_literal.hpp +++ b/include/bitlib/bit-containers/bit_literal.hpp @@ -112,8 +112,21 @@ constexpr std::pair _parameter_pack_decode_prefixed_num() { } // namespace bit +#ifdef BITLIB_LITERAL_NAMESPACE +namespace BITLIB_LITERAL_NAMESPACE { +#endif + +#ifndef BITLIB_LITERAL_SUFFIX +#define _BITLIB_LITERAL_SUFFIX b +#else +#define _BITLIB_LITERAL_SUFFIX BITLIB_LITERAL_SUFFIX +#endif + +#define _BITLIB_CONCAT(x, y) x##y +#define _BITLIB_LITERAL_OPERATOR(suffix) _BITLIB_CONCAT(operator""_, suffix) + template -constexpr auto operator""_b() { +constexpr auto _BITLIB_LITERAL_OPERATOR(_BITLIB_LITERAL_SUFFIX)() { constexpr auto pair = bit::_parameter_pack_decode_prefixed_num(); constexpr auto bits = pair.first; constexpr auto num = pair.second; @@ -124,4 +137,12 @@ constexpr auto operator""_b() { return bit::bit_array>(static_cast(num)); } +#undef _BITLIB_LITERAL_SUFFIX +#undef _BITLIB_LITERAL_OPERATOR +#undef _BITLIB_CONCAT + +#ifdef BITLIB_LITERAL_NAMESPACE +} +#endif + #endif // _BIT_LITERAL_HPP_INCLUDED diff --git a/include/bitlib/bit-containers/bit_policy.hpp b/include/bitlib/bit-containers/bit_policy.hpp index 844ecd93..65cbd053 100644 --- a/include/bitlib/bit-containers/bit_policy.hpp +++ b/include/bitlib/bit-containers/bit_policy.hpp @@ -26,11 +26,30 @@ struct typical { }; struct truncate { - template - constexpr static void to_integral(const bit_sized_range auto& value, U& integral) noexcept { + template + constexpr static void to_integral(const bit_iterator& first, U& integral) noexcept { bit_pointer integral_begin(&integral); - ::bit::copy(value.begin(), value.begin() + bitsof(), integral_begin); + ::bit::copy(first, first + bitsof(), integral_begin); + } + + template + constexpr static void to_integral(const bit_range auto& value, U& integral) noexcept { + to_integral(value.begin(), integral); + } + + template + constexpr static void from_integral( + const bit_iterator& first, + const bit_iterator& last, + const U& integral) noexcept { + const bit_pointer integral_begin(&integral); + if constexpr (N == std::dynamic_extent) { + ::bit::copy(integral_begin, integral_begin + distance(first, last), first); + } else { + ::bit::copy(integral_begin, integral_begin + N, first); + } } + template constexpr static void from_integral(bit_sized_range auto& value, const U& integral) noexcept { const bit_pointer integral_begin(&integral); @@ -43,25 +62,67 @@ struct truncate { }; struct sign_extend { + template + constexpr static void to_integral( + const bit_iterator& first, + const bit_iterator& last, + U& integral) noexcept { + bit_pointer integral_begin(&integral); + if constexpr (N == std::dynamic_extent) { + if constexpr (std::is_signed_v) { + if (last[-1]) { + ::bit::fill(integral_begin + distance(first, last), integral_begin + bitsof(), bit1); + } + } + } else { + if constexpr (std::is_signed_v) { + if (first[N - 1]) { + ::bit::fill(integral_begin + N, integral_begin + bitsof(), bit1); + } + } + } + } + template constexpr static void to_integral(const bit_sized_range auto& value, U& integral) noexcept { bit_pointer integral_begin(&integral); if constexpr (N == std::dynamic_extent) { if constexpr (std::is_signed_v) { - if (value.last()[-1]) { + if (value.end()[-1]) { ::bit::fill(integral_begin + value.size(), integral_begin + bitsof(), bit1); } } } else { if constexpr (std::is_signed_v) { if (value.begin()[N - 1]) { - ::bit::fill(integral_begin + value.size(), integral_begin + bitsof(), bit1); + ::bit::fill(integral_begin + N, integral_begin + bitsof(), bit1); } } } } + + template + constexpr static void to_integral( + detail::uninitialized_t, + const bit_iterator& first, + const bit_iterator& last, U& integral) noexcept { + bit_pointer integral_begin(&integral); + if constexpr (N == std::dynamic_extent) { + if constexpr (std::is_signed_v) { + ::bit::fill(integral_begin + distance(first, last), integral_begin + bitsof(), last[-1]); + } + } else { + if constexpr (std::is_signed_v) { + ::bit::fill(integral_begin + N, integral_begin + bitsof(), first[N - 1]); + } + } + } + template - constexpr static void to_integral(const bit_sized_range auto& value, U& integral, detail::uninitialized_t) noexcept { + constexpr static void to_integral( + detail::uninitialized_t, + const bit_sized_range auto& value, + U& integral) noexcept { bit_pointer integral_begin(&integral); if constexpr (N == std::dynamic_extent) { if constexpr (std::is_signed_v) { @@ -77,30 +138,51 @@ struct sign_extend { } } } - template - constexpr static void from_integral(bit_sized_range auto& value, const U& integral) noexcept { + + template + constexpr static void from_integral( + const bit_iterator& first, + const bit_iterator& last, + const U& integral) noexcept { if constexpr (N == std::dynamic_extent) { if constexpr (std::is_signed_v) { if (integral < 0) { - ::bit::fill(value.begin() + bitsof(), value.end(), bit1); + ::bit::fill(first + bitsof(), last, bit1); } } } else { if constexpr (std::is_signed_v) { if (integral < 0) { - ::bit::fill(value.begin() + N, value.end(), bit1); + ::bit::fill(first + bitsof(), last, bit1); } } } } template - constexpr static void from_integral(bit_sized_range auto& value, const U& integral, detail::uninitialized_t) noexcept { + constexpr static void from_integral(bit_sized_range auto& value, const U& integral) noexcept { + from_integral(value.begin(), value.end(), integral); + } + + template + constexpr static void from_integral( + detail::uninitialized_t, + const bit_iterator& first, + const bit_iterator& last, + const U& integral) noexcept { if constexpr (N == std::dynamic_extent) { - ::bit::fill(value.begin() + bitsof(), value.end(), (integral < 0) ? bit1 : bit0); + ::bit::fill(first + bitsof(), last, (integral < 0) ? bit1 : bit0); } else { - ::bit::fill(value.begin() + N, value.end(), (integral < 0) ? bit1 : bit0); + ::bit::fill(first + N, last, (integral < 0) ? bit1 : bit0); } } + + template + constexpr static void from_integral( + detail::uninitialized_t, + bit_sized_range auto& value, + const U& integral) noexcept { + from_integral(detail::uninitialized, value.begin(), value.end(), integral); + } }; } // namespace bit::policy diff --git a/include/bitlib/bit-containers/bit_vector.hpp b/include/bitlib/bit-containers/bit_vector.hpp index f65f8acb..78fa10e8 100644 --- a/include/bitlib/bit-containers/bit_vector.hpp +++ b/include/bitlib/bit-containers/bit_vector.hpp @@ -31,8 +31,6 @@ namespace bit { // ========================================================================== // - - /* ****************************** BIT VECTOR ****************************** */ //! A bit-vector with a similar interface to std::vector template > @@ -186,7 +184,7 @@ class bit_vector { /* * Slice */ - constexpr bit_array_ref<> operator()(size_type begin, size_type end) const noexcept; + constexpr bit_array_ref operator()(size_type begin, size_type end) const noexcept; /* * Helper functions @@ -659,8 +657,8 @@ constexpr void bit_vector::append_range(R&& range) { * Slice */ template -constexpr bit_array_ref<> bit_vector::operator()(size_type begin, size_type end) const noexcept { - return bit_array_ref<>(&this->at(begin), end - begin); +constexpr bit_array_ref bit_vector::operator()(size_type begin, size_type end) const noexcept { + return bit_array_ref(&this->at(begin), end - begin); } // ------------------------ BIT VECTOR: DEBUGGING -------------------------- // diff --git a/include/bitlib/bit-iterator/bit.hpp b/include/bitlib/bit-iterator/bit.hpp index d0b5f70d..679019fb 100644 --- a/include/bitlib/bit-iterator/bit.hpp +++ b/include/bitlib/bit-iterator/bit.hpp @@ -30,7 +30,7 @@ // Miscellaneous // ========================================================================== // - +static_assert(std::is_same_v>::word_type, uint8_t>); // ========================================================================== // #endif // _BIT_HPP_INCLUDED diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index f56725be..fbeaa07e 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -163,6 +163,16 @@ struct exact_floor_integral { template using exact_floor_integral_t = typename exact_floor_integral::type; +template +struct is_static_castable : std::false_type {}; + +template +struct is_static_castable(std::declval()))>> + : std::true_type {}; + +template +constexpr bool is_static_castable_v = is_static_castable::value; + /* ***************************** BINARY DIGITS ****************************** */ // Binary digits structure definition // Implementation template: only instantiates static_asserts for non-byte types. @@ -224,10 +234,14 @@ template constexpr T _shrd(T dst, T src, T cnt) noexcept; // Multiword multiply -template +template constexpr T _mulx(T src0, T src1, T* hi) noexcept; -template +template constexpr T _mulx(T src0, T src1, T* hi, X...) noexcept; +template +constexpr T _divx(const T& numerator_hi, const T& numerator_lo, const T& denominator, T* remainder) noexcept; +template +constexpr T _divx(const T& numerator_hi, const T& numerator_lo, const T& denominator, T* remainder, X...) noexcept; /* ************************************************************************** */ /* @@ -235,6 +249,9 @@ Logical shift right */ template constexpr T lsr(const T val, const size_type shift) { +#ifdef BITLIB_DETECT_UNDEFINED_SHIFT + assert(shift < bitsof()); +#endif return static_cast(static_cast>(val) >> shift); } @@ -245,9 +262,36 @@ template constexpr exact_floor_integral_t lsr(const T val, const size_type shift) { static_assert(!std::is_same_v, void>, "Type T must be convertible to an integral type"); +#ifdef BITLIB_DETECT_UNDEFINED_SHIFT + assert(shift < bitsof>()); +#endif return static_cast>(static_cast>>(val) >> shift); } +/* +Logical shift left +*/ +template +constexpr T lsl(const T val, const size_type shift) { +#ifdef BITLIB_DETECT_UNDEFINED_SHIFT + assert(shift < bitsof()); +#endif + return static_cast(static_cast>(val) << shift); +} + +/* +Logic shift left when `val` operand is a proxy reference +*/ +template +constexpr exact_floor_integral_t lsl(const T val, const size_type shift) { + static_assert(!std::is_same_v, void>, + "Type T must be convertible to an integral type"); +#ifdef BITLIB_DETECT_UNDEFINED_SHIFT + assert(shift < bitsof>()); +#endif + return static_cast>(static_cast>>(val) << shift); +} + enum class _mask_len { unknown, in_range @@ -257,6 +301,9 @@ template one = std::make_unsigned_t(1); if constexpr (len_in_range != _mask_len::unknown) { +#ifdef BITLIB_DETECT_UNDEFINED_SHIFT + assert(len < bitsof()); +#endif return static_cast((one << len) - one); } else { // The digits_mask is solely here to prevent Undefined Sanitizer @@ -479,9 +526,9 @@ constexpr T _shld(T dst, T src, T cnt) noexcept { static_assert(binary_digits::value, ""); constexpr T digits = binary_digits::value; if (cnt < digits) { - dst = (dst << cnt) | (lsr(src, (digits - cnt))); + dst = lsl(dst, cnt) | (lsr(src, (digits - cnt))); } else { - dst = (src << (cnt - digits)) * (cnt < digits + digits); + dst = lsl(src, cnt - digits) * (cnt < (digits + digits)); } return dst; } @@ -494,9 +541,9 @@ constexpr T _shrd(T dst, T src, T cnt) noexcept { static_assert(binary_digits::value, ""); constexpr T digits = binary_digits::value; if (cnt < digits) { - dst = (lsr(dst, cnt)) | (src << (digits - cnt)); + dst = (lsr(dst, cnt)) | lsl(src, (digits - cnt)); } else { - dst = (lsr(src, (cnt - digits))) * (cnt < digits + digits); + dst = (lsr(src, (cnt - digits))) * (cnt < (digits + digits)); } return dst; } @@ -563,14 +610,14 @@ static inline unsigned char add_carry_sub_borrow(unsigned char c_in, U a, U b, U *out = static_cast(tmp_out >> shift); return carry; } else if constexpr (32 == bitsof()) { - return ADDCARRYSUBBORROW32(c_in, static_cast(a), static_cast(b), reinterpret_cast(out)); + return ADDCARRYSUBBORROW32(c_in, static_cast(a), static_cast(b), reinterpret_cast(out)); } else if constexpr (64 == bitsof()) { - return ADDCARRYSUBBORROW64(c_in, static_cast(a), static_cast(b), reinterpret_cast(out)); + return ADDCARRYSUBBORROW64(c_in, static_cast(a), static_cast(b), reinterpret_cast(out)); } else if constexpr (0 == (bitsof() % 64)) { using t64 = std::conditional, int64_t, uint64_t>; unsigned char carry; for (int i = 0; i < (bitsof() / 64); i++) { - carry = ADDCARRYSUBBORROW64(c_in, static_cast(a >> (i * 64)), static_cast(b >> (i * 64)), reinterpret_cast(out) + i); + carry = ADDCARRYSUBBORROW64(c_in, static_cast(a >> (i * 64)), static_cast(b >> (i * 64)), reinterpret_cast(out) + i); } return carry; } else { @@ -591,42 +638,161 @@ static inline unsigned char sub_borrow(unsigned char c_in, U a, U b, U* out) noe // -------------------------------------------------------------------------- // +template +constexpr T _divx(const T& numerator_hi, const T& numerator_lo, const T& denominator, T* remainder) noexcept { + constexpr auto digits = bitsof(); + if constexpr (digits > bitsof()) { + T128 tmp128 = (static_cast(numerator_hi) << digits) | static_cast(numerator_lo); + T quotient = static_cast(tmp128 / static_cast(denominator)); + if (remainder) { + *remainder = static_cast(tmp128 % static_cast(denominator)); + } + return quotient; + } else { + return _divx(numerator_hi, numerator_lo, denominator, remainder, std::ignore); + } +} + +// This code is from a 'public domain' post of ridiculousfish: +// https://ridiculousfish.com/blog/posts/labor-of-division-episode-v.html +template +constexpr T _divx(const T& numerator_hi, const T& numerator_lo, const T& denominator, T* remainder, X...) noexcept { + constexpr auto digits = bitsof(); + using wider_t = ceil_integral; + if constexpr ((digits + digits) <= binary_digits::value) { + wider_t tmp = (static_cast(numerator_hi) << digits) | static_cast(numerator_lo); + T quotient = static_cast(tmp / static_cast(denominator)); + if (remainder) { + *remainder = static_cast(tmp % static_cast(denominator)); + } + return quotient; + } else if constexpr (digits > bitsof()) { + T numhi = numerator_hi; + T numlo = numerator_lo; + T den = denominator; + // We work in base 2**32. + // A uint32 holds a single digit. A uint64 holds two digits. + // Our numerator is conceptually [num3, num2, num1, num0]. + // Our denominator is [den1, den0]. + const uint64_t b = (1ull << 32); + + // The high and low digits of our computed quotient. + uint32_t q1; + uint32_t q0; + + // The normalization shift factor. + int shift; + + // The high and low digits of our denominator (after normalizing). + // Also the low 2 digits of our numerator (after normalizing). + uint32_t den1; + uint32_t den0; + uint32_t num1; + uint32_t num0; + + // A partial remainder. + uint64_t rem; + + // The estimated quotient, and its corresponding remainder (unrelated to true remainder). + uint64_t qhat; + uint64_t rhat; + + // Variables used to correct the estimated quotient. + uint64_t c1; + uint64_t c2; + + // Check for overflow and divide by 0. + if (numhi >= den) { + if (remainder != nullptr) { + *remainder = ~0ull; + } + return ~0ull; + } + + // Determine the normalization factor. We multiply den by this, so that its leading digit is at + // least half b. In binary this means just shifting left by the number of leading zeros, so that + // there's a 1 in the MSB. + // We also shift numer by the same amount. This cannot overflow because numhi < den. + // The expression (-shift & 63) is the same as (64 - shift), except it avoids the UB of shifting + // by 64. The funny bitwise 'and' ensures that numlo does not get shifted into numhi if shift is 0. + // clang 11 has an x86 codegen bug here: see LLVM bug 50118. The sequence below avoids it. + shift = std::countl_zero(den); + den <<= shift; + numhi <<= shift; + numhi |= (numlo >> (-shift & 63)) & (-(int64_t)shift >> 63); + numlo <<= shift; + + // Extract the low digits of the numerator and both digits of the denominator. + num1 = (uint32_t)(numlo >> 32); + num0 = (uint32_t)(numlo & 0xFFFFFFFFu); + den1 = (uint32_t)(den >> 32); + den0 = (uint32_t)(den & 0xFFFFFFFFu); + + // We wish to compute q1 = [n3 n2 n1] / [d1 d0]. + // Estimate q1 as [n3 n2] / [d1], and then correct it. + // Note while qhat may be 2 digits, q1 is always 1 digit. + qhat = numhi / den1; + rhat = numhi % den1; + c1 = qhat * den0; + c2 = rhat * b + num1; + if (c1 > c2) { + qhat -= (c1 - c2 > den) ? 2 : 1; + } + q1 = (uint32_t)qhat; + + // Compute the true (partial) remainder. + rem = numhi * b + num1 - q1 * den; + + // We wish to compute q0 = [rem1 rem0 n0] / [d1 d0]. + // Estimate q0 as [rem1 rem0] / [d1] and correct it. + qhat = rem / den1; + rhat = rem % den1; + c1 = qhat * den0; + c2 = rhat * b + num0; + if (c1 > c2) { + qhat -= (c1 - c2 > den) ? 2 : 1; + } + q0 = (uint32_t)qhat; + + // Return remainder if requested. + if (remainder != nullptr) { + *remainder = (rem * b + num0 - q0 * den) >> shift; + } + return ((uint64_t)q1 << 32) | q0; + } else { + assert(false); + } +} + // -------- IMPLEMENTATION DETAILS: INSTRUCTIONS: MULTIWORD MULTIPLY -------- // // Multiplies src0 and src1 and gets the full result with compiler intrinsics -template -constexpr T _mulx(T src0, T src1, T* hi) noexcept -{ - static_assert(binary_digits::value, ""); - using wider_t = ceil_integral() + bitsof()>; - constexpr T digits = binary_digits::value; - wider_t tmp = 0; - T128 tmp128 = 0; - T lo = 0; - if (digits == std::numeric_limits::digits) { - tmp128 = static_cast(src0) * static_cast(src1); - *hi = tmp128 >> digits; - lo = tmp128; - } else if (digits + digits == binary_digits::value) { - tmp = static_cast(src0) * static_cast(src1); - *hi = tmp >> digits; - lo = tmp; - } else { - lo = _mulx(src0, src1, hi, std::ignore); - } - return lo; +template +constexpr T _mulx(T src0, T src1, T* hi) noexcept { + using wider_t = ceil_integral() + bitsof()>; + constexpr auto digits = bitsof(); + if constexpr (digits > bitsof()) { + T128 tmp128 = static_cast(src0) * static_cast(src1); + *hi = static_cast(tmp128 >> digits); + return static_cast(tmp128); + } else { + return _mulx(src0, src1, hi, std::ignore); + } } -// Multiplies src0 and src1 and gets the full result without compiler intrinsics -template -constexpr T _mulx(T src0, T src1, T* hi, X...) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; +template +constexpr T _mulx(T src0, T src1, T* hi, X...) noexcept { + constexpr T digits = bitsof(); + using wider_t = ceil_integral() + bitsof()>; + if constexpr ((digits + digits) <= bitsof()) { + wider_t tmp = static_cast(src0) * static_cast(src1); + *hi = tmp >> digits; + return static_cast(tmp); + } else { + // Multiplies src0 and src1 and gets the full result without compiler intrinsics constexpr T offset = digits / 2; - constexpr T ones = ~static_cast(0); - const T lsbs0 = src0 & static_cast(lsr(ones, (digits - offset))); + const T lsbs0 = src0 & _mask(digits - offset); const T msbs0 = lsr(src0, offset); - const T lsbs1 = src1 & static_cast(lsr(ones, (digits - offset))); + const T lsbs1 = src1 & _mask(digits - offset); const T msbs1 = lsr(src1, offset); const T llsbs = lsbs0 * lsbs1; const T mlsbs = msbs0 * lsbs1; @@ -637,6 +803,7 @@ constexpr T _mulx(T src0, T src1, T* hi, X...) noexcept const T mcarry = static_cast(mi < mlsbs || mi < lmsbs) << offset; *hi = static_cast(lsr(mi, offset)) + msbs0 * msbs1 + mcarry + lcarry; return lo; + } } // -------------------------------------------------------------------------- // diff --git a/include/bitlib/bit-iterator/bit_word_pointer_adapter.hpp b/include/bitlib/bit-iterator/bit_word_pointer_adapter.hpp index d9c207a6..f167db54 100644 --- a/include/bitlib/bit-iterator/bit_word_pointer_adapter.hpp +++ b/include/bitlib/bit-iterator/bit_word_pointer_adapter.hpp @@ -208,6 +208,8 @@ constexpr auto operator-( } } +static_assert(std::is_same_v>, uint8_t>); + } // namespace bit #endif // _BIT_WORD_POINTER_ADAPTER_HPP_ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2e4bd50e..aad7387e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,24 +24,27 @@ add_executable(bitlib-tests) set(TEST_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/fixtures.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-addition.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-array.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-bitwise.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-copy_backward.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-copy.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-count.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-count_leading.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-division.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-equal.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-fill.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-find.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-iterator_adapter.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-literal.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-move.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-multiplication.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-reverse.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-rotate.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-shift.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-span.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-swap_ranges.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/src/test-to_string.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/test-to_from_string.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-transform.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-usecase.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/test-array_ref.cpp" diff --git a/test/src/test-addition.cpp b/test/src/test-addition.cpp new file mode 100644 index 00000000..8a90ca57 --- /dev/null +++ b/test/src/test-addition.cpp @@ -0,0 +1,16 @@ +#include + +#include "bitlib/bitlib.hpp" +#include "gtest/gtest.h" + +TEST(BitAddition, Basic) { + auto bits = 0xF'0F_b; + EXPECT_EQ(0, bit::addition(bits.begin(), bits.end(), static_cast(0x1u))); + EXPECT_EQ(0xF'10_b, bits); +} + +TEST(BitAddition, LowBits) { + auto bits = 0x7'F_b; + EXPECT_EQ(0, bit::addition(bits.begin(), bits.end(), 1'1_b)); + EXPECT_EQ(0x7'10_b, bits); +} diff --git a/test/src/test-copy.cpp b/test/src/test-copy.cpp index 045fe743..12ab40fe 100644 --- a/test/src/test-copy.cpp +++ b/test/src/test-copy.cpp @@ -122,3 +122,11 @@ TYPED_TEST(MixedDoubleRangeTest, Copy) { boolvec1.begin(), boolvec1.end(), comparator)); } } + +TEST(Copy, ImplicitConvert) { + std::array words = {0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x1A, 0x1B, 0x1C, 0x1D}; + bit::bit_array<> + bits(5 * 8 + 4, bit::bit0); + bit::copy(words.begin(), words.begin() + 5, bits.begin() + 4); + EXPECT_EQ(bits, 0x2C'0E0D0C0B0A0_b); +} diff --git a/test/src/test-division.cpp b/test/src/test-division.cpp new file mode 100644 index 00000000..c3ca4587 --- /dev/null +++ b/test/src/test-division.cpp @@ -0,0 +1,22 @@ +#include + +#include "bitlib/bitlib.hpp" +#include "gtest/gtest.h" + +TEST(BitDivision, Basic) { + auto bits = 0xF'0F_b; + EXPECT_EQ((15 % 2), bit::division(bits.begin(), bits.end(), static_cast(0x2u))); + EXPECT_EQ(0xF'07_b, bits); +} + +TEST(BitDivision, AllOnes) { + auto bits = 0xF'00FF_b; + EXPECT_EQ((255 % 255), bit::division(bits.begin(), bits.end(), 8'255_b)); + EXPECT_EQ(0xF'0001_b, bits); +} + +TEST(BitDivision, LowBits) { + auto bits = 0xF'F_b; + EXPECT_EQ((15 % 2), bit::division(bits.begin(), bits.end(), 2'2_b)); + EXPECT_EQ(0xF'07_b, bits); +} diff --git a/test/src/test-multiplication.cpp b/test/src/test-multiplication.cpp new file mode 100644 index 00000000..8c952ca9 --- /dev/null +++ b/test/src/test-multiplication.cpp @@ -0,0 +1,22 @@ +#include + +#include "bitlib/bitlib.hpp" +#include "gtest/gtest.h" + +TEST(BitMultiplication, Basic) { + auto bits = 0xF'0F_b; + EXPECT_EQ(0, bit::multiplication(bits.begin(), bits.end(), static_cast(0x2u))); + EXPECT_EQ(0xF'1E_b, bits); +} + +TEST(BitMultiplication, AllOnes) { + auto bits = 0xF'00FF_b; + EXPECT_EQ(1, bit::multiplication(bits.begin(), bits.end(), 8'255_b)); + EXPECT_EQ(0xF'7E01_b, bits); +} + +TEST(BitMultiplication, LowBits) { + auto bits = 0xF'F_b; + EXPECT_EQ(0, bit::multiplication(bits.begin(), bits.end(), 2'2_b)); + EXPECT_EQ(0xF'1E_b, bits); +} diff --git a/test/src/test-to_from_string.cpp b/test/src/test-to_from_string.cpp new file mode 100644 index 00000000..839e9258 --- /dev/null +++ b/test/src/test-to_from_string.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#include "bitlib/bit-algorithms/count.hpp" +#include "bitlib/bit-algorithms/to_from_string.hpp" +#include "bitlib/bit-containers/bit_array.hpp" +#include "bitlib/bit-containers/bit_array_dynamic_extent.hpp" +#include "fixtures.hpp" + +// Third-party libraries +#include "gtest/gtest.h" + +TEST(ToString, Blah) { + auto num = 0x20'DEADBEEF_b; + EXPECT_EQ(static_cast(num), 0xDEADBEEF); + auto str = bit::to_string(num); + EXPECT_EQ(str, "DEADBEEF"); + num = 0x20'0EADBEEF_b; + str = bit::to_string(num); + EXPECT_EQ(str, "EADBEEF"); + str = bit::to_string(num); + EXPECT_EQ(str, "0EADBEEF"); + auto num2 = 0x23'10EADBEEF_b; + EXPECT_EQ(num2.size(), 35); + str = bit::to_string(num2); + EXPECT_EQ(str, "10EADBEEF"); +} + +TEST(ToString, base10) { + auto num = 10'123_b; + auto str = bit::to_string(num); + EXPECT_EQ(str, "123"); +} + +TEST(ToString, Streaming) { + std::stringstream sstr; + auto num = 10'123_b; + sstr << num; + EXPECT_EQ(sstr.str(), "123"); +} + +TEST(FromString, Blah) { + bit::bit_array<16> arr_16; + bit::from_string("DEADBEEF", arr_16); + EXPECT_EQ(arr_16, 0x10'BEEF_b); + bit::bit_array<18> arr_18; + bit::from_string("BEEF", arr_18); + EXPECT_EQ(arr_18, 0x12'0BEEF_b); + bit::from_string("123", arr_16); + EXPECT_EQ(arr_16, 16'123_b); +} \ No newline at end of file diff --git a/test/src/test-to_string.cpp b/test/src/test-to_string.cpp deleted file mode 100644 index 65ffc50d..00000000 --- a/test/src/test-to_string.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include -#include - -#include "bitlib/bit-algorithms/count.hpp" -#include "bitlib/bit-algorithms/to_string.hpp" -#include "bitlib/bit-containers/bit_array.hpp" -#include "bitlib/bit-containers/bit_array_dynamic_extent.hpp" -#include "fixtures.hpp" - -// Third-party libraries -#include "gtest/gtest.h" - -TEST(ToString, Blah) { - auto num = 0x20'DEADBEEF_b; - EXPECT_EQ(static_cast(num), 0xDEADBEEF); - auto str = bit::to_string<16>(num); - EXPECT_EQ(str, "DEADBEEF"); - num = 0x20'0EADBEEF_b; - str = bit::to_string<16>(num); - EXPECT_EQ(str, "EADBEEF"); - str = bit::to_string<16, true>(num); - EXPECT_EQ(str, "0EADBEEF"); - auto num2 = 0x23'10EADBEEF_b; - EXPECT_EQ(num2.size(), 35); - str = bit::to_string<16>(num2); - EXPECT_EQ(str, "10EADBEEF"); -} From 729052928ad04de4f3ca9dd8c040e1b3fd7b6be4 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sat, 2 Aug 2025 08:43:59 -0400 Subject: [PATCH 79/85] Remove unused transform_compare (can be implemented with accumulate while) --- .../bit-algorithms/transform_compare.hpp | 134 ------------------ 1 file changed, 134 deletions(-) delete mode 100644 include/bitlib/bit-algorithms/transform_compare.hpp diff --git a/include/bitlib/bit-algorithms/transform_compare.hpp b/include/bitlib/bit-algorithms/transform_compare.hpp deleted file mode 100644 index fb0e177d..00000000 --- a/include/bitlib/bit-algorithms/transform_compare.hpp +++ /dev/null @@ -1,134 +0,0 @@ -// ================================= EQUAL =================================== // -// Project: The Experimental Bit Algorithms Library -// Name: equal.hpp -// Contributor: Bryce Kille [2019] -// License: BSD 3-Clause License -// ========================================================================== // -#ifndef _EQUAL_HPP_INCLUDED -#define _EQUAL_HPP_INCLUDED -// ========================================================================== // - - - -// ================================ PREAMBLE ================================ // -// C++ standard library -#include -#include -// Project sources -#include "bitlib/bit-iterator/bit.hpp" -// Third-party libraries -// Miscellaneous -namespace bit { -// ========================================================================== // - - - -// ---------------------------- Equal Algorithms ----------------------------- // - -// Status: Does not work for Input/Output iterators due to distance call -template -constexpr bool transform_compare( - bit_iterator first, - bit_iterator last, - bit_iterator first2, - BinaryComparison binary_cmp -) -{ - // Types and constants - using dst_word_type = typename bit_iterator::word_type; - using src_word_type = typename bit_iterator::word_type; - using word_type = dst_word_type; - using size_type = typename bit_iterator::size_type; - constexpr size_type digits = binary_digits::value; - - // Assertions - _assert_range_viability(first, last); - static_assert(::std::is_same::value, "Underlying word types must be equal"); - if (first == last) return true; - - // Initialization - size_type total_bits_to_check = distance(first, last); - size_type remaining_bits_to_check = total_bits_to_check; - - if (remaining_bits_to_check < digits) { - - } - - auto it1 = first1.base() + total_bits_to_check - 1 - digits; - auto it2 = first2.base() + total_bits_to_check - 1 - digits; - const bool is_d_first_aligned = it1.position() == 0; - - - bitsof< - // d_first is not aligned. - if (!is_d_first_aligned) { - - size_type partial_bits_to_op = ::std::min( - remaining_bits_to_op, - digits - d_first.position() - ); - - if (!binary_cmp( - static_cast( - get_word(it1, partial_bits_to_op) - << static_cast(d_first.position()) - ), - static_cast( - get_word(first2, partial_bits_to_op) - << static_cast(d_first.position()) - ) - )) { return false;} - remaining_bits_to_op -= partial_bits_to_op; - advance(first1, partial_bits_to_op); - advance(first2, partial_bits_to_op); - it++; - const size_type partial_bits_to_check = ::std::min( - remaining_bits_to_check, - digits - d_first.position()); - const word_type mask = _mask(partial_bits_to_check) << d_first.position(); - const word_type comp = static_cast( - get_word(first, partial_bits_to_check) - << d_first.position()); - if ((mask & *it) != (mask & comp)) { return false; } - remaining_bits_to_check -= partial_bits_to_check; - advance(first, partial_bits_to_check); - it++; - } - - if (remaining_bits_to_check > 0) { - const bool is_first_aligned = first.position() == 0; - // d_first will be aligned at this point - if (is_first_aligned && remaining_bits_to_check >= digits) { - auto N = ::std::distance(first.base(), last.base()); - bool found_mismatch = !::std::equal(first.base(), last.base(), it); - if (found_mismatch) {return false;} - it += N; - first += digits * N; - remaining_bits_to_check -= digits * N; - } else { - // TODO benchmark if its faster to ::std::check the entire range then shift - while (remaining_bits_to_check >= digits) { - if (*it != get_word(first, digits)) {return false;} - remaining_bits_to_check -= digits; - it++; - advance(first, digits); - } - } - if (remaining_bits_to_check > 0) { - const word_type mask = _mask(remaining_bits_to_check); - const word_type comp = get_word(first, remaining_bits_to_check); - if ((mask & *it) != (mask & comp)) { - return false; - } - } - } - return true; -} -// -------------------------------------------------------------------------- // - - - -// ========================================================================== // -} // namespace bit -#endif // _EQUAL_HPP_INCLUDED -// ========================================================================== // From 6c9d77699b1c1ade6b26d7dfe113a4c2d5e1fc7e Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Sun, 3 Aug 2025 00:47:37 +0000 Subject: [PATCH 80/85] Add clang-format workflow (#26) * Add clang-format workflow * Check clang-format of diff/patch instead of everything --- .github/workflows/clang_format.yaml | 63 ++++++++++++++++++++++ .github/workflows/cmake-multi-platform.yml | 2 + 2 files changed, 65 insertions(+) create mode 100644 .github/workflows/clang_format.yaml diff --git a/.github/workflows/clang_format.yaml b/.github/workflows/clang_format.yaml new file mode 100644 index 00000000..59c0c12b --- /dev/null +++ b/.github/workflows/clang_format.yaml @@ -0,0 +1,63 @@ +name: Clang Format Check + +on: + push: + branches: [ "master" ] + paths-ignore: + - '**/*.md' + - '*.md' + pull_request: + branches: [ "master" ] + paths-ignore: + - '**/*.md' + - '*.md' + +jobs: + clang-format: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install clang-format + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y clang-format-19 + + - name: Fetch base branch + if: github.event_name == 'pull_request' + run: | + echo "Fetching base branch ${{ github.base_ref }}" + git fetch origin ${{ github.base_ref }}:${{ github.base_ref }} + + - name: Get Git Diff + id: diff + run: | + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + echo "Getting diff for pull request" + git diff origin/${{ github.base_ref }}...HEAD > diff.patch + else + echo "Getting diff for commit" + git diff ${{ github.event.before }} ${{ github.event.after }} > diff.patch + fi + + - name: Check formatting + shell: bash + run: | + cat diff.patch | clang-format-diff-19 -p 1 > diff_format.patch + + if [ -s diff_format.patch ]; then + echo "āŒ Code formatting issues found. See diff_format.patch." + exit 1 + fi + + - name: Upload clang-formatted diff + if: failure() + uses: actions/upload-artifact@v4 + with: + name: clang-format-diff + path: diff_format.patch diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 66d25613..c452235a 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -6,10 +6,12 @@ on: branches: [ "master" ] paths-ignore: - '**/*.md' + - '*.md' pull_request: branches: [ "master" ] paths-ignore: - '**/*.md' + - '*.md' jobs: run-matrix: From 61746a6ab2d93ffec94dad3a2a920d6f9ba341e8 Mon Sep 17 00:00:00 2001 From: Peter McLean Date: Sun, 3 Aug 2025 20:36:29 -0400 Subject: [PATCH 81/85] Remove unused / empty debug_utils --- CMakeLists.txt | 1 - .../bitlib/bit-algorithms/bit_algorithm.hpp | 6 --- include/bitlib/bit-algorithms/debug_utils.hpp | 40 ------------------- 3 files changed, 47 deletions(-) delete mode 100644 include/bitlib/bit-algorithms/debug_utils.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b2616ae2..4cbea98a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,6 @@ target_sources(bitlib INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/copy_backward.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/copy.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/count.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/debug_utils.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/equal.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/fill.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/bitlib/bit-algorithms/find.hpp diff --git a/include/bitlib/bit-algorithms/bit_algorithm.hpp b/include/bitlib/bit-algorithms/bit_algorithm.hpp index 3ba67fc6..390f1154 100644 --- a/include/bitlib/bit-algorithms/bit_algorithm.hpp +++ b/include/bitlib/bit-algorithms/bit_algorithm.hpp @@ -14,10 +14,7 @@ #define _Bit_Algorithm_VERSION_MINOR @Bit_Algorithm_VERSION_MINOR@ // ========================================================================== // - - // ================================ PREAMBLE ================================ // -#include "debug_utils.hpp" //TODO does this belong somewhere else? #include "bit_algorithm_details.hpp" // overloads #include "addition.hpp" @@ -25,7 +22,6 @@ #include "copy.hpp" #include "copy_backward.hpp" #include "count.hpp" -#include "debug_utils.hpp" #include "division.hpp" #include "equal.hpp" #include "fill.hpp" @@ -42,8 +38,6 @@ #include "type_traits.hpp" // ========================================================================== // - - // ========================================================================== // #endif // _BIT_ALGORITHM_HPP_INCLUDED // ========================================================================== // diff --git a/include/bitlib/bit-algorithms/debug_utils.hpp b/include/bitlib/bit-algorithms/debug_utils.hpp deleted file mode 100644 index 74e590d1..00000000 --- a/include/bitlib/bit-algorithms/debug_utils.hpp +++ /dev/null @@ -1,40 +0,0 @@ -// =========================== DEBUG_UTILS ============================== // -// Project: The Experimental Bit Algorithms Library -// Name: debug_utils.hpp -// Description: Utilities useful for debugging -// Contributor: Bryce Kille [2019] -// License: BSD 3-Clause License -// ========================================================================== // -#ifndef _DEBUG_HPP_INCLUDED -#define _DEBUG_HPP_INCLUDED -// ========================================================================== // - - - -// ================================ PREAMBLE ================================ // -// C++ standard library -#include -// Project sources -// Third-party libraries -// Miscellaneous -namespace bit { -// ========================================================================== // - - - -// --------------------------- Utility Functions ---------------------------- // -//template -//std::string word_to_vec(T1 word) { - //std::bitset::value> word_c(word); - //std::string out = word_c.to_string(); - //std::reverse(out.begin(), out.end()); - //return out; -//} -// -------------------------------------------------------------------------- // - - - -// ========================================================================== // -} // namespace bit -#endif // _COPY_HPP_INCLUDED -// ========================================================================== // From 3fead7b1b9892b1720226de5dd4157af3ce599d0 Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Wed, 6 Aug 2025 15:17:35 +0000 Subject: [PATCH 82/85] Use upload / download action instead of cache (#28) * Use upload / download action instead of cache * Try without single quotes * Rerun base_ref build if possible --- .github/workflows/cmake_build_and_test.yml | 45 ++++++++++------------ 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/.github/workflows/cmake_build_and_test.yml b/.github/workflows/cmake_build_and_test.yml index b56d2469..b503a5ff 100644 --- a/.github/workflows/cmake_build_and_test.yml +++ b/.github/workflows/cmake_build_and_test.yml @@ -203,7 +203,7 @@ jobs: - name: Upload build dir uses: actions/upload-artifact@v4 with: - name: ${{ inputs.preset }}_${{ inputs.test_os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_${{ inputs.build_type }} + name: ${{ inputs.preset }}_${{ inputs.test_os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_${{ inputs.build_type }}_${{ github.head_ref || github.ref_name }} path: '${{ steps.strings.outputs.build-output-dir }}' test: @@ -234,7 +234,7 @@ jobs: - name: Download build dir uses: actions/download-artifact@v4 with: - name: ${{ inputs.preset }}_${{ inputs.test_os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_${{ inputs.build_type }} + name: ${{ inputs.preset }}_${{ inputs.test_os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_${{ inputs.build_type }}_${{ github.head_ref || github.ref_name }} path: '${{ github.workspace }}/../build' - name: Set reusable strings @@ -276,42 +276,37 @@ jobs: --output-on-failure \ --parallel - - name: Cache benchmark json - if: inputs.preset == 'benchmark' - uses: actions/cache@v4 + - name: Fetch base_ref benchmark build + if: inputs.preset == 'benchmark' && github.event_name == 'pull_request' + id: base_ref_build + continue-on-error: true + uses: actions/download-artifact@v4 with: - path: '${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result.json' - key: benchmark_${{ inputs.test_os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_${{ inputs.build_type }}_${{ github.event.pull_request.head.ref }} + name: ${{ inputs.preset }}_${{ inputs.test_os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_${{ inputs.build_type }}_${{ github.event.pull_request.base.ref }} + path: ${{ steps.strings.outputs.build-output-dir }}/base_ref_build - - name: Rename current benchmark json - if: inputs.preset == 'benchmark' + - name: Run base_ref benchmark + if: inputs.preset == 'benchmark' && github.event_name == 'pull_request' && steps.base_ref_build.outcome == 'success' shell: bash run: | - mv '${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result.json' '${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result_new.json' - - - name: Fetch base_ref benchmark json - if: inputs.preset == 'benchmark' - id: base_ref_json - uses: actions/cache@v4 - with: - path: '${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result.json' - key: benchmark_${{ inputs.test_os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_${{ inputs.build_type }}_${{ github.event.pull_request.base.ref }} + ctest --test-dir ${{ steps.strings.outputs.build-output-dir }}/base_ref_build \ + --build-config ${{ inputs.build_type }} \ + --output-on-failure \ + --parallel - name: Compare Benchmarks - if: inputs.preset == 'benchmark' && steps.base_ref_json.outputs.cache-hit == 'true' + if: inputs.preset == 'benchmark' && github.event_name == 'pull_request' && steps.base_ref_build.outcome == 'success' shell: bash run: | python3 '${{ steps.strings.outputs.build-output-dir }}/_deps/benchmark-src/tools/compare.py' benchmarks \ '${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result.json' \ - '${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result_new.json' \ + '${{ steps.strings.outputs.build-output-dir }}/base_ref_build/benchmark/benchmark_result.json' \ | tee '${{ steps.strings.outputs.build-output-dir }}/benchmark/comparison.txt' - name: Archive benchmark results - if: inputs.preset == 'benchmark' && steps.base_ref_json.outputs.cache-hit == 'true' + if: inputs.preset == 'benchmark' uses: actions/upload-artifact@v4 with: - name: benchmark_${{ inputs.os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_json + name: benchmark_${{ inputs.test_os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_${{ inputs.build_type }}_${{ github.head_ref || github.ref_name }}_json path: | - '${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result.json' - '${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result_new.json' - '${{ steps.strings.outputs.build-output-dir }}/benchmark/comparison.txt' + ${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result.json From 4adca69e9cd248cf83dae60458e268b1d4efa3cb Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Wed, 6 Aug 2025 16:55:10 +0000 Subject: [PATCH 83/85] Minor tweaks and improvements (#27) * bit_range args must accept rvalue temporary * Reorder from_integral to be consistent * Remove old style casts * Remove some more warnings * More efficient bit_iterator increment * Must use another download action to access other branch's workflows * Only download base ref build artifact for benchmark * Check outcome. clang-format * Missing python requirements * Must upgrade setuptools * setup python step * Use ref as baseline * Get rid of a few conversion warnings * Fix pointer adapter math for negative increments * Explicit cast * clang format * Give the matrix job a name --- .github/workflows/cmake-multi-platform.yml | 1 + .github/workflows/cmake_build_and_test.yml | 20 ++++-- include/bitlib/bit-algorithms/division.hpp | 8 +-- .../bitlib/bit-algorithms/to_from_string.hpp | 32 ++++----- .../bitlib/bit-containers/bit_array_base.hpp | 20 +++--- include/bitlib/bit-containers/bit_policy.hpp | 24 +++---- include/bitlib/bit-iterator/bit_details.hpp | 18 +++-- include/bitlib/bit-iterator/bit_iterator.hpp | 65 +++++++------------ .../bit-iterator/bit_word_pointer_adapter.hpp | 39 ++++------- 9 files changed, 102 insertions(+), 125 deletions(-) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index c452235a..b8264d73 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -15,6 +15,7 @@ on: jobs: run-matrix: + name: Run CMake Build and Test Matrix uses: ./.github/workflows/cmake_build_and_test.yml strategy: # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. diff --git a/.github/workflows/cmake_build_and_test.yml b/.github/workflows/cmake_build_and_test.yml index b503a5ff..bd88cb01 100644 --- a/.github/workflows/cmake_build_and_test.yml +++ b/.github/workflows/cmake_build_and_test.yml @@ -276,14 +276,16 @@ jobs: --output-on-failure \ --parallel - - name: Fetch base_ref benchmark build - if: inputs.preset == 'benchmark' && github.event_name == 'pull_request' + - name: Download base_ref build id: base_ref_build - continue-on-error: true - uses: actions/download-artifact@v4 + if: inputs.preset == 'benchmark' && github.event_name == 'pull_request' + uses: dawidd6/action-download-artifact@v6 with: + branch: ${{ github.event.pull_request.base.ref }} name: ${{ inputs.preset }}_${{ inputs.test_os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_${{ inputs.build_type }}_${{ github.event.pull_request.base.ref }} + workflow_conclusion: success # only successful runs path: ${{ steps.strings.outputs.build-output-dir }}/base_ref_build + if_no_artifact_found: 'warn' - name: Run base_ref benchmark if: inputs.preset == 'benchmark' && github.event_name == 'pull_request' && steps.base_ref_build.outcome == 'success' @@ -294,13 +296,21 @@ jobs: --output-on-failure \ --parallel + - name: Setup Python + if: inputs.preset == 'benchmark' && github.event_name == 'pull_request' && steps.base_ref_build.outcome == 'success' + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Compare Benchmarks if: inputs.preset == 'benchmark' && github.event_name == 'pull_request' && steps.base_ref_build.outcome == 'success' shell: bash run: | + pip install --upgrade setuptools + python -m pip install -r '${{ steps.strings.outputs.build-output-dir }}/_deps/benchmark-src/tools/requirements.txt' python3 '${{ steps.strings.outputs.build-output-dir }}/_deps/benchmark-src/tools/compare.py' benchmarks \ - '${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result.json' \ '${{ steps.strings.outputs.build-output-dir }}/base_ref_build/benchmark/benchmark_result.json' \ + '${{ steps.strings.outputs.build-output-dir }}/benchmark/benchmark_result.json' \ | tee '${{ steps.strings.outputs.build-output-dir }}/benchmark/comparison.txt' - name: Archive benchmark results diff --git a/include/bitlib/bit-algorithms/division.hpp b/include/bitlib/bit-algorithms/division.hpp index 98ff58c3..48f5d2a1 100644 --- a/include/bitlib/bit-algorithms/division.hpp +++ b/include/bitlib/bit-algorithms/division.hpp @@ -33,15 +33,15 @@ constexpr typename bit_iterator::word_type division( const bit_iterator& d_first, const U& integral_operand) { using word_type = typename bit_iterator::word_type; - word_type remainder = 0; auto long_div = [integral_operand]( - const word_type& remainder, - const word_type& word, - size_t bits = bitsof()) { + const word_type& remainder, + const word_type& word, + size_t bits = bitsof()) { word_type next_remainder; word_type result_word = _divx(remainder, word, static_cast(integral_operand), &next_remainder); return std::make_pair(result_word, next_remainder); }; + word_type remainder = 0; return transform_accumulate_backward(first, last, d_first, d_first + distance(first, last), remainder, long_div, long_div); } diff --git a/include/bitlib/bit-algorithms/to_from_string.hpp b/include/bitlib/bit-algorithms/to_from_string.hpp index 25472819..d3f04d7e 100644 --- a/include/bitlib/bit-algorithms/to_from_string.hpp +++ b/include/bitlib/bit-algorithms/to_from_string.hpp @@ -33,8 +33,8 @@ constexpr auto make_digit_map() { static_assert(Base <= 64, "Base too large for simple char mapping"); ::std::array map{}; - for (std::size_t i = 0; i < Base; ++i) { - map[i] = (i < 10) ? ('0' + i) : ('A' + (i - 10)); + for (unsigned char i = 0; i < static_cast(Base); ++i) { + map[i] = static_cast((i < 10) ? ('0' + i) : ('A' + (i - 10))); } return map; } @@ -76,16 +76,16 @@ constexpr auto make_from_digit_map() { static_assert(Base <= 64, "Base too large for simple char mapping"); ::std::array map{}; - for (std::size_t i = 0; i < 128; ++i) { - map[i] = ~0; + map.fill(~0); + for (unsigned char i = '0'; i <= 'z'; ++i) { if (i >= '0' && i <= '9') { - map[i] = i - '0'; + map[i] = static_cast(i - '0'); } if (i >= 'a' && i <= 'z') { - map[i] = (i - 'a') + 10; + map[i] = static_cast((i - 'a') + 10); } if (i >= 'A' && i <= 'Z') { - map[i] = (i - 'A') + 10; + map[i] = static_cast((i - 'A') + 10); } } return map; @@ -152,7 +152,7 @@ constexpr CharIt to_string( const auto base_bits = std::bit_width(meta.base - 1); const auto base_digits = string::make_digit_map(meta.base); - CharIt cursor = accumulate_while( + CharIt start = accumulate_while( policy::AccumulateNoInitialSubword{}, bit_first, bit_last, str_last, [meta, base_bits, base_digits, str_first](CharIt cursor, auto word, const size_t bits = bitsof()) { @@ -166,8 +166,8 @@ constexpr CharIt to_string( } return std::make_pair(cursor != str_first, cursor); }); - if (cursor != str_first) { - return std::copy(cursor, str_last, str_first); + if (start != str_first) { + return std::copy(start, str_last, str_first); } else { return str_last; } @@ -209,7 +209,7 @@ constexpr size_t estimate_length( str_len = (str_len + base_bits - 1) / base_bits; // Round up to nearest base digit return static_cast(std::max(1, str_len)); } else { - const uint32_t LOG2BASE = std::ceil(1 / std::logbf(base) * (1 << 16)); + const uint32_t LOG2BASE = static_cast(std::ceil(static_cast(1 << 16) / std::logbf(static_cast(base)))); int skip_leading_bits = str_sign_extend_zeros ? 0 : count_msb(first, last, bit0); const auto bits = distance(first, last) - skip_leading_bits; const auto fixed_point = (bits * LOG2BASE); @@ -308,18 +308,18 @@ constexpr void from_string( if (~0 == digit) { continue; } - work |= (digit << bits); + work |= (static_cast(digit) << bits); bits += base_bits; } if (store_bits < bits) { Policy::truncation::template from_integral( - bit_it, bit_last, work); + work, bit_it, bit_last); return; } else if ((store_bits > bits) && (cursor < 0)) { const bit_iterator p_integral(&work); bit_it = ::bit::copy(p_integral, p_integral + bits, bit_it); Policy::extension::template from_integral( - bit_it, bit_last, work); + work, bit_it, bit_last); } else if (store_bits >= bits) { const bit_iterator p_integral(&work); bit_it = ::bit::copy(p_integral, p_integral + bits, bit_it); @@ -421,7 +421,7 @@ constexpr void from_string( template ::value_type>> constexpr void from_string( const std::string& str, - RangeT& bits) { + RangeT&& bits) { using range_iterator_t = std::ranges::iterator_t; using RandomAccessIt = typename range_iterator_t::iterator_type; from_string(str.begin(), str.end(), bits.begin(), bits.end()); @@ -441,7 +441,7 @@ constexpr void from_string( template ::value_type>> constexpr void from_string( const std::string& str, - RangeT& bits, + RangeT&& bits, string::metadata_t meta = string::typical()) { using range_iterator_t = std::ranges::iterator_t; using RandomAccessIt = typename range_iterator_t::iterator_type; diff --git a/include/bitlib/bit-containers/bit_array_base.hpp b/include/bitlib/bit-containers/bit_array_base.hpp index fa82a0e4..842ef00a 100644 --- a/include/bitlib/bit-containers/bit_array_base.hpp +++ b/include/bitlib/bit-containers/bit_array_base.hpp @@ -230,9 +230,9 @@ class array_base : public detail::container_size_storage word_type { return a | b; }); return result; } - constexpr Derived& operator|=(bit_sized_range auto& other) { + constexpr Derived& operator|=(const bit_sized_range auto& other) { assert(other.size() == size()); - transform(derived().begin(), derived().end(), other.begin(), derived().begin(), + transform(derived().cbegin(), derived().cend(), other.begin(), derived().begin(), [](const word_type& a, const word_type& b) -> word_type { return a | b; }); return derived(); } @@ -243,9 +243,9 @@ class array_base : public detail::container_size_storage word_type { return a & b; }); return result; } - constexpr Derived& operator&=(bit_sized_range auto& other) { + constexpr Derived& operator&=(const bit_sized_range auto& other) { assert(other.size() == size()); - transform(derived().begin(), derived().end(), other.begin(), derived().begin(), + transform(derived().cbegin(), derived().cend(), other.begin(), derived().begin(), [](const word_type& a, const word_type& b) -> word_type { return a & b; }); return derived(); } @@ -256,9 +256,9 @@ class array_base : public detail::container_size_storage word_type { return a ^ b; }); return result; } - constexpr Derived& operator^=(bit_sized_range auto& other) { + constexpr Derived& operator^=(const bit_sized_range auto& other) { assert(other.size() == size()); - transform(derived().begin(), derived().end(), other.begin(), derived().begin(), + transform(derived().cbegin(), derived().cend(), other.begin(), derived().begin(), [](const word_type& a, const word_type& b) -> word_type { return a ^ b; }); return derived(); } @@ -314,21 +314,21 @@ class array_base : public detail::container_size_storage()) < bitsof()) { - Policy::truncation::template from_integral(derived(), integral); + Policy::truncation::template from_integral(integral, derived()); } else { ::bit::copy(&integral, &integral + 1, derived().begin()); } if (bitsof() < (size() * bitsof())) { - Policy::extension::template from_integral(detail::uninitialized, derived(), integral); + Policy::extension::template from_integral(detail::uninitialized, integral, derived()); } } else { if constexpr ((N * bitsof()) < bitsof()) { - Policy::truncation::template from_integral(derived(), integral); + Policy::truncation::template from_integral(integral, derived()); } else { ::bit::copy(&integral, &integral + 1, derived().begin()); } if constexpr (bitsof() < (N * bitsof())) { - Policy::extension::template from_integral(detail::uninitialized, derived(), integral); + Policy::extension::template from_integral(detail::uninitialized, integral, derived()); } } } diff --git a/include/bitlib/bit-containers/bit_policy.hpp b/include/bitlib/bit-containers/bit_policy.hpp index 65cbd053..306d175b 100644 --- a/include/bitlib/bit-containers/bit_policy.hpp +++ b/include/bitlib/bit-containers/bit_policy.hpp @@ -39,9 +39,9 @@ struct truncate { template constexpr static void from_integral( + const U& integral, const bit_iterator& first, - const bit_iterator& last, - const U& integral) noexcept { + const bit_iterator& last) noexcept { const bit_pointer integral_begin(&integral); if constexpr (N == std::dynamic_extent) { ::bit::copy(integral_begin, integral_begin + distance(first, last), first); @@ -51,7 +51,7 @@ struct truncate { } template - constexpr static void from_integral(bit_sized_range auto& value, const U& integral) noexcept { + constexpr static void from_integral(const U& integral, bit_sized_range auto& value) noexcept { const bit_pointer integral_begin(&integral); if constexpr (N == std::dynamic_extent) { ::bit::copy(integral_begin, integral_begin + value.size(), value.begin()); @@ -141,9 +141,9 @@ struct sign_extend { template constexpr static void from_integral( + const U& integral, const bit_iterator& first, - const bit_iterator& last, - const U& integral) noexcept { + const bit_iterator& last) noexcept { if constexpr (N == std::dynamic_extent) { if constexpr (std::is_signed_v) { if (integral < 0) { @@ -159,16 +159,16 @@ struct sign_extend { } } template - constexpr static void from_integral(bit_sized_range auto& value, const U& integral) noexcept { - from_integral(value.begin(), value.end(), integral); + constexpr static void from_integral(const U& integral, bit_sized_range auto& value) noexcept { + from_integral(integral, value.begin(), value.end()); } template constexpr static void from_integral( detail::uninitialized_t, + const U& integral, const bit_iterator& first, - const bit_iterator& last, - const U& integral) noexcept { + const bit_iterator& last) noexcept { if constexpr (N == std::dynamic_extent) { ::bit::fill(first + bitsof(), last, (integral < 0) ? bit1 : bit0); } else { @@ -179,9 +179,9 @@ struct sign_extend { template constexpr static void from_integral( detail::uninitialized_t, - bit_sized_range auto& value, - const U& integral) noexcept { - from_integral(detail::uninitialized, value.begin(), value.end(), integral); + const U& integral, + bit_sized_range auto& value) noexcept { + from_integral(detail::uninitialized, integral, value.begin(), value.end()); } }; diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index fbeaa07e..dda2b30b 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -362,7 +362,6 @@ template constexpr T _bitswap(T src) noexcept { static_assert(binary_digits::value, ""); using byte_t = unsigned char; - constexpr auto ignore = nullptr; constexpr T digits = binary_digits::value; constexpr unsigned long long int first = 0x80200802ULL; constexpr unsigned long long int second = 0x0884422110ULL; @@ -719,14 +718,14 @@ constexpr T _divx(const T& numerator_hi, const T& numerator_lo, const T& denomin shift = std::countl_zero(den); den <<= shift; numhi <<= shift; - numhi |= (numlo >> (-shift & 63)) & (-(int64_t)shift >> 63); + numhi |= (numlo >> (-shift & 63)) & (-static_cast(shift) >> 63); numlo <<= shift; // Extract the low digits of the numerator and both digits of the denominator. - num1 = (uint32_t)(numlo >> 32); - num0 = (uint32_t)(numlo & 0xFFFFFFFFu); - den1 = (uint32_t)(den >> 32); - den0 = (uint32_t)(den & 0xFFFFFFFFu); + num1 = static_cast(numlo >> 32); + num0 = static_cast(numlo & 0xFFFFFFFFu); + den1 = static_cast(den >> 32); + den0 = static_cast(den & 0xFFFFFFFFu); // We wish to compute q1 = [n3 n2 n1] / [d1 d0]. // Estimate q1 as [n3 n2] / [d1], and then correct it. @@ -738,7 +737,7 @@ constexpr T _divx(const T& numerator_hi, const T& numerator_lo, const T& denomin if (c1 > c2) { qhat -= (c1 - c2 > den) ? 2 : 1; } - q1 = (uint32_t)qhat; + q1 = static_cast(qhat); // Compute the true (partial) remainder. rem = numhi * b + num1 - q1 * den; @@ -752,13 +751,13 @@ constexpr T _divx(const T& numerator_hi, const T& numerator_lo, const T& denomin if (c1 > c2) { qhat -= (c1 - c2 > den) ? 2 : 1; } - q0 = (uint32_t)qhat; + q0 = static_cast(qhat); // Return remainder if requested. if (remainder != nullptr) { *remainder = (rem * b + num0 - q0 * den) >> shift; } - return ((uint64_t)q1 << 32) | q0; + return (static_cast(q1) << 32) | q0; } else { assert(false); } @@ -768,7 +767,6 @@ constexpr T _divx(const T& numerator_hi, const T& numerator_lo, const T& denomin // Multiplies src0 and src1 and gets the full result with compiler intrinsics template constexpr T _mulx(T src0, T src1, T* hi) noexcept { - using wider_t = ceil_integral() + bitsof()>; constexpr auto digits = bitsof(); if constexpr (digits > bitsof()) { T128 tmp128 = static_cast(src0) * static_cast(src1); diff --git a/include/bitlib/bit-iterator/bit_iterator.hpp b/include/bitlib/bit-iterator/bit_iterator.hpp index 458a4471..859bbffe 100644 --- a/include/bitlib/bit-iterator/bit_iterator.hpp +++ b/include/bitlib/bit-iterator/bit_iterator.hpp @@ -221,27 +221,20 @@ constexpr typename bit_iterator::pointer bit_iterator::opera template constexpr typename bit_iterator::reference bit_iterator::operator[](difference_type n) const { constexpr difference_type digits = binary_digits::value; - const difference_type sum = _position + n; - difference_type diff = sum / digits; - if (sum < 0 && diff * digits != sum) { - --diff; - } - return reference(*std::next(_current, diff), sum - diff * digits); + const difference_type sum = static_cast(_position) + n; + const difference_type diff = (sum - (n < 0) * (digits - 1)) / digits; + return reference(*std::next(_current, diff), static_cast(sum) % digits); } // -------------------------------------------------------------------------- // - - // ------------- BIT ITERATOR: INCREMENT AND DECREMENT OPERATORS ------------ // // Increments the bit iterator and returns it template constexpr bit_iterator& bit_iterator::operator++() { - constexpr size_type digits = binary_digits::value; - if (_position + 1 < digits) { - ++_position; - } else { + constexpr auto digits = binary_digits::value; + _position = (_position + 1) % digits; + if (0 == _position) { ++_current; - _position = 0; } return *this; } @@ -250,12 +243,10 @@ constexpr bit_iterator& bit_iterator::operator++() { template constexpr bit_iterator& bit_iterator::operator--() { constexpr size_type digits = binary_digits::value; - if (_position) { - --_position; - } else { + if (0 == _position) { --_current; - _position = digits - 1; } + _position = (_position - 1) % digits; return *this; } @@ -279,37 +270,31 @@ constexpr bit_iterator bit_iterator::operator--(int) { template constexpr bit_iterator bit_iterator::operator+(difference_type n) const { constexpr difference_type digits = binary_digits::value; - const difference_type sum = _position + n; - difference_type diff = sum / digits; - if (sum < 0 && diff * digits != sum) { - --diff; - } - return bit_iterator(std::next(_current, diff), sum - diff * digits); + const difference_type sum = static_cast(_position) + n; + const difference_type diff = (sum - (n < 0) * (digits - 1)) / digits; + const size_type new_pos = static_cast(sum) % static_cast(digits); + return bit_iterator(std::next(_current, diff), new_pos); } // Looks backward several bits and gets an iterator at this position template constexpr bit_iterator bit_iterator::operator-(difference_type n) const { constexpr difference_type digits = binary_digits::value; - const difference_type sum = _position - n; - difference_type diff = sum / digits; - if (sum < 0 && diff * digits != sum) { - --diff; - } - return bit_iterator(std::next(_current, diff), sum - diff * digits); + const difference_type sum = static_cast(_position) - n; + const difference_type diff = (sum - (n > 0) * (digits - 1)) / digits; + const size_type new_pos = static_cast(sum) % static_cast(digits); + return bit_iterator(std::next(_current, diff), new_pos); } // Increments the iterator by several bits and returns it template constexpr bit_iterator& bit_iterator::operator+=(difference_type n) { constexpr difference_type digits = binary_digits::value; - const difference_type sum = _position + n; - difference_type diff = sum / digits; - if (sum < 0 && diff * digits != sum) { - --diff; - } + const difference_type sum = static_cast(_position) + n; + const difference_type diff = (sum - (n < 0) * (digits - 1)) / digits; + const size_type new_pos = static_cast(sum) % static_cast(digits); _current = std::next(_current, diff); - _position = (sum - diff * digits); + _position = new_pos; return *this; } @@ -317,13 +302,11 @@ constexpr bit_iterator& bit_iterator::operator+=(difference_ template constexpr bit_iterator& bit_iterator::operator-=(difference_type n) { constexpr difference_type digits = binary_digits::value; - const difference_type sum = _position - n; - difference_type diff = sum / digits; - if (sum < 0 && diff * digits != sum) { - --diff; - } + const difference_type sum = static_cast(_position) - n; + const difference_type diff = (sum - (n > 0) * (digits - 1)) / digits; + const size_type new_pos = static_cast(sum) % static_cast(digits); _current = std::next(_current, diff); - _position = (sum - diff * digits); + _position = new_pos; return *this; } // -------------------------------------------------------------------------- // diff --git a/include/bitlib/bit-iterator/bit_word_pointer_adapter.hpp b/include/bitlib/bit-iterator/bit_word_pointer_adapter.hpp index f167db54..b82045dd 100644 --- a/include/bitlib/bit-iterator/bit_word_pointer_adapter.hpp +++ b/include/bitlib/bit-iterator/bit_word_pointer_adapter.hpp @@ -72,14 +72,11 @@ class bit_word_pointer_adapter { constexpr reference operator[](difference_type n) const noexcept { if constexpr (is_small_to_big) { - return reference(*std::next(_source, n*ratio)); + return reference(*std::next(_source, n * ratio)); } else { const difference_type sum = _index + n; - difference_type src_diff = sum / static_cast(ratio); + const difference_type src_diff = (sum - (n < 0) * static_cast(ratio - 1)) / static_cast(ratio); const size_type new_index = sum % ratio; - if (n < 0 && new_index > _index) { - --src_diff; - } return reference(*std::next(_source, src_diff), new_index); } } @@ -119,27 +116,21 @@ class bit_word_pointer_adapter { } constexpr bit_word_pointer_adapter operator+(difference_type n) const noexcept { if constexpr (is_small_to_big) { - return bit_word_pointer_adapter(std::next(_source, n*ratio)); + return bit_word_pointer_adapter(std::next(_source, n * ratio)); } else { const difference_type sum = _index + n; - difference_type src_diff = sum / static_cast(ratio); - const size_type new_index = sum % ratio; - if (n < 0 && new_index > _index) { - --src_diff; - } + const difference_type src_diff = (sum - (n < 0) * static_cast(ratio - 1)) / static_cast(ratio); + const size_type new_index = static_cast(sum) % ratio; return bit_word_pointer_adapter(std::next(_source, src_diff), new_index); } } constexpr bit_word_pointer_adapter operator-(difference_type n) const noexcept { if constexpr (is_small_to_big) { - return bit_word_pointer_adapter(std::next(_source, -n*ratio)); + return bit_word_pointer_adapter(std::next(_source, -n * ratio)); } else { const difference_type sum = _index - n; - difference_type src_diff = sum / static_cast(ratio); - const size_type new_index = sum % ratio; - if (n > 0 && new_index > _index) { - --src_diff; - } + const difference_type src_diff = (sum - (n > 0) * static_cast(ratio - 1)) / static_cast(ratio); + const size_type new_index = static_cast(sum) % ratio; return bit_word_pointer_adapter(std::next(_source, src_diff), new_index); } } @@ -148,11 +139,8 @@ class bit_word_pointer_adapter { _source = std::next(_source, n * ratio); } else { const difference_type sum = _index + n; - difference_type src_diff = sum / static_cast(ratio); - const size_type new_index = sum % ratio; - if (n < 0 && new_index > _index) { - --src_diff; - } + const difference_type src_diff = (sum - (n < 0) * static_cast(ratio - 1)) / static_cast(ratio); + const size_type new_index = static_cast(sum) % ratio; _source = std::next(_source, src_diff); _index = new_index; } @@ -163,11 +151,8 @@ class bit_word_pointer_adapter { _source = std::next(_source, -n * ratio); } else { const difference_type sum = _index - n; - difference_type src_diff = sum / static_cast(ratio); - const size_type new_index = sum % ratio; - if (n > 0 && new_index > _index) { - --src_diff; - } + const difference_type src_diff = (sum - (n > 0) * static_cast(ratio - 1)) / static_cast(ratio); + const size_type new_index = static_cast(sum) % ratio; _source = std::next(_source, src_diff); _index = new_index; } From 65d3132d9eb87c4cfd2a3748f4d6643469299113 Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Fri, 8 Aug 2025 17:59:29 +0000 Subject: [PATCH 84/85] Fix string conversion and many warnings (#29) * Explicitly cast inside _mask * Name clang format workflow job * Default is detail::uninitialized. Fix from_string * Manually promote short or char types * Remove some other pedantic conversion warnings * Simplify the types into bitblend * Remove unnecessary casts in bitblend calls * Improve correctness when using unsigned size_type * clang format * size_type back to size_t * clang format * More conversion warning reduction * No floats allowed * Squash more conversion warnings * Even more warnings * Annoying that promotion happens even for bitwise of same type * Narrowing in on double digit warnings * More warnings * Turn on Werror * Fix clang warnings * Add a single status check for all matrix jobs * Filter C/C++ sources for clang format check --- .github/workflows/clang_format.yaml | 5 +- .github/workflows/cmake-multi-platform.yml | 17 +- CMakePresets.json | 66 ++++- include/bitlib/bit-algorithms/accumulate.hpp | 12 +- .../bit-algorithms/bit_algorithm_details.hpp | 155 ++++-------- include/bitlib/bit-algorithms/copy.hpp | 4 +- .../bitlib/bit-algorithms/copy_backward.hpp | 60 +++-- include/bitlib/bit-algorithms/count.hpp | 15 +- include/bitlib/bit-algorithms/division.hpp | 1 + include/bitlib/bit-algorithms/equal.hpp | 2 +- .../bitlib/bit-algorithms/multiplication.hpp | 2 +- include/bitlib/bit-algorithms/rotate.hpp | 5 +- include/bitlib/bit-algorithms/shift.hpp | 235 +++++++++--------- .../bitlib/bit-algorithms/to_from_string.hpp | 30 +-- include/bitlib/bit-algorithms/transform.hpp | 4 +- .../bit-algorithms/transform_accumulate.hpp | 18 +- include/bitlib/bit-containers/bit_policy.hpp | 40 ++- include/bitlib/bit-containers/bit_span.hpp | 3 +- include/bitlib/bit-containers/bit_vector.hpp | 38 +-- include/bitlib/bit-iterator/bit_details.hpp | 104 ++++---- include/bitlib/bit-iterator/bit_iterator.hpp | 5 +- include/bitlib/bit-iterator/bit_reference.hpp | 2 +- test/src/test-iterator_adapter.cpp | 2 - test/src/test-to_from_string.cpp | 58 +++++ test/src/vector_test.cpp | 8 +- 25 files changed, 501 insertions(+), 390 deletions(-) diff --git a/.github/workflows/clang_format.yaml b/.github/workflows/clang_format.yaml index 59c0c12b..24a2e0b4 100644 --- a/.github/workflows/clang_format.yaml +++ b/.github/workflows/clang_format.yaml @@ -14,6 +14,7 @@ on: jobs: clang-format: + name: Clang Format Check runs-on: ubuntu-latest steps: @@ -39,10 +40,10 @@ jobs: run: | if [[ "${{ github.event_name }}" == "pull_request" ]]; then echo "Getting diff for pull request" - git diff origin/${{ github.base_ref }}...HEAD > diff.patch + git diff origin/${{ github.base_ref }}...HEAD -- '*.c' '*.cpp' '*.hpp' '*.cc' '*.cxx' '*.h' '*.hh' '*.hxx' > diff.patch else echo "Getting diff for commit" - git diff ${{ github.event.before }} ${{ github.event.after }} > diff.patch + git diff ${{ github.event.before }} ${{ github.event.after }} -- '*.c' '*.cpp' '*.hpp' '*.cc' '*.cxx' '*.h' '*.hh' '*.hxx' > diff.patch fi - name: Check formatting diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index b8264d73..94241aba 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -15,7 +15,6 @@ on: jobs: run-matrix: - name: Run CMake Build and Test Matrix uses: ./.github/workflows/cmake_build_and_test.yml strategy: # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. @@ -107,4 +106,18 @@ jobs: build_type: ${{ matrix.build_type || matrix.preset == 'benchmark' && 'Release' || 'Debug' }} head_ref: ${{ github.event.pull_request.head.ref }} base_ref: ${{ github.event.pull_request.base.ref }} - secrets: inherit \ No newline at end of file + secrets: inherit + status_check: + runs-on: ubuntu-latest + needs: run-matrix + if: ${{ always() }} + steps: + - name: Check for failed jobs + shell: bash + run: | + if [ -n "${{ needs.run-matrix.result }}" != "success" ]; then + echo "Some jobs failed. Please check the logs." + exit 1 + else + echo "All jobs completed successfully." + fi \ No newline at end of file diff --git a/CMakePresets.json b/CMakePresets.json index ec4a866e..ca824f0a 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -77,7 +77,7 @@ "name": "warnings_base", "hidden": true, "environment": { - "TARGET_FLAGS": "-Wall -Wextra -Wpedantic -Wold-style-cast -Wconversion -Wshadow -Wno-error" + "TARGET_FLAGS": "-Wall -Wextra -Wpedantic -Wshadow -Werror" } }, { @@ -628,6 +628,36 @@ "lhs": "${hostSystemName}", "rhs": "Linux" } + }, + { + "name": "warnings_linux_gcc_libstdcxx", + "configurePreset": "warnings_linux_gcc_libstdcxx", + "execution": { + "jobs": 1 + }, + "output": { + "verbosity": "verbose" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "warnings_linux_clang_libcxx", + "configurePreset": "warnings_linux_clang_libcxx", + "execution": { + "jobs": 1 + }, + "output": { + "verbosity": "verbose" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } } ], "workflowPresets": [ @@ -800,6 +830,40 @@ "name": "benchmark_linux_clang_libcxx" } ] + }, + { + "name": "warnings_linux_gcc_libstdcxx", + "steps": [ + { + "type": "configure", + "name": "warnings_linux_gcc_libstdcxx" + }, + { + "type": "build", + "name": "warnings_linux_gcc_libstdcxx" + }, + { + "type": "test", + "name": "warnings_linux_gcc_libstdcxx" + } + ] + }, + { + "name": "warnings_linux_clang_libcxx", + "steps": [ + { + "type": "configure", + "name": "warnings_linux_clang_libcxx" + }, + { + "type": "build", + "name": "warnings_linux_clang_libcxx" + }, + { + "type": "test", + "name": "warnings_linux_clang_libcxx" + } + ] } ] } diff --git a/include/bitlib/bit-algorithms/accumulate.hpp b/include/bitlib/bit-algorithms/accumulate.hpp index 62b77ada..e70c76d0 100644 --- a/include/bitlib/bit-algorithms/accumulate.hpp +++ b/include/bitlib/bit-algorithms/accumulate.hpp @@ -48,7 +48,7 @@ constexpr auto accumulate( } else { sub_digits = std::min(last.position(), total_bits_to_op); if (sub_digits != 0) { - advance(last, -sub_digits); + reverse(last, sub_digits); acc = binary_op_subword(std::move(acc), get_masked_word(last, sub_digits), sub_digits); } } @@ -63,7 +63,7 @@ constexpr auto accumulate( acc = binary_op(std::move(acc), get_word(first)); advance(first, digits); } else { - advance(last, -digits); + reverse(last, digits); acc = binary_op(std::move(acc), get_word(last)); } } @@ -71,7 +71,7 @@ constexpr auto accumulate( if constexpr (forward) { acc = binary_op_subword(std::move(acc), get_masked_word(first, remaining_bits_to_op), remaining_bits_to_op); } else { - advance(last, -remaining_bits_to_op); + reverse(last, remaining_bits_to_op); acc = binary_op_subword(std::move(acc), get_masked_word(last, remaining_bits_to_op), remaining_bits_to_op); } } @@ -106,7 +106,7 @@ constexpr auto accumulate_while( } else { sub_digits = std::min(last.position(), total_bits_to_op); if (sub_digits != 0) { - advance(last, -sub_digits); + reverse(last, sub_digits); std::tie(keep_going, acc) = binary_op_subword(std::move(acc), get_masked_word(first, sub_digits), sub_digits); } } @@ -125,7 +125,7 @@ constexpr auto accumulate_while( std::tie(keep_going, acc) = binary_op(std::move(acc), get_word(first)); advance(first, digits); } else { - advance(last, -digits); + reverse(last, digits); std::tie(keep_going, acc) = binary_op(std::move(acc), get_word(last)); } if (!keep_going) { @@ -136,7 +136,7 @@ constexpr auto accumulate_while( if constexpr (forward) { std::tie(std::ignore, acc) = binary_op_subword(std::move(acc), get_masked_word(first, remaining_bits_to_op), remaining_bits_to_op); } else { - advance(last, -remaining_bits_to_op); + reverse(last, remaining_bits_to_op); std::tie(std::ignore, acc) = binary_op_subword(std::move(acc), get_masked_word(last, remaining_bits_to_op), remaining_bits_to_op); } } diff --git a/include/bitlib/bit-algorithms/bit_algorithm_details.hpp b/include/bitlib/bit-algorithms/bit_algorithm_details.hpp index f23abea3..3895ecd8 100644 --- a/include/bitlib/bit-algorithms/bit_algorithm_details.hpp +++ b/include/bitlib/bit-algorithms/bit_algorithm_details.hpp @@ -22,44 +22,42 @@ namespace bit { // ========================================================================== // - - // -------------------------- Iterator Algorithms --------------------------- // // Returns the number of increments needed to get to last from first. // May be negative if last comes before first (Only when input is RAI) template typename bit_iterator::difference_type - distance(bit_iterator first, - bit_iterator last -) -{ - _assert_range_viability(first, last); - using word_type = typename bit_iterator::word_type; - using size_type = typename bit_iterator::size_type; - constexpr size_type digits = binary_digits::value; - return std::distance(first.base(), last.base())*digits - + (last.position() - first.position()); +distance(bit_iterator first, + bit_iterator last) { + _assert_range_viability(first, last); + using word_type = typename bit_iterator::word_type; + using difference_type = typename bit_iterator::difference_type; + constexpr difference_type digits = binary_digits::value; + return std::distance(first.base(), last.base()) * digits + + static_cast(last.position()) - + static_cast(first.position()); } // Increments the iterator n times. (If n is negative, the iterator is decremented n times) -template -void advance(bit_iterator& first, Distance n) -{ - first += n; +template +void advance(bit_iterator& first, typename bit_iterator::difference_type n) { + first += n; } -template +template +void reverse(InputIt& it, const T& n) { + ::std::advance(it, -static_cast>(n)); +} + +template bit_iterator next( - bit_iterator bit_it, - typename bit_iterator::difference_type n = 1 -) { - return bit_it + n; + bit_iterator bit_it, + typename bit_iterator::difference_type n = 1) { + return bit_it + n; } // -------------------------------------------------------------------------- // - - // --------------------------- Utility Functions ---------------------------- // // Returns distance(first, last) <= n @@ -106,10 +104,10 @@ constexpr bool is_within( template T get_word(const bit_iterator& first, size_t len = binary_digits::value) { using native_word_type = typename bit_iterator::word_type; - constexpr T digits = binary_digits::value; + constexpr size_t digits = binary_digits::value; assert(digits >= len); using non_const_T = std::remove_cvref_t; - non_const_T offset = digits - first.position(); + size_t offset = digits - first.position(); non_const_T ret_word = lsr(*first.base(), first.position()); // We've already assigned enough bits @@ -147,81 +145,14 @@ T get_masked_word(const bit_iterator& first, size_t len = binary_digits return get_word(first, len) & _mask(len); } -// Get next len bits beginning at start and store them in a word of type T -// If we reach `last` before we get len bits, break and return the current word -// bits_read will store the number of bits that we read. -//template -//T get_word(bit_iterator first, bit_iterator last, - //T& bits_read, T len=binary_digits::value - //) -//{ - //using native_word_type = typename bit_iterator::word_type; - //constexpr T native_digits = binary_digits::value; - //constexpr T ret_digits = binary_digits::value; - //assert(ret_digits >= len); - //bits_read = native_digits - first.position(); - //T ret_word = *first.base() >> first.position(); - - //// TODO vincent mentioned that we should aim for only 1 return function - //// per function. However I'm not sure how that can be accomplished here - //// without suffering a minor performance loss - - //// We have reached the last iterator - //if (first.base() == last.base()) { - //bits_read -= (native_digits - last.position()); - //return ret_word; - //} - //// We've already assigned enough bits - //if (len <= bits_read) { - //return ret_word; - //} - - //InputIt it = std::next(first.base()); - //len -= bits_read; - //// Fill up ret_word starting at bit [bits_read] using it - //// TODO define a mask and use the _bitblend that takes in the extra mask - //while (len > native_digits && it != last.base()) { - //ret_word = _bitblend( - //ret_word, - //static_cast(static_cast(*it) << bits_read), - //bits_read, - //native_digits - //); - //++it; - //bits_read += native_digits; - //len -= native_digits; - //} - - //// Assign remaining len bits of last word - //if (it == last.base()) { - //bits_read -= (native_digits - last.position()); - //ret_word = _bitblend( - //ret_word, - //static_cast(static_cast(*it) << bits_read), - //bits_read, - //last.position() - //); - //} else { - //ret_word = _bitblend( - //ret_word, - //static_cast(static_cast(*it) << bits_read), - //bits_read, - //len - //); - //} - //return ret_word; -//} - - // Writes len bits from src beginning at dstIt template void write_word(src_type src, bit_iterator dst_bit_it, - src_type len=binary_digits::value - ) -{ + size_t len = binary_digits::value) { using dst_type = typename bit_iterator::word_type; - constexpr dst_type dst_digits = binary_digits::value; - constexpr dst_type src_digits = binary_digits::value; + using size_type = typename bit_iterator::size_type; + constexpr size_type dst_digits = binary_digits::value; + constexpr size_type src_digits = binary_digits::value; if constexpr (dst_digits >= src_digits) { if (dst_bit_it.position() == 0 && len == dst_digits) { @@ -231,7 +162,7 @@ void write_word(src_type src, bit_iterator dst_bit_it, *dst_bit_it.base(), static_cast(src << dst_bit_it.position()), dst_bit_it.position(), - std::min( + std::min( dst_digits - dst_bit_it.position(), len)); if (len > dst_digits - dst_bit_it.position()) { @@ -270,26 +201,26 @@ void write_word(src_type src, bit_iterator dst_bit_it, _mask(len)); } } - return; + return; } - // Shifts the range [first, last) to the left by n, filling the empty // bits with 0 template RandomAccessIt word_shift_left(RandomAccessIt first, - RandomAccessIt last, - typename RandomAccessIt::difference_type n -) -{ - if (n <= 0) return last; - if (n >= distance(first, last)) return first; - RandomAccessIt mid = first + n; - auto ret = std::move(mid, last, first); - return ret; + RandomAccessIt last, + typename RandomAccessIt::difference_type n) { + if (n <= 0) { + return last; + } + if (n >= distance(first, last)) { + return first; + } + RandomAccessIt mid = first + n; + auto ret = std::move(mid, last, first); + return ret; } - // Shifts the range [first, right) to the left by n, filling the empty // bits with 0 // NOT OPTIMIZED. Will be replaced with std::shift eventually. @@ -419,9 +350,7 @@ typename bit_iterator::word_type _padded_read(bit_iterator first, } // -------------------------------------------------------------------------- // - - // ========================================================================== // -} // namespace bit -#endif // _BIT_ALGORITHM_DETAILS_HPP_INCLUDED +} // namespace bit +#endif // _BIT_ALGORITHM_DETAILS_HPP_INCLUDED // ========================================================================== // diff --git a/include/bitlib/bit-algorithms/copy.hpp b/include/bitlib/bit-algorithms/copy.hpp index adc55f5b..2cfe8230 100644 --- a/include/bitlib/bit-algorithms/copy.hpp +++ b/include/bitlib/bit-algorithms/copy.hpp @@ -89,8 +89,8 @@ struct copy_impl { static_cast( get_word(first, partial_bits_to_copy) << static_cast(d_first.position())), - static_cast(d_first.position()), - static_cast(partial_bits_to_copy)); + d_first.position(), + partial_bits_to_copy); remaining_bits_to_copy -= partial_bits_to_copy; advance(first, partial_bits_to_copy); it++; diff --git a/include/bitlib/bit-algorithms/copy_backward.hpp b/include/bitlib/bit-algorithms/copy_backward.hpp index 6c255a08..cd463b67 100644 --- a/include/bitlib/bit-algorithms/copy_backward.hpp +++ b/include/bitlib/bit-algorithms/copy_backward.hpp @@ -61,46 +61,44 @@ constexpr bit_iterator copy_backward(bit_iterator(last - partial_bits_to_copy, partial_bits_to_copy) << (d_last.position() - partial_bits_to_copy)), d_last.position() - partial_bits_to_copy, - static_cast(partial_bits_to_copy)); + partial_bits_to_copy); remaining_bits_to_copy -= partial_bits_to_copy; - advance(last, -partial_bits_to_copy); + reverse(last, partial_bits_to_copy); } if (remaining_bits_to_copy > 0) { - const bool is_last_aligned = last.position() == 0; - //size_type words_to_copy = ::std::ceil(remaining_bits_to_copy / static_cast(digits)); - // d_last will be aligned at this point - if (is_last_aligned && remaining_bits_to_copy >= digits) { - auto N = ::std::distance(first.base(), last.base()) - 1; - it = ::std::copy_backward(first.base() + 1, last.base(), it); - last -= digits * N; - remaining_bits_to_copy -= digits * N; - } else { - // TODO benchmark if its faster to ::std::copy the entire range then shift - while (remaining_bits_to_copy >= digits) { - *(--it) = get_word(last - digits, digits); - remaining_bits_to_copy -= digits; - advance(last, -digits); - } - } - if (remaining_bits_to_copy > 0) { - it--; - *it = _bitblend( - *it, - static_cast(get_word(last - remaining_bits_to_copy, remaining_bits_to_copy) - << (digits - remaining_bits_to_copy)), - digits - remaining_bits_to_copy, - remaining_bits_to_copy); - remaining_bits_to_copy = 0; + const bool is_last_aligned = last.position() == 0; + //size_type words_to_copy = ::std::ceil(remaining_bits_to_copy / static_cast(digits)); + // d_last will be aligned at this point + if (is_last_aligned && remaining_bits_to_copy >= digits) { + auto N = ::std::distance(first.base(), last.base()) - 1; + it = ::std::copy_backward(first.base() + 1, last.base(), it); + last -= digits * N; + remaining_bits_to_copy -= digits * N; + } else { + // TODO benchmark if its faster to ::std::copy the entire range then shift + while (remaining_bits_to_copy >= digits) { + *(--it) = get_word(last - digits, digits); + remaining_bits_to_copy -= digits; + reverse(last, digits); } + } + if (remaining_bits_to_copy > 0) { + it--; + *it = _bitblend( + *it, + static_cast(get_word(last - remaining_bits_to_copy, remaining_bits_to_copy) + << (digits - remaining_bits_to_copy)), + digits - remaining_bits_to_copy, + remaining_bits_to_copy); + remaining_bits_to_copy = 0; + } } return d_last - total_bits_to_copy; } - - // ========================================================================== // -} // namespace bit +} // namespace bit -#endif // _COPY_BACKWARD_HPP_INCLUDED +#endif // _COPY_BACKWARD_HPP_INCLUDED // ========================================================================== // diff --git a/include/bitlib/bit-algorithms/count.hpp b/include/bitlib/bit-algorithms/count.hpp index 4bcc5b49..31db939f 100644 --- a/include/bitlib/bit-algorithms/count.hpp +++ b/include/bitlib/bit-algorithms/count.hpp @@ -100,14 +100,17 @@ count( result += popcnt(&*it, words); } } - if (last.position() != 0) { - word_type last_value = *last.base() << (digits - last.position()); - result += std::popcount(static_cast>(last_value)); - } - // Computation when bits belong to the same underlying word + if (last.position() != 0) { + word_type last_value = static_cast(*last.base() << (digits - last.position())); + result += std::popcount(static_cast>(last_value)); + } + // Computation when bits belong to the same underlying word } else { result = std::popcount(static_cast>( - _bextr(*first.base(), first.position(), last.position() - first.position()))); + _bextr( + *first.base(), + first.position(), + last.position() - first.position()))); } // Negates when the number of zero bits is requested diff --git a/include/bitlib/bit-algorithms/division.hpp b/include/bitlib/bit-algorithms/division.hpp index 48f5d2a1..d7cfa28b 100644 --- a/include/bitlib/bit-algorithms/division.hpp +++ b/include/bitlib/bit-algorithms/division.hpp @@ -37,6 +37,7 @@ constexpr typename bit_iterator::word_type division( const word_type& remainder, const word_type& word, size_t bits = bitsof()) { + static_cast(bits); // Suppress unused variable warning word_type next_remainder; word_type result_word = _divx(remainder, word, static_cast(integral_operand), &next_remainder); return std::make_pair(result_word, next_remainder); diff --git a/include/bitlib/bit-algorithms/equal.hpp b/include/bitlib/bit-algorithms/equal.hpp index 60642d8e..0b3c0e9f 100644 --- a/include/bitlib/bit-algorithms/equal.hpp +++ b/include/bitlib/bit-algorithms/equal.hpp @@ -64,7 +64,7 @@ struct equal_impl { const size_type partial_bits_to_check = ::std::min( remaining_bits_to_check, digits - d_first.position()); - const word_type mask = _mask(partial_bits_to_check) << d_first.position(); + const word_type mask = _mask(partial_bits_to_check, d_first.position()); const word_type comp = static_cast( get_word(first, partial_bits_to_check) << d_first.position()); diff --git a/include/bitlib/bit-algorithms/multiplication.hpp b/include/bitlib/bit-algorithms/multiplication.hpp index 01b32340..20b246a1 100644 --- a/include/bitlib/bit-algorithms/multiplication.hpp +++ b/include/bitlib/bit-algorithms/multiplication.hpp @@ -35,7 +35,7 @@ constexpr typename bit_iterator::word_type multiplication( [&carry, integral_operand](auto word, auto bits = bitsof()) -> word_type { word_type result_word = (carry + _mulx(static_cast(integral_operand), word, &carry)); if (bits < bitsof()) { - carry = (carry << (bitsof() - bits)) | lsr(result_word, bits); + carry = static_cast((carry << (bitsof() - bits)) | lsr(result_word, bits)); } return result_word; }); diff --git a/include/bitlib/bit-algorithms/rotate.hpp b/include/bitlib/bit-algorithms/rotate.hpp index 1d5ecb28..e4e81c3e 100644 --- a/include/bitlib/bit-algorithms/rotate.hpp +++ b/include/bitlib/bit-algorithms/rotate.hpp @@ -219,15 +219,14 @@ bit_iterator rotate( size_type p = last_pos - n_first.position(); size_type d = last_pos - first.position(); - word_type mask = _mask(d) << first.position(); + word_type mask = _mask(d, first.position()); word_type rotated = *first.base() & mask; rotated = static_cast(lsr(rotated, k)) | static_cast(rotated << p); *first.base() = _bitblend( *first.base(), rotated, first.position(), - d - ); + d); return std::next(first, p); } } diff --git a/include/bitlib/bit-algorithms/shift.hpp b/include/bitlib/bit-algorithms/shift.hpp index ab36dd92..883bf2a4 100644 --- a/include/bitlib/bit-algorithms/shift.hpp +++ b/include/bitlib/bit-algorithms/shift.hpp @@ -87,133 +87,133 @@ bit_iterator shift_left( ); return first + d - n; } - // clang-format on + // clang-format on - // Triggered if all remaining bits can fit in a word - if (d - n <= digits) { - word_type new_word = get_word(middle, d - n); - write_word(new_word, first, d - n); - return first + d - n; - } + // Triggered if all remaining bits can fit in a word + if (d - n <= static_cast(digits)) { + word_type new_word = get_word(middle, d - n); + write_word(new_word, first, d - n); + return first + d - n; + } - // Align first - if (!is_first_aligned) { - if (first.position() >= middle.position()) { - *first.base() = _bitblend( - *first.base(), - static_cast((*middle.base()) << (first.position() - middle.position())), - first.position(), - digits - first.position()); - } else { - const int n1 = digits - middle.position(); - const int n2 = digits - first.position() - n1; - *first.base() = _bitblend( - *first.base(), - lsr(*middle.base(), (middle.position() - first.position())), - first.position(), - n1); - *first.base() = _bitblend( - *first.base(), - static_cast((*std::next(middle.base())) << (digits - n2)), - first.position() + n1, - n2); + // Align first + if (!is_first_aligned) { + if (first.position() >= middle.position()) { + *first.base() = _bitblend( + *first.base(), + static_cast((*middle.base()) << (first.position() - middle.position())), + first.position(), + digits - first.position()); + } else { + const size_t n1 = digits - middle.position(); + const size_t n2 = digits - first.position() - n1; + *first.base() = _bitblend( + *first.base(), + lsr(*middle.base(), (middle.position() - first.position())), + first.position(), + n1); + *first.base() = _bitblend( + *first.base(), + static_cast((*std::next(middle.base()) << (digits - n2))), + first.position() + n1, + n2); + } + const int shifted = std::min(d - n, (digits - first.position())); + first += shifted; + middle += shifted; } - const int shifted = std::min(d - n, (digits - first.position())); - first += shifted; - middle += shifted; - } - if (middle.base() == last.base()) { - const int bits_left = last.position() - middle.position(); - if (bits_left > 0) { - *first.base() = _bitblend( - *first.base(), - lsr(*middle.base(), middle.position()), - 0, - bits_left); - first += bits_left; + if (middle.base() == last.base()) { + const int bits_left = last.position() - middle.position(); + if (bits_left > 0) { + *first.base() = _bitblend( + *first.base(), + lsr(*middle.base(), middle.position()), + 0, + bits_left); + first += bits_left; + } + // https://en.cppreference.com/w/cpp/algorithm/shift + // "Elements that are in the original range but not the new range + // are left in a valid but unspecified state." + // + //bit::fill(first, last, bit::bit0); + return first; } - // https://en.cppreference.com/w/cpp/algorithm/shift - // "Elements that are in the original range but not the new range - // are left in a valid but unspecified state." - // - //bit::fill(first, last, bit::bit0); - return first; - } - // More initialization - d = bit::distance(first, last); - const size_type word_shifts = n / digits; - const size_type offset = middle.position(); - - // At this point, first is aligned - if (offset == 0) { - first = bit::bit_iterator( - STD_SHIFT_LEFT(first.base(), - last.base(), - word_shifts), - 0); - if (!is_last_aligned) { - write_word(*last.base(), first, last.position()); - first += last.position(); + // More initialization + d = bit::distance(first, last); + const size_type word_shifts = n / digits; + const size_type offset = middle.position(); + + // At this point, first is aligned + if (offset == 0) { + first = bit::bit_iterator( + STD_SHIFT_LEFT(first.base(), + last.base(), + word_shifts), + 0); + if (!is_last_aligned) { + write_word(*last.base(), first, last.position()); + first += last.position(); + } + // https://en.cppreference.com/w/cpp/algorithm/shift + // "Elements that are in the original range but not the new range + // are left in a valid but unspecified state." + // + //bit::fill(first, last, bit::bit0); + return first; } - // https://en.cppreference.com/w/cpp/algorithm/shift - // "Elements that are in the original range but not the new range - // are left in a valid but unspecified state." - // - //bit::fill(first, last, bit::bit0); - return first; - } - // Shift bit sequence to the lsb + // Shift bit sequence to the lsb #ifdef BITLIB_HWY - // Align to 64 bit boundary - while (std::next(middle.base()) < last.base() && !is_aligned(&*first.base(), 64)) { - *first.base() = _shrd(*middle.base(), *std::next(middle.base()), offset); - first += digits; - middle += digits; - } + // Align to 64 bit boundary + while (std::next(middle.base()) < last.base() && !is_aligned(&*first.base(), 64)) { + *first.base() = _shrd(*middle.base(), *std::next(middle.base()), offset); + first += digits; + middle += digits; + } - const hn::ScalableTag d_tag; - while (std::distance(middle.base(), last.base()) >= hn::Lanes(d_tag) + 10 + !is_last_aligned) { - const auto v = hn::ShiftRightSame(hn::LoadU(d_tag, &*middle.base()), offset); - const auto v_plus1 = hn::ShiftLeftSame(hn::LoadU(d_tag, &*(middle.base() + 1)), digits - offset); - hn::Store(v | v_plus1, d_tag, &*first.base()); - first += hn::Lanes(d_tag) * digits; - middle += hn::Lanes(d_tag) * digits; - } + const hn::ScalableTag d_tag; + while (std::distance(middle.base(), last.base()) >= hn::Lanes(d_tag) + 10 + !is_last_aligned) { + const auto v = hn::ShiftRightSame(hn::LoadU(d_tag, &*middle.base()), offset); + const auto v_plus1 = hn::ShiftLeftSame(hn::LoadU(d_tag, &*(middle.base() + 1)), digits - offset); + hn::Store(v | v_plus1, d_tag, &*first.base()); + first += hn::Lanes(d_tag) * digits; + middle += hn::Lanes(d_tag) * digits; + } #endif - auto first_base = first.base(); - auto middle_base = middle.base(); - - while (std::next(middle_base) < last.base()) { - *first_base = _shrd(*middle_base, *std::next(middle_base), offset); - first_base++; - middle_base++; - ; - } - first = bit_iterator(first_base, 0); - middle = bit_iterator(middle_base, middle.position()); + auto first_base = first.base(); + auto middle_base = middle.base(); + + while (std::next(middle_base) < last.base()) { + *first_base = _shrd(*middle_base, *std::next(middle_base), offset); + first_base++; + middle_base++; + ; + } + first = bit_iterator(first_base, 0); + middle = bit_iterator(middle_base, middle.position()); - // If middle is now penultimate word - if (std::next(middle.base()) == last.base()) { - *first.base() = _bitblend( - *first.base(), - lsr(*middle.base(), offset), - 0, - digits - offset); - first += digits - offset; - middle += digits - offset; - } + // If middle is now penultimate word + if (std::next(middle.base()) == last.base()) { + *first.base() = _bitblend( + *first.base(), + lsr(*middle.base(), offset), + 0, + digits - offset); + first += digits - offset; + middle += digits - offset; + } - if (!is_last_aligned) { - const difference_type bits_left = last.position() - middle.position(); - const word_type new_word = get_word(middle, bits_left); - write_word(new_word, first, bits_left); - first += bits_left; - } + if (!is_last_aligned) { + const difference_type bits_left = last.position() - middle.position(); + const word_type new_word = get_word(middle, bits_left); + write_word(new_word, first, bits_left); + first += bits_left; + } - //bit::fill(first, last, bit::bit0); - return first; + //bit::fill(first, last, bit::bit0); + return first; } template @@ -224,6 +224,7 @@ bit_iterator shift_right( // Types and constants using word_type = typename bit_iterator::word_type; using size_type = typename bit_iterator::size_type; + using difference_type = typename bit_iterator::difference_type; // Initialization const bool is_last_aligned = last.position() == 0; @@ -246,8 +247,8 @@ bit_iterator shift_right( static_cast( (*first.base() & (static_cast(-1) << first.position())) << n), - static_cast(first.position()), - static_cast((is_last_aligned ? digits : last.position()) - first.position())); + first.position(), + ((is_last_aligned ? digits : last.position()) - first.position())); return first + n; } @@ -293,7 +294,7 @@ bit_iterator shift_right( return first + n; } - if (bit::distance(first, middle) >= digits) { + if (bit::distance(first, middle) >= static_cast(digits)) { #ifdef BITLIB_HWY // Align to 64 bit boundary const hn::ScalableTag d; diff --git a/include/bitlib/bit-algorithms/to_from_string.hpp b/include/bitlib/bit-algorithms/to_from_string.hpp index d3f04d7e..e2d5eb1c 100644 --- a/include/bitlib/bit-algorithms/to_from_string.hpp +++ b/include/bitlib/bit-algorithms/to_from_string.hpp @@ -156,13 +156,12 @@ constexpr CharIt to_string( policy::AccumulateNoInitialSubword{}, bit_first, bit_last, str_last, [meta, base_bits, base_digits, str_first](CharIt cursor, auto word, const size_t bits = bitsof()) { - const int characters = ((bits + base_bits - 1) / base_bits); - for (int i = characters - 1; i >= 0; i--) { + for (size_t i = 0; i < ((bits + base_bits - 1) / base_bits); i++) { if (cursor == str_first) { return std::make_pair(false, cursor); } *(--cursor) = base_digits[word & (meta.base - 1)]; - word >>= base_bits; + word >>= static_cast(base_bits); } return std::make_pair(cursor != str_first, cursor); }); @@ -203,18 +202,18 @@ constexpr size_t estimate_length( if (std::has_single_bit(base)) { const auto base_bits = std::bit_width(base - 1); - int skip_leading_bits = str_sign_extend_zeros ? 0 : count_msb(first, last, bit0); + size_t skip_leading_bits = str_sign_extend_zeros ? 0 : count_msb(first, last, bit0); - int str_len = (distance(first, last) - skip_leading_bits); + size_t str_len = (distance(first, last) - skip_leading_bits); str_len = (str_len + base_bits - 1) / base_bits; // Round up to nearest base digit - return static_cast(std::max(1, str_len)); + return static_cast(std::max(static_cast(1), str_len)); } else { const uint32_t LOG2BASE = static_cast(std::ceil(static_cast(1 << 16) / std::logbf(static_cast(base)))); - int skip_leading_bits = str_sign_extend_zeros ? 0 : count_msb(first, last, bit0); + size_t skip_leading_bits = str_sign_extend_zeros ? 0 : count_msb(first, last, bit0); const auto bits = distance(first, last) - skip_leading_bits; const auto fixed_point = (bits * LOG2BASE); - const auto max_len = (fixed_point >> 16) + ((fixed_point & ((1 << 16) - 1)) != 0); - return static_cast(std::max(max_len, static_cast(1))); + const size_t max_len = (fixed_point >> 16) + ((fixed_point & ((1 << 16) - 1)) != 0); + return static_cast(std::max(static_cast(1), max_len)); } } @@ -300,7 +299,7 @@ constexpr void from_string( for (; (bits < bitsof()) && (cursor >= 0); cursor--) { char c = str_first[cursor]; // TODO: This should be a policy - if (c >= base_from_digits.size()) { + if (static_cast(c) >= base_from_digits.size()) { continue; } auto digit = base_from_digits[c]; @@ -318,11 +317,13 @@ constexpr void from_string( } else if ((store_bits > bits) && (cursor < 0)) { const bit_iterator p_integral(&work); bit_it = ::bit::copy(p_integral, p_integral + bits, bit_it); - Policy::extension::template from_integral( - work, bit_it, bit_last); + // TODO: policy + ::bit::fill(bit_it, bit_last, meta.is_signed ? bit_it[-1] : bit0); // Clear the rest + return; } else if (store_bits >= bits) { const bit_iterator p_integral(&work); bit_it = ::bit::copy(p_integral, p_integral + bits, bit_it); + store_bits -= bits; } } } else { @@ -331,14 +332,13 @@ constexpr void from_string( } using word_type = typename bit_iterator::word_type; std::vector vec; - size_t store_bits = distance(bit_first, bit_last); // TODO: template with uninitialized_t ::bit::fill(bit_first, bit_last, bit0); // Clear the bits first CharIt cursor = str_first; while (cursor != str_last) { - unsigned char c = (*cursor - '0'); + unsigned char c = static_cast(*cursor - '0'); if (c <= 9) { auto overflow_mult = ::bit::multiplication(bit_first, bit_last, word_type{10}); auto overflow_add = ::bit::addition(bit_first, bit_last, c); @@ -380,7 +380,7 @@ constexpr std::vector from_string( for (; (bits < bitsof()) && (last >= first); last--) { char c = *last; // TODO: This should be a policy - if (c >= base_from_digits.size()) { + if (static_cast(c) >= base_from_digits.size()) { continue; } auto digit = base_from_digits[c]; diff --git a/include/bitlib/bit-algorithms/transform.hpp b/include/bitlib/bit-algorithms/transform.hpp index e343ffc3..12a857c9 100644 --- a/include/bitlib/bit-algorithms/transform.hpp +++ b/include/bitlib/bit-algorithms/transform.hpp @@ -191,8 +191,8 @@ constexpr bit_iterator transform( static_cast( get_masked_word(first2, partial_bits_to_op) << static_cast(d_first.position()))), - static_cast(d_first.position()), - static_cast(partial_bits_to_op)); + d_first.position(), + partial_bits_to_op); remaining_bits_to_op -= partial_bits_to_op; advance(first1, partial_bits_to_op); advance(first2, partial_bits_to_op); diff --git a/include/bitlib/bit-algorithms/transform_accumulate.hpp b/include/bitlib/bit-algorithms/transform_accumulate.hpp index 80b7ef34..445fb362 100644 --- a/include/bitlib/bit-algorithms/transform_accumulate.hpp +++ b/include/bitlib/bit-algorithms/transform_accumulate.hpp @@ -58,8 +58,8 @@ constexpr auto transform_accumulate( *d_it = _bitblend( *d_it, word, - static_cast(d_first.position()), - static_cast(partial_bits_to_op)); + d_first.position(), + partial_bits_to_op); total_bits_to_op -= partial_bits_to_op; advance(first, partial_bits_to_op); advance(d_it, 1); @@ -71,14 +71,14 @@ constexpr auto transform_accumulate( total_bits_to_op, d_last.position()); if (partial_bits_to_op != 0) { - advance(last, -partial_bits_to_op); + reverse(last, partial_bits_to_op); word_type word; std::tie(word, acc) = binary_op_subword(std::move(acc), get_masked_word(last, partial_bits_to_op), partial_bits_to_op); *d_it = _bitblend( *d_it, word, - static_cast(d_first.position()), - static_cast(partial_bits_to_op)); + d_first.position(), + partial_bits_to_op); total_bits_to_op -= partial_bits_to_op; } } @@ -96,8 +96,8 @@ constexpr auto transform_accumulate( advance(first, digits); std::advance(d_it, 1); } else { - advance(last, -digits); - std::advance(d_it, -1); + reverse(last, digits); + reverse(d_it, 1); word_type word; std::tie(word, acc) = binary_op(std::move(acc), get_word(last)); *d_it = word; @@ -114,8 +114,8 @@ constexpr auto transform_accumulate( remaining_bits_to_op); } else { - advance(last, -remaining_bits_to_op); - std::advance(d_it, -1); + reverse(last, remaining_bits_to_op); + reverse(d_it, 1); word_type word; std::tie(word, acc) = binary_op_subword(std::move(acc), get_masked_word(last, remaining_bits_to_op), remaining_bits_to_op); *d_it = _bitblend( diff --git a/include/bitlib/bit-containers/bit_policy.hpp b/include/bitlib/bit-containers/bit_policy.hpp index 306d175b..0a4d31f7 100644 --- a/include/bitlib/bit-containers/bit_policy.hpp +++ b/include/bitlib/bit-containers/bit_policy.hpp @@ -64,6 +64,7 @@ struct truncate { struct sign_extend { template constexpr static void to_integral( + detail::initialized_t, const bit_iterator& first, const bit_iterator& last, U& integral) noexcept { @@ -84,7 +85,8 @@ struct sign_extend { } template - constexpr static void to_integral(const bit_sized_range auto& value, U& integral) noexcept { + constexpr static void to_integral( + detail::initialized_t, const bit_sized_range auto& value, U& integral) noexcept { bit_pointer integral_begin(&integral); if constexpr (N == std::dynamic_extent) { if constexpr (std::is_signed_v) { @@ -139,8 +141,22 @@ struct sign_extend { } } + template + constexpr static void to_integral(const bit_sized_range auto& value, U& integral) noexcept { + return to_integral(detail::uninitialized, value, integral); + } + + template + constexpr static void to_integral( + const bit_iterator& first, + const bit_iterator& last, + U& integral) noexcept { + return to_integral(detail::uninitialized, first, last, integral); + } + template constexpr static void from_integral( + detail::initialized_t, const U& integral, const bit_iterator& first, const bit_iterator& last) noexcept { @@ -158,10 +174,6 @@ struct sign_extend { } } } - template - constexpr static void from_integral(const U& integral, bit_sized_range auto& value) noexcept { - from_integral(integral, value.begin(), value.end()); - } template constexpr static void from_integral( @@ -176,6 +188,14 @@ struct sign_extend { } } + template + constexpr static void from_integral( + const U& integral, + const bit_iterator& first, + const bit_iterator& last) noexcept { + from_integral(detail::uninitialized, integral, first, last); + } + template constexpr static void from_integral( detail::uninitialized_t, @@ -183,6 +203,16 @@ struct sign_extend { bit_sized_range auto& value) noexcept { from_integral(detail::uninitialized, integral, value.begin(), value.end()); } + + template + constexpr static void from_integral(detail::initialized_t, const U& integral, bit_sized_range auto& value) noexcept { + from_integral(detail::initialized, integral, value.begin(), value.end()); + } + + template + constexpr static void from_integral(const U& integral, bit_sized_range auto& value) noexcept { + from_integral(detail::uninitialized, integral, value.begin(), value.end()); + } }; } // namespace bit::policy diff --git a/include/bitlib/bit-containers/bit_span.hpp b/include/bitlib/bit-containers/bit_span.hpp index 7eaf466b..cd4b61eb 100644 --- a/include/bitlib/bit-containers/bit_span.hpp +++ b/include/bitlib/bit-containers/bit_span.hpp @@ -335,7 +335,8 @@ template constexpr bit_span bit_span::last(size_type offset) const noexcept requires(Extent == std::dynamic_extent) { + return bit_span(begin() + size() - offset); } -} // namespace bit +} // namespace bit #endif diff --git a/include/bitlib/bit-containers/bit_vector.hpp b/include/bitlib/bit-containers/bit_vector.hpp index 78fa10e8..bc8450bf 100644 --- a/include/bitlib/bit-containers/bit_vector.hpp +++ b/include/bitlib/bit-containers/bit_vector.hpp @@ -42,7 +42,7 @@ class bit_vector { // TODO are privates always inlined? // @brief Get the number of words needed to represet num_bits bits - constexpr uint64_t word_count(unsigned int num_bits) { + constexpr uint64_t word_count(size_t num_bits) { return ((num_bits + digits - 1) / digits); } @@ -531,26 +531,26 @@ bit_vector::insert( } // TODO should use std::insert to maintain the constant amortized time. -template +template constexpr typename bit_vector::iterator bit_vector::insert( - const_iterator pos, - size_type count, - const bit_vector::value_type& value) { - const auto d = distance(cbegin(), pos); - if (count == 0) { - return begin() + d; - } - const float bits_available = word_vector.size() * digits; - const bool need_to_add = length_ + count > bits_available; - if (need_to_add) { - const auto words_to_add = word_count(length_ + count - bits_available); - word_vector.resize(word_vector.size() + words_to_add); - } - length_ += count; - shift_right(begin() + d, begin() + length_, count); - fill(begin() + d, begin() + d + count, value); + const_iterator pos, + size_type count, + const bit_vector::value_type& value) { + const auto d = distance(cbegin(), pos); + if (count == 0) { return begin() + d; + } + const size_t bits_available = word_vector.size() * digits; + const bool need_to_add = length_ + count > bits_available; + if (need_to_add) { + const auto words_to_add = word_count(length_ + count - bits_available); + word_vector.resize(word_vector.size() + words_to_add); + } + length_ += count; + shift_right(begin() + d, begin() + length_, count); + fill(begin() + d, begin() + d + count, value); + return begin() + d; } template @@ -565,7 +565,7 @@ bit_vector::insert( if (count == 0) { return begin() + d; } - const float bits_available = word_vector.size() * digits; + const size_t bits_available = word_vector.size() * digits; const auto need_to_add = length_ + count > bits_available; if (need_to_add) { const auto words_to_add = word_count(length_ + count - bits_available); diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index dda2b30b..664ed475 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -205,9 +205,9 @@ constexpr bool _assert_range_viability(Iterator first, Iterator last); // Bit field extraction template -constexpr T _bextr(T src, T start, T len) noexcept; +constexpr T _bextr(T src, size_t start, size_t len) noexcept; template -constexpr T _bextr(T src, T start, T len, X...) noexcept; +constexpr T _bextr(T src, size_t start, size_t len, X...) noexcept; // Bit swap template @@ -227,11 +227,11 @@ constexpr void _bitexch(T& src0, T& src1, S start0, S start1, S len) noexcept; // Double precision shift left template -constexpr T _shld(T dst, T src, T cnt) noexcept; +constexpr T _shld(const T& dst, const T& src, const size_t& cnt) noexcept; // Double precision shift right template -constexpr T _shrd(T dst, T src, T cnt) noexcept; +constexpr T _shrd(const T& dst, const T& src, const size_t& cnt) noexcept; // Multiword multiply template @@ -250,7 +250,7 @@ Logical shift right template constexpr T lsr(const T val, const size_type shift) { #ifdef BITLIB_DETECT_UNDEFINED_SHIFT - assert(shift < bitsof()); + assert(static_cast(shift) < bitsof()); #endif return static_cast(static_cast>(val) >> shift); } @@ -263,7 +263,7 @@ constexpr exact_floor_integral_t lsr(const T val, const size_type shift) { static_assert(!std::is_same_v, void>, "Type T must be convertible to an integral type"); #ifdef BITLIB_DETECT_UNDEFINED_SHIFT - assert(shift < bitsof>()); + assert(static_cast(shift) < bitsof>()); #endif return static_cast>(static_cast>>(val) >> shift); } @@ -274,7 +274,7 @@ Logical shift left template constexpr T lsl(const T val, const size_type shift) { #ifdef BITLIB_DETECT_UNDEFINED_SHIFT - assert(shift < bitsof()); + assert(static_cast(shift) < bitsof()); #endif return static_cast(static_cast>(val) << shift); } @@ -287,7 +287,7 @@ constexpr exact_floor_integral_t lsl(const T val, const size_type shift) { static_assert(!std::is_same_v, void>, "Type T must be convertible to an integral type"); #ifdef BITLIB_DETECT_UNDEFINED_SHIFT - assert(shift < bitsof>()); + assert(static_cast(shift) < bitsof>()); #endif return static_cast>(static_cast>>(val) << shift); } @@ -296,21 +296,37 @@ enum class _mask_len { unknown, in_range }; +enum class _mask_start { + unknown, + in_range +}; template constexpr T _mask(const size_type len) { - constexpr std::make_unsigned_t one = std::make_unsigned_t(1); + using unsigned_t = std::make_unsigned_t; + constexpr unsigned_t one = unsigned_t(1); if constexpr (len_in_range != _mask_len::unknown) { #ifdef BITLIB_DETECT_UNDEFINED_SHIFT - assert(len < bitsof()); + assert(static_cast(len) < bitsof()); #endif - return static_cast((one << len) - one); + return static_cast((one << static_cast(len)) - one); } else { // The digits_mask is solely here to prevent Undefined Sanitizer // complaining about shift of len >= digits // Note: on -O1 the (len & digits_mask) is optimized to simply (len) - constexpr std::make_unsigned_t digits_mask = bitsof() - one; - return static_cast((one << (len & digits_mask)) * (len < bitsof()) - one); + constexpr unsigned_t digits_mask = static_cast(bitsof()) - one; + return static_cast((one << (static_cast(len) & digits_mask)) * static_cast(len < bitsof()) - one); + } +} +template +constexpr T _mask(const size_type len, const size_type start) { + if constexpr (start_in_range != _mask_start::unknown) { + return static_cast(_mask(len) << start); + } else { + return static_cast((_mask(len) << start) * (start < bitsof())); } } @@ -331,9 +347,9 @@ constexpr bool _assert_range_viability(Iterator first, Iterator last) { // ------- IMPLEMENTATION DETAILS: INSTRUCTIONS: BIT FIELD EXTRACTION ------- // // Extacts to lsbs a field of contiguous bits with compiler intrinsics template -constexpr T _bextr(T src, T start, T len) noexcept { +constexpr T _bextr(T src, size_t start, size_t len) noexcept { static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; + constexpr size_t digits = binary_digits::value; T dst = T(); if (digits <= std::numeric_limits::digits) { dst = __builtin_ia32_bextr_u32(src, start, len); @@ -347,12 +363,10 @@ constexpr T _bextr(T src, T start, T len) noexcept { // Extacts to lsbs a field of contiguous bits without compiler intrinsics template -constexpr T _bextr(T src, T start, T len, X...) noexcept { +constexpr T _bextr(T src, size_t start, size_t len, X...) noexcept { static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - constexpr T one = 1; - const T msk = (one << len) * (len < digits) - one; - return (lsr(src, start)) & msk * (start < digits); + const T msk = _mask(len); + return (lsr(src, start))&msk; } // -------------------------------------------------------------------------- // @@ -394,7 +408,7 @@ constexpr T _bitswap(T src) noexcept { static_assert(binary_digits::value, ""); constexpr T cnt = N >> 1; constexpr T msk = _bitswap(); - src = ((lsr(src, cnt)) & msk) | ((src << cnt) & ~msk); + src = static_cast(((lsr(src, cnt))&msk) | ((src << cnt) & ~msk)); return cnt > 1 ? _bitswap(src) : src; } @@ -407,7 +421,7 @@ constexpr T _bitswap() noexcept { T msk = ~T(); while (cnt != N) { cnt = lsr(cnt, 1); - msk ^= (msk << cnt); + msk ^= static_cast(msk << cnt); } return msk; } @@ -434,16 +448,17 @@ template constexpr exact_floor_integral_t _bitblend( const T src0_, const U src1_, - const exact_floor_integral_t start, - const exact_floor_integral_t len) noexcept + const size_t start, + const size_t len) noexcept requires(std::is_same_v, exact_floor_integral_t>) { + using resolved_t = exact_floor_integral_t; + using promoted_t = std::conditional_t() < bitsof(), int, resolved_t>; static_assert(binary_digits>::value, ""); - constexpr exact_floor_integral_t digits = bitsof>(); - const exact_floor_integral_t src0 = static_cast>(src0_); - const exact_floor_integral_t src1 = static_cast>(src1_); - const exact_floor_integral_t msk = _mask, _mask_len::unknown>(len) << start; - return src0 ^ ((src0 ^ src1) & msk * (start < digits)); + const promoted_t src0 = static_cast(src0_); + const promoted_t src1 = static_cast(src1_); + const resolved_t msk = _mask(len, start); + return static_cast(src0 ^ ((src0 ^ src1) & msk)); } // -------------------------------------------------------------------------- // @@ -461,14 +476,10 @@ constexpr void _bitexch(T& src0, T& src1, T msk) noexcept { template constexpr void _bitexch(T& src0, T& src1, S start, S len) noexcept { static_assert(binary_digits::value, ""); - constexpr auto digits = binary_digits::value; - const T msk = (len < digits) - ? _mask(len) << start - : -1; // TODO: What if start > 0 here? + const T msk = _mask(len, start); src0 = src0 ^ static_cast(src1 & msk); src1 = src1 ^ static_cast(src0 & msk); src0 = src0 ^ static_cast(src1 & msk); - return; } // Replaces len bits of src0 by the ones of src1 starting at start0 @@ -521,30 +532,28 @@ constexpr void _bitexch(T& src0, T& src1, S start0, S start1, S len) noexcept // --- IMPLEMENTATION DETAILS: INSTRUCTIONS: DOUBLE PRECISION SHIFT LEFT ---- // // Left shifts dst by cnt bits, filling the lsbs of dst by the msbs of src template -constexpr T _shld(T dst, T src, T cnt) noexcept { +constexpr T _shld(const T& dst, const T& src, const size_t& cnt) noexcept { static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; + constexpr size_t digits = binary_digits::value; if (cnt < digits) { - dst = lsl(dst, cnt) | (lsr(src, (digits - cnt))); + return static_cast(lsl(dst, cnt) | (lsr(src, (digits - cnt)))); } else { - dst = lsl(src, cnt - digits) * (cnt < (digits + digits)); + return static_cast(lsl(src, cnt - digits) * (cnt < (digits + digits))); } - return dst; } // -------------------------------------------------------------------------- // // --- IMPLEMENTATION DETAILS: INSTRUCTIONS: DOUBLE PRECISION SHIFT RIGHT --- // // Right shifts dst by cnt bits, filling the msbs of dst by the lsbs of src template -constexpr T _shrd(T dst, T src, T cnt) noexcept { +constexpr T _shrd(const T& dst, const T& src, const size_t& cnt) noexcept { static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; + constexpr size_t digits = binary_digits::value; if (cnt < digits) { - dst = (lsr(dst, cnt)) | lsl(src, (digits - cnt)); + return static_cast((lsr(dst, cnt)) | lsl(src, (digits - cnt))); } else { - dst = (lsr(src, (cnt - digits))) * (cnt < (digits + digits)); + return static_cast((lsr(src, (cnt - digits))) * (cnt < (digits + digits))); } - return dst; } // -------------------------------------------------------------------------- // @@ -783,7 +792,7 @@ constexpr T _mulx(T src0, T src1, T* hi, X...) noexcept { using wider_t = ceil_integral() + bitsof()>; if constexpr ((digits + digits) <= bitsof()) { wider_t tmp = static_cast(src0) * static_cast(src1); - *hi = tmp >> digits; + *hi = static_cast(tmp >> digits); return static_cast(tmp); } else { // Multiplies src0 and src1 and gets the full result without compiler intrinsics @@ -887,6 +896,11 @@ struct uninitialized_t { }; inline constexpr uninitialized_t uninitialized{}; +struct initialized_t { + explicit initialized_t() = default; +}; +inline constexpr initialized_t initialized{}; + template struct container_size_storage { constexpr size_type size() const noexcept { diff --git a/include/bitlib/bit-iterator/bit_iterator.hpp b/include/bitlib/bit-iterator/bit_iterator.hpp index 859bbffe..e9f340ec 100644 --- a/include/bitlib/bit-iterator/bit_iterator.hpp +++ b/include/bitlib/bit-iterator/bit_iterator.hpp @@ -315,7 +315,8 @@ template constexpr bit_iterator::difference_type bit_iterator::operator-(const bit_iterator& other) const { constexpr difference_type digits = binary_digits::value; - return (_current - other._current) * digits + (_position - other._position); + return (_current - other._current) * digits + + (static_cast(_position) - static_cast(other._position)); } // -------------------- BIT ITERATOR: UNDERLYING DETAILS -------------------- // @@ -359,7 +360,7 @@ operator-(const bit_iterator& lhs, const bit_iterator& rhs) { constexpr difference_type digits = rhs_digits; static_assert(lhs_digits == rhs_digits, ""); const difference_type main = lhs._current - rhs._current; - return main * digits + (lhs._position - rhs._position); + return main * digits + (static_cast(lhs._position) - static_cast(rhs._position)); } static_assert(bit_iterator_c>, "bit_iterator does not satisfy bit_iterator_c concept!"); diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index 68844324..17af860c 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -276,7 +276,7 @@ constexpr bit_reference& bit_reference::set() const noexcept { // Resets the value of the referenced bit to 0 template constexpr bit_reference& bit_reference::reset() const noexcept { - _ref &= ~_mask; + _ref &= static_cast(~_mask); return const_cast&>(*this); } diff --git a/test/src/test-iterator_adapter.cpp b/test/src/test-iterator_adapter.cpp index 81b94f7f..8f35b79d 100644 --- a/test/src/test-iterator_adapter.cpp +++ b/test/src/test-iterator_adapter.cpp @@ -180,8 +180,6 @@ TEST(BitIteratorAdapter_BigToSmall, OperatorSubscript) { // Negative index: it[ -1 ] => moves to previous base word; // but since there's no previous word, behavior is UB. We skip testing negative here. - // If we do it + 4, then subscript( it + 4 )[0] should be same as it[4] - auto it_shift = it + 4; // rolls into next base (out-of-bounds, but for test allocate two words) // For safety, create two‐element array: uint32_t arr2[2] = {0x01020304u, 0x05060708u}; Adapter8_32 it2(arr2, 0); diff --git a/test/src/test-to_from_string.cpp b/test/src/test-to_from_string.cpp index 839e9258..b76ff380 100644 --- a/test/src/test-to_from_string.cpp +++ b/test/src/test-to_from_string.cpp @@ -50,4 +50,62 @@ TEST(FromString, Blah) { EXPECT_EQ(arr_18, 0x12'0BEEF_b); bit::from_string("123", arr_16); EXPECT_EQ(arr_16, 16'123_b); +} + +TEST(FromString, IntoBookendRange) { + for (const auto word : get_random_vec(64)) { + for (int i = 1; i < 8; i++) { + auto bits = bit::bit_array<32>(word); + auto bits2 = bits; + bit::from_string( + "123ABC", bits(i, i + 4 * 6)); + bits2(i, i + 4 * 6) = 0x18'123ABC_b; + EXPECT_EQ(bits, bits2); + } + for (int i = 1; i < 8; i++) { + auto bits = bit::bit_array<32>(word); + auto bits2 = bits; + bit::from_string( + "F0000F", bits(i, i + 4 * 6)); + bits2(i, i + 4 * 6) = 0x18'F0000F_b; + EXPECT_EQ(bits, bits2); + } + } +} + +TEST(FromString, IntoBookendRangeOverflow) { + for (int i = 1; i < 8; i++) { + auto bits = 0x20'DEADBEEF_b; + auto bits2 = bits; + bit::from_string( + "F123ABC", bits(i, i + 4 * 6)); + bits2(i, i + 4 * 6) = 0x18'123ABC_b; + EXPECT_EQ(bits, bits2); + } + for (int i = 1; i < 8; i++) { + auto bits = 0x20'00000000_b; + auto bits2 = bits; + bit::from_string( + "F123ABC", bits(i, i + 4 * 6)); + bits2(i, i + 4 * 6) = 0x18'123ABC_b; + EXPECT_EQ(bits, bits2); + } +} + +TEST(FromString, IntoBookendRangeLarge) { + for (int i = 0; i < 128; i++) { + for (auto b : {bit::bit0, bit::bit1}) { + auto bits = bit::bit_array<>(200 + i, b); + auto bits2 = bits; + auto setbits = bit::bit_array<>(200 + i, b ? bit::bit0 : bit::bit1); + auto str = bit::to_string(setbits); + for (int j = 64 / 4; j < 128 / 4; j++) { + for (int k = 0; k < 64; k++) { + bit::from_string(str.substr(str.length() - 1 - j, j), bits(k, k + j * 4)); + bits2(k, k + j * 4) = setbits(0, j * 4); + EXPECT_EQ(bits, bits2); + } + } + } + } } \ No newline at end of file diff --git a/test/src/vector_test.cpp b/test/src/vector_test.cpp index d78a603d..6bf3b27a 100644 --- a/test/src/vector_test.cpp +++ b/test/src/vector_test.cpp @@ -149,7 +149,7 @@ TYPED_TEST(VectorTest, StringConstructor) { std::string rand_bs(strlen, 0); this->empty_vec_bool.clear(); for (auto& pos : rand_bs) { - pos = generate_random_number('0', '1'); + pos = static_cast(generate_random_number('0', '1')); this->empty_vec_bool.push_back(pos == '1'); } this->empty_vec = bit::bit_vector(rand_bs); @@ -270,13 +270,13 @@ TYPED_TEST(VectorTest, AtRead) { constexpr auto digits = bit::binary_digits::value; EXPECT_EQ(this->v3_.at(0), bit::bit0); EXPECT_EQ(this->v3_.at(8), bit::bit1); - for (unsigned int vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { + for (size_t vec_idx = 0; vec_idx < this->random_bitvecs.size(); ++vec_idx) { auto& bitvec = this->random_bitvecs[vec_idx]; auto& boolvec = this->random_boolvecs[vec_idx]; - for (unsigned int i = 0; i < boolvec.size(); i++) { + for (size_t i = 0; i < boolvec.size(); i++) { EXPECT_TRUE(comparator(bitvec.at(i), boolvec.at(i))); } - for (unsigned int i = boolvec.size(); i < boolvec.size() + 4 * digits; i++) { + for (size_t i = boolvec.size(); i < boolvec.size() + 4 * digits; i++) { EXPECT_THROW(bitvec.at(i), std::out_of_range); } } From 464acce091088f31eff5fa5be1fafad81f38b111 Mon Sep 17 00:00:00 2001 From: PeterCDMcLean Date: Fri, 8 Aug 2025 20:04:21 +0000 Subject: [PATCH 85/85] Add additional from_string variants (#30) * Add additional from_string variants * clang format --- .../bitlib/bit-algorithms/to_from_string.hpp | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/include/bitlib/bit-algorithms/to_from_string.hpp b/include/bitlib/bit-algorithms/to_from_string.hpp index e2d5eb1c..c57ee3dc 100644 --- a/include/bitlib/bit-algorithms/to_from_string.hpp +++ b/include/bitlib/bit-algorithms/to_from_string.hpp @@ -1,7 +1,7 @@ -// ================================= array_REF =================================== // +// ============================= to_from_string ============================= // // Project: The Experimental Bit Algorithms Library -// \file to_string.hpp -// Description: Implementation of array_ref +// \file to_from_string.hpp +// Description: Implementation of to_string and from_string // Creator: Vincent Reverdy // Contributor: Peter McLean [2025] // License: BSD 3-Clause License @@ -275,7 +275,7 @@ constexpr CharIt to_string( return to_string(bits, str_first, str_last, meta); } -template > +template ::value_type>> constexpr void from_string( const CharIt str_first, const CharIt str_last, const bit_iterator& bit_first, const bit_iterator& bit_last, @@ -356,7 +356,7 @@ constexpr void from_string( template > + typename Policy = policy::typical::value_type>> constexpr void from_string( const CharIt str_first, const CharIt str_last, const bit_iterator& bit_first, const bit_iterator& bit_last) { @@ -410,7 +410,7 @@ constexpr std::vector from_string( return from_string(first, last, meta); } -template > +template ::value_type>> constexpr void from_string( const std::string& str, const bit_iterator& bit_first, const bit_iterator& bit_last) { @@ -418,7 +418,7 @@ constexpr void from_string( from_string(str.c_str(), str.c_str() + str.length(), bit_first, bit_last); } -template ::value_type>> +template ::word_type>> constexpr void from_string( const std::string& str, RangeT&& bits) { @@ -427,7 +427,7 @@ constexpr void from_string( from_string(str.begin(), str.end(), bits.begin(), bits.end()); } -template > +template ::value_type>> constexpr void from_string( const std::string& str, const bit_iterator& bit_first, const bit_iterator& bit_last, @@ -438,7 +438,7 @@ constexpr void from_string( meta); } -template ::value_type>> +template ::word_type>> constexpr void from_string( const std::string& str, RangeT&& bits, @@ -451,6 +451,25 @@ constexpr void from_string( meta); } +template ::word_type>> +constexpr void from_string( + const CharIt first, const CharIt last, + RangeT&& bits, + string::metadata_t meta = string::typical()) { + using range_iterator_t = std::ranges::iterator_t; + using RandomAccessIt = typename range_iterator_t::iterator_type; + from_string(first, last, bits.begin(), bits.end(), meta); +} + +template ::word_type>> +constexpr void from_string( + const CharIt first, const CharIt last, + RangeT&& bits) { + using range_iterator_t = std::ranges::iterator_t; + using RandomAccessIt = typename range_iterator_t::iterator_type; + return from_string(first, last, bits.begin(), bits.end(), meta); +} + } // namespace bit -#endif // _BIT_TO_STRING_HPP_INCLUDED \ No newline at end of file +#endif // _BIT_TO_STRING_HPP_INCLUDED \ No newline at end of file