libdpf/include/dpf/bit_array.hpp

1786 lines
59 KiB
C++
Raw Normal View History

/// @file dpf/bit_array.hpp
/// @brief Packed bit arrays, static and dynamic, with bit proxies and iterators.
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_BIT_ARRAY_HPP__
#define LIBDPF_INCLUDE_DPF_BIT_ARRAY_HPP__
#include <cstddef>
#include <cmath>
#include <type_traits>
#include <memory>
#include <new>
#include <utility>
#include <algorithm>
#include <numeric>
#include <functional>
#include <iterator>
#include <stdexcept>
#include <string>
#include <array>
#include <ostream>
#include <cassert>
#include "hedley/hedley.h"
#include "portable-snippets/exact-int/exact-int.h"
#include "portable-snippets/builtin/builtin.h"
#include "portable-snippets/endian/endian.h"
#include "dpf/utils.hpp"
#include "dpf/bit.hpp"
#include "dpf/aligned_allocator.hpp"
namespace dpf
{
namespace detail
{
template <typename Word>
HEDLEY_NO_THROW
constexpr void check_one_bit(Word mask) noexcept
{
(void)mask;
#if defined(__GNUC__) || defined(__clang__)
if (__builtin_is_constant_evaluated()) return;
#endif
assert(mask != Word{0}
&& (mask & static_cast<Word>(mask - Word{1})) == Word{0});
}
} // namespace detail
template <typename ConcreteBitArrayT, typename WordT>
class setbit_index_iterable; // forward declaration
template <typename ConcreteBitArrayT, typename WordT>
class bit_iterator_base; // forward reference
template <typename ConcreteBitArrayT, typename WordT>
class bit_iterator; // forward reference
template <typename ConcreteBitArrayT, typename WordT>
class const_bit_iterator; // forward reference
/// @brief a base class for classes representing a sequence of bits
/// @details A `bit_array` represents a sequence of bits. The underlying
/// storage is an array of integers of type `dpf::bit_array::word_type`.
/// @tparam ConcreteBitArrayT concrete bit array type
/// @tparam WordT word used to pack bits
template <typename ConcreteBitArrayT, typename WordT = psnip_uint64_t>
class bit_array_base
{
private:
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_NO_THROW
constexpr auto derived_from_this() noexcept
{
return static_cast<ConcreteBitArrayT *>(this);
}
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_NO_THROW
constexpr auto derived_from_this() const noexcept
{
return static_cast<const ConcreteBitArrayT *>(this);
}
public:
class bit_reference; // forward reference
/// @brief `dpf::bit`
using value_type = dpf::bit;
/// @brief proxy class representing a reference to a single bit.
using reference = bit_array_base::bit_reference;
/// @brief `dpf::bit`
using const_reference = dpf::bit;
/// @brief a random access iterator to `value_type`.
/// @note convertible to `const_bit_iterator`
using iterator = bit_iterator<ConcreteBitArrayT, WordT>;
/// @brief a random access iterator to `const value_type`
using const_iterator = const_bit_iterator<ConcreteBitArrayT, WordT>;
/// @brief a type that simulates pointer-to-`value_type` behavior,
/// identical to `iterator`.
using pointer = iterator;
/// @brief a type that simulates pointer-to-`const value_type` behavior,
/// identical to `const_iterator`.
using const_pointer = const_iterator;
/// @brief a signed integral type, identical to
/// `std::iterator_traits<iterator>::difference_type`.
using difference_type = std::ptrdiff_t;
static_assert(std::is_integral_v<difference_type>
&& std::is_signed_v<difference_type>);
/// @brief an unsigned integral type that can represent any non-negative
/// value of `difference_type`.
using size_type = std::size_t;
static_assert(std::is_integral_v<size_type>
&& std::is_unsigned_v<size_type>);
/// @brief an unsigned integral type used for the internal representation
/// of bits.
using word_type = WordT;
static_assert(std::is_integral_v<word_type>
&& std::is_unsigned_v<word_type>);
/// @brief pointer to `word_type`
using word_pointer = std::add_pointer_t<word_type>;
/// @brief const pointer to `word_type`
using const_word_pointer = std::add_pointer_t<std::add_const_t<word_type>>;
/// @brief the number of `dpf::bit`s represented by each `word_type`.
/// @note guaranteed (via `static_assert`) to at most `64` bits
using child_type = ConcreteBitArrayT;
static constexpr size_type bits_per_word = utils::bitlength_of<word_type>();
static constexpr size_type lg_bits_per_word = std::log2(bits_per_word);
static_assert(bits_per_word <= 64, "bits_per_word greater than 64");
/// @brief default copy constructor
inline constexpr bit_array_base(const bit_array_base &) = default;
/// @brief default move constructor
HEDLEY_NO_THROW
inline constexpr bit_array_base(bit_array_base &&) noexcept = default;
/// @brief default destructor
~bit_array_base() = default;
/// @brief default copy assignment
/// @return `*this`
inline constexpr
bit_array_base & operator=(const bit_array_base &) = default;
/// @brief defaulted move assignment
/// @return `*this`
HEDLEY_NO_THROW
inline constexpr
bit_array_base & operator=(bit_array_base &&) noexcept = default;
/// @brief direct access to the underlying data array
/// @return a pointer to the start of the data array
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr const_word_pointer data() const noexcept
{
return derived_from_this()->data();
}
/// @brief direct access into the underlying data array (w/o bounds
/// checking)
/// @param pos the array element to access
/// @note Does not perform bounds checking; behaviour is undefined if
/// `pos` is out of bounds
/// @return `data()[pos]`
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr word_type data(size_type pos) const noexcept
{
return data()[pos];
}
/// @brief direct access to the underlying data array
/// @return a pointer to the start of the data array
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr word_pointer data() noexcept { return derived_from_this()->data(); }
/// @brief direct access into the underlying data array (w/o bounds
/// checking)
/// @param pos the array element to access
/// @note Does not perform bounds checking; behaviour is undefined if
/// `pos` is out of bounds
/// @return `data()[pos]`
HEDLEY_ALWAYS_INLINE
constexpr word_type & data(size_type pos) noexcept
{
return data()[pos];
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr size_type data_length() const noexcept { return derived_from_this()->data_length(); }
/// @brief access specified bit (w/o bounds checking)
/// @{
/// @details accesses the bit at position `pos`
/// @param pos the 0-based position of the bit to return (least
/// significant to most significant)
/// @note Unlike `test` and `at`, does not throw exceptions: the behavior
/// is undefined if `pos` is out of bounds
/// @return an object of type `dpf::bit_array_base::reference`, which
/// allows writing to the requested bit
/// @complexity `O(1)`
HEDLEY_NO_THROW
inline constexpr reference operator[](size_type pos) noexcept
{
return reference{&data(pos / bits_per_word),
static_cast<word_type>(word_type(1) << (pos % bits_per_word))};
}
/// @details accesses the bit at position `pos`
/// @param pos the 0-based position of the bit to return (least
/// significant to most significant)
/// @return the value of the requested bit
/// @note Unlike `test` and `at`, does not throw exceptions: the behavior
/// is undefined if `pos` is out of bounds
/// @complexity `O(1)`
HEDLEY_NO_THROW
inline constexpr const_reference operator[](size_type pos) const noexcept
{
const word_type tmp = data(pos / bits_per_word);
const word_type mask = static_cast<word_type>(word_type(1) << (pos % bits_per_word));
return (tmp & mask) ? dpf::bit::one : dpf::bit::zero;
}
/// @}
/// @brief access specified bit with bounds checking
/// @{
/// @details accesses the bit at position `pos`
/// @param pos the 0-based position of the bit to return (least
/// significant to most significant)
/// @throws std::out_of_range if `pos` does not correspond to a valid
/// position within the `bit_array_base`
/// @return an object of type `dpf::bit_array_base::reference`, which
/// allows writing to the requested bit
/// @complexity `O(1)`
constexpr reference at(size_type pos)
{
utils::constexpr_maybe_throw<std::out_of_range>(
!(pos < size()),
"pos is out of range");
return this->operator[](pos);
}
/// @details accesses the bit at position `pos`
/// @param pos the 0-based position of the bit to return (least
/// significant to most significant)
/// @return the value of the requested bit
/// @throws std::out_of_range if `pos` does not correspond to a valid
/// position within the `bit_array_base`
/// @complexity `O(1)`
constexpr const_reference at(size_type pos) const
{
utils::constexpr_maybe_throw<std::out_of_range>(
!(pos < size()),
"pos is out of range");
return this->operator[](pos);
}
/// @}
/// @brief returns an iterator to the first bit
/// @{
/// @return iterator to the first element
/// @complexity `O(1)`
HEDLEY_NO_THROW
constexpr iterator begin() noexcept
{
auto *p = data();
if (p == nullptr) return iterator{};
return iterator{p, word_type(1)};
}
/// @return iterator to the first element
/// @complexity `O(1)`
HEDLEY_NO_THROW
constexpr const_iterator begin() const noexcept
{
auto *p = data();
if (p == nullptr) return const_iterator{};
return const_iterator{p, word_type(1)};
}
/// @return iterator to the first element
/// @complexity `O(1)`
HEDLEY_NO_THROW
constexpr const_iterator cbegin() const noexcept
{
return begin();
}
/// @}
/// @brief returns an iterator to the end (one past the last bit)
/// @{
/// @return iterator to the element following the last element
/// @complexity `O(1)`
HEDLEY_NO_THROW
constexpr iterator end() noexcept
{
auto *p = data();
if (p == nullptr) return iterator{};
return iterator{p + (size() >> lg_bits_per_word),
static_cast<word_type>(word_type(1) << (size() % bits_per_word))};
}
/// @return iterator to the element following the last element
/// @complexity `O(1)`
HEDLEY_NO_THROW
constexpr const_iterator end() const noexcept
{
auto *p = data();
if (p == nullptr) return const_iterator{};
return const_iterator{p + (size() >> lg_bits_per_word),
static_cast<word_type>(word_type(1) << (size() % bits_per_word))};
}
/// @return iterator to the element following the last element
/// @complexity `O(1)`
HEDLEY_NO_THROW
constexpr const_iterator cend() const noexcept
{
return end();
}
/// @}
/// @brief checks if the specified bit is set to `true`
/// @param pos the 0-based position of the bit to return (least
/// significant to most significant)
/// @return `true` if the requested bit is set, `false` otherwise
/// @complexity `O(1)`
bool test(size_type pos) const
{
return at(pos);
}
/// @brief checks if all, any or none of the bits are set to `true`
/// @{
/// @details checks if all bits are set to `true`
/// @return `true` if all of the bits are set to `true`, otherwise `false`
/// @complexity `O(size())`
HEDLEY_NO_THROW
bool all() const noexcept
{
if (size() == 0) return true;
const size_type n = data_length();
const size_type rem = size() % bits_per_word;
const word_type *p = data();
const size_type full = (rem == 0) ? n : n - 1;
const word_type ones = static_cast<word_type>(~word_type{0});
for (size_type i = 0; i < full; ++i)
if (p[i] != ones) return false;
if (rem != 0)
{
const word_type m = low_bits_mask(rem);
if (static_cast<word_type>(p[n - 1] & m) != m) return false;
}
return true;
}
/// @details checks if all bits in a range are set to `true`
/// @tparam Iterator an iterator type
/// @param first,last the range of elements under consideration
/// @return `true` if all of the bits in the given range are set to `true`, otherwise `false`
/// @complexity `O(last-first)`
template <typename Iterator>
HEDLEY_NO_THROW
bool all(Iterator first, Iterator last) const noexcept
{
bool ok = true;
for_each_span(first, last, [&](word_type bits, word_type relevant)
{
if (bits != relevant) ok = false;
return ok;
});
return ok;
}
/// @details checks if any bits are set to `true`
/// @return `true` if any of the bits are set to `true`, otherwise `false`
/// @complexity `O(size())`
HEDLEY_NO_THROW
bool any() const noexcept
{
const size_type n = data_length();
if (n == 0 || size() == 0) return false;
const size_type rem = size() % bits_per_word;
const word_type *p = data();
const size_type full = (rem == 0) ? n : n - 1;
for (size_type i = 0; i < full; ++i)
if (p[i] != word_type{0}) return true;
if (rem != 0)
return static_cast<word_type>(p[n - 1] & low_bits_mask(rem)) != word_type{0};
return false;
}
/// @details checks if any bits in a range are set to `true`
/// @tparam Iterator an iterator type
/// @param first,last the range of elements under consideration
/// @return `true` if any of the bits in the given range are set to `true`, otherwise `false`
/// @complexity `O(last-first)`
template <typename Iterator>
HEDLEY_NO_THROW
bool any(Iterator first, Iterator last) const noexcept
{
bool found = false;
for_each_span(first, last, [&](word_type bits, word_type)
{
if (bits != word_type{0}) found = true;
return !found;
});
return found;
}
/// @details checks if none of the bits are set to `true`
/// @return `true` if none of the bits are set to `true`, otherwise `false`
/// @complexity `O(size())`
HEDLEY_NO_THROW
bool none() const noexcept
{
return !any();
}
/// @details checks if none bits in a range are set to `true`
/// @tparam Iterator an iterator type
/// @param first,last the range of elements under consideration
/// @return `true` if none of the bits in the given range are set to `true`, otherwise `false`
/// @complexity `O(last-first)`
template <typename Iterator>
HEDLEY_NO_THROW
bool none(Iterator first, Iterator last) const noexcept
{
return !any(first, last);
}
/// @}
/// @brief returns the number of bits set to `true`
/// @{
/// @details counts the number of bits that are set to `true`
/// @return the number of bits set to `true`
/// @complexity `O(size())`
HEDLEY_NO_THROW
size_type count() const noexcept
{
const size_type n = data_length();
if (n == 0 || size() == 0) return 0;
const size_type rem = size() % bits_per_word;
const word_type *p = data();
const size_type full = (rem == 0) ? n : n - 1;
size_type sum = 0;
for (size_type i = 0; i < full; ++i)
sum += pop(p[i]);
if (rem != 0)
sum += pop(static_cast<word_type>(p[n - 1] & low_bits_mask(rem)));
return sum;
}
/// @details counts the number of bits in a range that are set to `true`
/// @tparam Iterator an iterator type
/// @param first,last the range of elements under consideration
/// @return the number of bits in the given range that are set to `true`
/// @complexity `O(last-first)`
template <typename Iterator>
HEDLEY_NO_THROW
size_type count(Iterator first, Iterator last) const noexcept
{
size_type sum = 0;
for_each_span(first, last, [&](word_type bits, word_type)
{
sum += pop(bits);
return true;
});
return sum;
}
/// @}
/// @brief returns the parity of all stored bits
/// @{
/// @details counts the parity of all stored bits
/// @return the parity of all stored bits
/// @complexity `O(size())`
HEDLEY_NO_THROW
size_type parity() const noexcept
{
const size_type n = data_length();
if (n == 0 || size() == 0) return 0;
const size_type rem = size() % bits_per_word;
const word_type *p = data();
const size_type full = (rem == 0) ? n : n - 1;
word_type x = word_type{0};
for (size_type i = 0; i < full; ++i)
x ^= p[i];
if (rem != 0)
x ^= static_cast<word_type>(p[n - 1] & low_bits_mask(rem));
return utils::parity(x);
}
/// @details counts the parity of bits in a range
/// @tparam Iterator an iterator type
/// @param first,last the range of elements under consideration
/// @return the parity of all bits in the given range
/// @complexity `O(last-first)`
template <typename Iterator>
HEDLEY_NO_THROW
size_type parity(Iterator first, Iterator last) const noexcept
{
word_type x = word_type{0};
for_each_span(first, last, [&](word_type bits, word_type)
{
x ^= bits;
return true;
});
return utils::parity(x);
}
/// @}
/// @brief returns the number of bits
/// @return number of bits that the `bit_array_base` holds
/// @complexity `O(1)`
HEDLEY_NO_THROW
HEDLEY_PURE
HEDLEY_ALWAYS_INLINE
constexpr size_type size() const noexcept
{
return derived_from_this()->size();
}
/// @brief sets bits to `true` or given value
/// @{
/// @brief sets all bits to `true`
/// @complexity `O(size())`
HEDLEY_NO_THROW
constexpr void set() noexcept
{
const size_type n = data_length();
if (n == 0) return;
const size_type rem = size() % bits_per_word;
word_type *p = data();
const word_type ones = static_cast<word_type>(~word_type{0});
if (rem == 0)
{
for (size_type i = 0; i < n; ++i) p[i] = ones;
return;
}
for (size_type i = 0; i + 1 < n; ++i) p[i] = ones;
p[n - 1] = low_bits_mask(rem);
}
/// @brief sets the bit at position `pos` to the value `value`
/// @param pos the 0-based position of the bit to set (least significant
/// to most significant)
/// @param value the value to set the bit to
/// @throws std::out_of_range if `pos` does not correspond to a valid
/// position within the `bit_array_base`
/// @complexity `O(1)`
constexpr void set(size_type pos, bool value = true)
{
at(pos) = value;
}
/// @brief sets the bit at position `pos` to the value `value`
/// @param pos the 0-based position of the bit to set (least significant
/// to most significant)
/// @param value the value to set the bit to
/// @throws std::out_of_range if `pos` does not correspond to a valid
/// position within the `bit_array_base`
/// @complexity `O(1)`
constexpr void unchecked_set(size_type pos, bool value = true)
{
this->operator[](pos) = value;
}
/// @}
/// @brief sets bits to `false`
/// @{
/// @brief sets all bits to `false'
/// @complexity `O(size())`
HEDLEY_NO_THROW
constexpr void unset() noexcept
{
word_type *p = data();
const size_type n = data_length();
for (size_type i = 0; i < n; ++i) p[i] = word_type{0};
}
/// @brief sets the bit at position `pos` to `false`
/// @param pos the 0-based position of the bit to unset (least
/// significant to most significant)
/// @throws std::out_of_range if `pos` does not correspond to a valid
/// position within the `bit_array_base`
/// @complexity `O(1)`
constexpr void unset(size_type pos)
{
at(pos) = dpf::bit::zero;
}
/// @}
constexpr void unchecked_unset(size_type pos)
{
this->operator[](pos) = dpf::bit::zero;
}
/// @brief toggles the values of bits
/// @{
/// @brief flips all bits (like `operator~`, but in-place)
/// @complexity `O(size())`
HEDLEY_NO_THROW
constexpr void flip() noexcept
{
const size_type n = data_length();
if (n == 0) return;
word_type *p = data();
for (size_type i = 0; i < n; ++i)
p[i] = static_cast<word_type>(~p[i]);
const size_type rem = size() % bits_per_word;
if (rem != 0)
p[n - 1] &= low_bits_mask(rem);
}
/// @brief flips the bit at the position `pos`
/// @param pos the 0-based position of the bit to flip (least
/// significant to most significant)
/// @throws std::out_of_range if `pos` does not correspond to a valid
/// position within the `bit_array_base`
/// @complexity `O(1)`
constexpr void flip(size_type pos)
{
at(pos).flip();
}
/// @}
/// @brief returns a string representation of the data
/// @details converts the contents of the `bit_array_base` to a string. Uses
/// `zero` to represent bits with value `false` and `one` to
/// represent bits with value `true`. The resulting string
/// contains `size()` characters with the first character
/// corresponding to the last `(size()-1th)` bit and the last
/// character corresponding tot he first `(0th)` bit.
/// @tparam CharT character type
/// @tparam Traits character traits
/// @tparam Allocator allocator type
/// @param zero character to use to represent `false`/`0` (default: ``CharT('0')``)
/// @param one character to use to represent `true`/`1` (default: ``CharT('1')``)
/// @return the converted string
/// @throws May throw `std::bad_alloc` from the `std::string` constructor.
/// @complexity `O(size())`
template <typename CharT = char,
typename Traits = std::char_traits<CharT>,
typename Allocator = std::allocator<CharT>>
std::basic_string<CharT, Traits, Allocator> to_string(
CharT zero = CharT('0'),
CharT one = CharT('1')) const
{
const size_type n = this->size();
std::basic_string<CharT, Traits, Allocator> s(n, zero);
for (size_type i = 0; i < n; ++i)
{
if (this->operator[](n - 1 - i))
s[i] = one;
}
return s;
}
/// @brief proxy class representing a reference to a bit
/// @details This class is used as a proxy object to allow users to
/// interact with individual bits of a ``bit_array_base`, since
/// standard C++ types have insufficient precision to specify
/// individual bits.
/// @note The primary use of ``bit_array_base::bit_reference`` is to
/// provide an l-value that can be returned from
/// `bit_array_base::operator[]`.
class bit_reference
{
public:
using word_type = bit_array_base::word_type;
using word_pointer = bit_array_base::word_pointer;
/// @brief (deleted) default c'tor
inline bit_reference() = delete;
/// @brief copy c'tor
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr bit_reference(const bit_reference &) noexcept = default;
/// @brief move c'tor
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr bit_reference(bit_reference &&) noexcept = default;
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr bit_reference & operator=(bool b) noexcept
{
this->assign(b);
return *this;
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr bit_reference & operator=(const bit_reference & b) noexcept
{
this->assign(static_cast<bool>(b));
return *this;
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr bit_reference & operator=(bit_reference && b) noexcept
{
this->assign(static_cast<bool>(b));
return *this;
}
~bit_reference() = default;
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr operator bool() const noexcept
{
assert(word_ptr_ != nullptr);
detail::check_one_bit(mask_);
return !(!(*word_ptr_ & mask_));
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr operator int() const noexcept
{
return int{static_cast<bool>(*this)};
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr operator dpf::bit() const noexcept
{
return dpf::bit{static_cast<bool>(*this)};
}
/// @brief XOR. Exact match so `bit_reference - bit_reference` is not
/// ambiguous with integer subtraction of the proxy.
/// @param lhs the left-hand operand
/// @param rhs the right-hand operand
/// @return XOR
friend constexpr dpf::bit operator-(bit_reference lhs, bit_reference rhs) noexcept
{
return static_cast<dpf::bit>(static_cast<bool>(lhs) ^ static_cast<bool>(rhs));
}
HEDLEY_NO_THROW
friend constexpr dpf::bit operator+(bit_reference lhs, bit_reference rhs) noexcept
{
return static_cast<dpf::bit>(static_cast<bool>(lhs) ^ static_cast<bool>(rhs));
}
/// @brief performs binary AND, OR, XOR and NOT
/// @{
/// @details sets `*this` to the result of binary AND on `*this` and `b`
/// @param b the other bit
/// @return `*this`
/// @complexity `O(1)`
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr bit_reference & operator&=(bool b) noexcept
{
if (b == false) *word_ptr_ &= ~this->mask_;
return *this;
}
/// @details sets `*this` to the result of binary OR on `*this` and `b`
/// @param b the other bit
/// @return `*this`
/// @complexity `O(1)`
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr bit_reference & operator|=(bool b) noexcept
{
if (b == true) *word_ptr_ |= this->mask_;
return *this;
}
/// @details sets `*this` to the result of binary XOR on `*this` and `b`
/// @param b the other bit
/// @return `*this`
/// @complexity `O(1)`
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr bit_reference & operator^=(bool b) noexcept
{
if (b == true) *word_ptr_ ^= this->mask_;
return *this;
}
/// @details returns a temporary copy of `*this` with its value flipped (binary NOT)
/// @return the flipped bit
/// @complexity `O(1)`
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr dpf::bit operator~() const noexcept
{
return dpf::bit{!static_cast<bool>(*this)};
}
/// @}
/// @brief sets to the referenced bit to 1
/// @return `*this`
/// @complexity `O(1)`
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr bit_reference& set() noexcept
{
assert(word_ptr_ != nullptr);
detail::check_one_bit(mask_);
*word_ptr_ |= mask_;
return *this;
}
/// @brief unsets the referenced bit to 0
/// @return `*this`
/// @complexity `O(1)`
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr bit_reference& unset() noexcept
{
assert(word_ptr_ != nullptr);
detail::check_one_bit(mask_);
*word_ptr_ &= static_cast<word_type>(~mask_);
return *this;
}
/// @brief assigns `b ? 1 : 0` to the referenced bit
/// @param b the `b`
/// @return `*this`
/// @complexity `O(1)`
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr bit_reference& assign(bool b) noexcept
{
assert(word_ptr_ != nullptr);
detail::check_one_bit(mask_);
*word_ptr_ = b ? (*word_ptr_ | mask_)
: (*word_ptr_ & static_cast<word_type>(~mask_));
return *this;
}
/// @brief flips the referenced bit
/// @return `*this`
/// @complexity `O(1)`
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr bit_reference& flip() noexcept
{
assert(word_ptr_ != nullptr);
detail::check_one_bit(mask_);
*word_ptr_ ^= mask_;
return *this;
}
/// @brief Exchange the bits named by two proxies, including temporaries
/// returned from `operator[]` and `operator*`.
/// @param a the `a`
/// @param b the `b`
HEDLEY_NO_THROW
friend constexpr void swap(bit_reference a, bit_reference b) noexcept
{
const bool tmp = static_cast<bool>(a);
a = static_cast<bool>(b);
b = tmp;
}
private:
word_pointer word_ptr_; //< pointer to word holding the bit.
word_type mask_; //< bitmask for referenced bit within `*word_ptr`.
/// @brief private c'tor.
/// @param word_ptr pointer to the word holding the actual bit.
/// @param mask bitmask indicating referenced bit of `*word_ptr`.
/// @note `word_ptr` must not be `nullptr`.
/// @note `mask` must be `1ull << b` for some `0 <= b < 64`.
/// @note accessible only to `dpf::bit_array_base` and its iterators.
HEDLEY_NO_THROW
HEDLEY_NON_NULL()
HEDLEY_ALWAYS_INLINE
constexpr bit_reference(word_pointer word_ptr, word_type mask) noexcept
: word_ptr_{word_ptr},
mask_{mask}
{
assert(word_ptr_ != nullptr);
detail::check_one_bit(mask_);
}
template <typename CharT,
typename Traits>
friend std::basic_ostream<CharT, Traits> &
operator<<(std::basic_ostream<CharT, Traits> & os, bit_reference bit)
{
return os << dpf::to_string<CharT, Traits>(bit);
}
friend class bit_array_base; //< access to c'tor
friend class bit_iterator_base<ConcreteBitArrayT, WordT>; //< access to c'tor
friend class bit_iterator<ConcreteBitArrayT, WordT>; //< access to c'tor
friend class const_bit_iterator<ConcreteBitArrayT, WordT>; //< access to c'tor
}; // class bit_array_base::bit_reference
protected:
/// @brief constructs a `bit_array_base`
inline constexpr bit_array_base() = default;
/// @brief an all-`1`s sentinel word marking the end of the data
/// @note `sentinel` exists to assist `setbit_index_iterator` in deciding
/// if it has hit the end of the data array
static constexpr word_type sentinel = ~word_type(0);
/// @brief Low `n` bits set. `n == 0` yields 0. `n >= bits_per_word` yields all ones.
/// @param n the `n`
/// @return Low `n` bits set
HEDLEY_NO_THROW
static constexpr word_type low_bits_mask(size_type n) noexcept
{
if (n == 0) return word_type{0};
if (n >= bits_per_word) return static_cast<word_type>(~word_type{0});
return static_cast<word_type>(
static_cast<word_type>(~word_type{0}) >> (bits_per_word - n));
}
HEDLEY_NO_THROW
static constexpr size_type pop(word_type w) noexcept
{
return static_cast<size_type>(utils::popcount(w));
}
/// @brief Bits strictly below the single set bit in `mask`.
/// @param mask the bit mask
/// @return Bits strictly below the single set bit in `mask`
HEDLEY_NO_THROW
static constexpr word_type bits_below(word_type mask) noexcept
{
return static_cast<word_type>(mask - word_type{1});
}
/// @brief Bits at and above the single set bit in `mask`.
/// @param mask the bit mask
/// @return Bits at and above the single set bit in `mask`
HEDLEY_NO_THROW
static constexpr word_type bits_at_and_above(word_type mask) noexcept
{
return static_cast<word_type>(~bits_below(mask));
}
/// @brief Invoke `fn(masked_bits, relevant_mask)` for each limb touched by
/// `[first, last)`. Does not dereference a one-past-the-end word.
/// `fn` returns false to stop early.
/// @tparam Iterator iterator type
/// @tparam Fn fn
/// @param first the first element of the range
/// @param last the past-the-end element of the range
/// @param fn the `fn`
template <typename Iterator, typename Fn>
void for_each_span(Iterator first, Iterator last, Fn fn) const
{
if (first == last) return;
const word_type *base = data();
const word_type *limit = base + data_length();
const word_type *fw = first.word_ptr_;
const word_type *lw = last.word_ptr_;
const word_type fm = first.mask_;
const word_type lm = last.mask_;
auto contained = [base, limit](const word_type *p)
{
return p >= base && p < limit;
};
auto emit = [&](const word_type *p, word_type relevant) -> bool
{
if (!contained(p) || relevant == word_type{0}) return true;
return fn(static_cast<word_type>(*p & relevant), relevant);
};
if (fw == lw)
{
const word_type span = static_cast<word_type>(
bits_below(lm) & bits_at_and_above(fm));
emit(fw, span);
return;
}
if (!emit(fw, bits_at_and_above(fm))) return;
const word_type *mid = fw + 1;
const word_type *mid_end = (lw < limit) ? lw : limit;
if (mid < base) mid = base;
const word_type ones = static_cast<word_type>(~word_type{0});
for (const word_type *p = mid; p < mid_end; ++p)
{
if (!fn(*p, ones)) return;
}
emit(lw, bits_below(lm));
}
}; // class bit_array_base
/// @brief a base class provided to simplify the definition of
/// `bit_iterator` and `const_bit_iterator`
/// @tparam ConcreteBitArrayT concrete bit array type
/// @tparam WordT word used to pack bits
template <typename ConcreteBitArrayT,
typename WordT>
class bit_iterator_base
{
public:
using difference_type = std::ptrdiff_t;
using iterator_category = std::random_access_iterator_tag;
using word_type = typename bit_array_base<ConcreteBitArrayT, WordT>::word_type;
using word_pointer = typename bit_array_base<ConcreteBitArrayT, WordT>::word_pointer;
static constexpr auto bits_per_word = bit_array_base<ConcreteBitArrayT, WordT>::bits_per_word;
inline constexpr bool operator==(const bit_iterator_base & rhs) const
{
return (word_ptr_ == rhs.word_ptr_) && (mask_ == rhs.mask_);
}
inline constexpr bool operator<(const bit_iterator_base & rhs) const
{
return word_ptr_ < rhs.word_ptr_ ||
(word_ptr_ == rhs.word_ptr_ && mask_ < rhs.mask_);
}
inline constexpr bool operator!=(const bit_iterator_base & rhs) const
{
return !(*this == rhs);
}
inline constexpr bool operator>(const bit_iterator_base & rhs) const
{
return rhs < *this;
}
inline constexpr bool operator<=(const bit_iterator_base & rhs) const
{
return !(rhs < *this);
}
inline constexpr bool operator>=(const bit_iterator_base & rhs) const
{
return !(*this < rhs);
}
protected:
/// @brief bitmask for the least-significant bit of a word
static constexpr word_type lsb = word_type(1);
/// @brief bitmask for the most-significant bit of a word
static constexpr word_type msb = ~(~word_type(0) >> 1);
/// @brief pointer to the word containing the current iteration bit
word_pointer word_ptr_;
/// @brief mask into `*word_ptr_` to obtain current iteration bit
word_type mask_;
/// @brief Singular iterator. Comparable, not dereferenceable.
HEDLEY_NO_THROW
inline constexpr bit_iterator_base() noexcept
: word_ptr_{nullptr},
mask_{lsb}
{ }
/// @brief constructs a `bit_iterator_base`
/// @{
/// @brief constructs a `bit_iterator_base`
/// @param word_ptr pointer to the word containing the first iteration
/// bit
/// @note `word_ptr` must be dereferencable (not `nullptr`)
HEDLEY_NO_THROW
HEDLEY_NON_NULL()
inline constexpr explicit bit_iterator_base(word_pointer word_ptr) noexcept
: word_ptr_{word_ptr},
mask_{lsb}
{
assert(word_ptr_ != nullptr);
}
/// @brief constructs a `bit_iterator_base`
/// @param word_ptr pointer to the word containing the first iteration
/// bit
/// @param mask bitmask to obtain `*word_ptr` first iteration bit from
/// `word_ptr`
/// @note `word_ptr` must be dereferencable (not `nullptr`)
/// @note `mask` must have exactly one bit set
HEDLEY_NO_THROW
HEDLEY_NON_NULL()
inline constexpr bit_iterator_base(word_pointer word_ptr, word_type mask) noexcept
: word_ptr_{word_ptr},
mask_{mask}
{
assert(word_ptr_ != nullptr);
detail::check_one_bit(mask_);
}
/// @}
/// @brief increments the iterator by one bit
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
void constexpr increment() noexcept
{
if (HEDLEY_UNLIKELY(!(mask_ <<= 1)))
{
mask_ = lsb;
++word_ptr_;
}
}
/// @brief decrements the iterator by one bit
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
void constexpr decrement() noexcept
{
if (HEDLEY_UNLIKELY(!(mask_ >>= 1)))
{
mask_ = msb;
--word_ptr_;
}
}
/// @brief increments the iterator by specified number of bits
/// @param amt the number of bits to increment by
HEDLEY_NO_THROW
inline constexpr void increment_by(difference_type amt) noexcept
{
if (amt == 0) return;
// Stay in signed arithmetic. `ctz` returns `size_t`, and dividing a
// negative `ptrdiff_t` by that unsigned width walks the pointer the
// wrong direction.
const auto bpw = static_cast<difference_type>(bits_per_word);
const auto bit = static_cast<difference_type>(utils::ctz(mask_));
difference_type offset = amt + bit;
difference_type words = offset / bpw;
difference_type rem = offset % bpw;
if (rem < 0)
{
rem += bpw;
--words;
}
word_ptr_ += words;
mask_ = static_cast<word_type>(static_cast<word_type>(1) << rem);
}
/// @brief decrements the iterator by specified number of bits
/// @param amt the number of bits to decrement by
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
void constexpr decrement_by(difference_type amt) noexcept
{
increment_by(-amt);
}
friend constexpr difference_type operator-(const bit_iterator_base & lhs,
const bit_iterator_base & rhs) noexcept
{
const auto bpw = static_cast<difference_type>(
bit_iterator_base<ConcreteBitArrayT, WordT>::bits_per_word);
const auto lb = static_cast<difference_type>(utils::ctz(lhs.mask_));
const auto rb = static_cast<difference_type>(utils::ctz(rhs.mask_));
return bpw * (lhs.word_ptr_ - rhs.word_ptr_) + (lb - rb);
}
friend class setbit_index_iterable<ConcreteBitArrayT, WordT>;
}; // class bit_iterator_base
template <typename ConcreteBitArrayT,
typename WordT>
class bit_iterator final
: public bit_iterator_base<ConcreteBitArrayT, WordT>
{
private:
using base = bit_iterator_base<ConcreteBitArrayT, WordT>;
public:
using difference_type = std::ptrdiff_t;
using value_type = typename bit_array_base<ConcreteBitArrayT, WordT>::value_type;
using reference = typename bit_array_base<ConcreteBitArrayT, WordT>::reference;
using const_reference = typename bit_array_base<ConcreteBitArrayT>::const_reference;
using pointer = std::add_pointer_t<reference>;
using iterator = bit_iterator<ConcreteBitArrayT, WordT>;
using word_type = typename bit_array_base<ConcreteBitArrayT, WordT>::word_type;
using word_pointer = typename bit_array_base<ConcreteBitArrayT, WordT>::word_pointer;
/// @brief Singular iterator. Comparable, not dereferenceable.
HEDLEY_NO_THROW
constexpr bit_iterator() noexcept = default;
HEDLEY_NO_THROW
inline constexpr
bit_iterator(const bit_iterator &) noexcept = default;
HEDLEY_NO_THROW
inline constexpr bit_iterator(bit_iterator &&) noexcept = default;
HEDLEY_NO_THROW
HEDLEY_NON_NULL()
inline constexpr explicit bit_iterator(word_pointer word_ptr) noexcept
: base{word_ptr} { }
HEDLEY_NO_THROW
HEDLEY_NON_NULL()
inline constexpr explicit bit_iterator(word_pointer word_ptr,
word_type mask) noexcept
: base{word_ptr, mask} { }
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
bit_iterator & operator=(const bit_iterator &) noexcept = default;
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
bit_iterator & operator=(bit_iterator &&) noexcept = default;
~bit_iterator() = default;
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr reference operator*() const noexcept
{
return reference{base::word_ptr_, base::mask_};
}
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr iterator & operator++() noexcept
{
base::increment();
return *this;
}
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr iterator operator++(int) noexcept
{
iterator tmp = *this;
base::increment();
return tmp;
}
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr iterator & operator--() noexcept
{
base::decrement();
return *this;
}
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr iterator operator--(int) noexcept
{
iterator tmp = *this;
base::decrement();
return tmp;
}
inline constexpr iterator & operator+=(difference_type amt)
{
base::increment_by(amt);
return *this;
}
inline constexpr iterator & operator-=(difference_type amt)
{
base::decrement_by(amt);
return *this;
}
inline constexpr iterator operator+(difference_type amt) const
{
iterator tmp = *this;
return tmp += amt;
}
inline constexpr iterator operator-(difference_type amt) const
{
iterator tmp = *this;
return tmp -= amt;
}
HEDLEY_NO_THROW
friend constexpr iterator operator+(difference_type amt, iterator it) noexcept
{
return it + amt;
}
inline constexpr reference operator[](difference_type i)
{
return *(*this + i);
}
inline constexpr const_reference operator[](difference_type i) const
{
return *(*this + i);
}
friend class bit_array_base<ConcreteBitArrayT, WordT>;
friend class const_bit_iterator<ConcreteBitArrayT, WordT>;
}; // class bit_iterator
template <typename ConcreteBitArrayT,
typename WordT>
class const_bit_iterator final
: public bit_iterator_base<ConcreteBitArrayT, WordT>
{
private:
using base = bit_iterator_base<ConcreteBitArrayT, WordT>;
public:
using difference_type = std::ptrdiff_t;
using value_type = typename bit_array_base<ConcreteBitArrayT, WordT>::value_type;
using reference = typename bit_array_base<ConcreteBitArrayT, WordT>::const_reference;
using const_reference = typename bit_array_base<ConcreteBitArrayT, WordT>::const_reference;
using pointer = std::add_pointer_t<reference>;
using iterator = const_bit_iterator<ConcreteBitArrayT, WordT>;
using const_iterator = const_bit_iterator<ConcreteBitArrayT, WordT>;
using word_type = typename bit_array_base<ConcreteBitArrayT, WordT>::word_type;
using word_pointer = typename bit_array_base<ConcreteBitArrayT, WordT>::word_pointer;
using const_word_pointer = typename bit_array_base<ConcreteBitArrayT, WordT>::const_word_pointer;
/// @brief Singular iterator. Comparable, not dereferenceable.
HEDLEY_NO_THROW
constexpr const_bit_iterator() noexcept = default;
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
const_bit_iterator(const const_bit_iterator &) noexcept = default;
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
const_bit_iterator(const_bit_iterator &&) noexcept = default;
HEDLEY_NO_THROW
HEDLEY_NON_NULL()
inline constexpr explicit const_bit_iterator(const_word_pointer word_ptr) noexcept
: base{word_ptr} { }
HEDLEY_NO_THROW
HEDLEY_NON_NULL()
inline constexpr explicit const_bit_iterator(const_word_pointer word_ptr,
word_type mask) noexcept
: base{const_cast<word_pointer>(word_ptr), mask} { }
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
const_bit_iterator & operator=(const const_bit_iterator &) noexcept
= default;
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
const_bit_iterator & operator=(const_bit_iterator &&) noexcept
= default;
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr
explicit const_bit_iterator(const bit_iterator<ConcreteBitArrayT, WordT> & to_copy) noexcept
: base{to_copy.word_ptr_, to_copy.mask_} {}
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr
explicit const_bit_iterator(bit_iterator<ConcreteBitArrayT, WordT> && to_copy) noexcept
: base{to_copy.word_ptr_, to_copy.mask_} {}
~const_bit_iterator() = default;
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr const_reference operator*() const noexcept
{
using bitref = typename bit_array_base<ConcreteBitArrayT, WordT>::bit_reference;
return bitref{base::word_ptr_, base::mask_};
}
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr const_iterator & operator++() noexcept
{
base::increment();
return *this;
}
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr const_iterator operator++(int) noexcept
{
const_iterator tmp = *this;
base::increment();
return tmp;
}
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr const_iterator & operator--() noexcept
{
base::decrement();
return *this;
}
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr const_iterator operator--(int) noexcept
{
const_iterator tmp = *this;
base::decrement();
return tmp;
}
inline constexpr
const_iterator & operator+=(difference_type amt) noexcept
{
base::increment_by(amt);
return *this;
}
HEDLEY_NO_THROW
inline constexpr
const_iterator & operator-=(difference_type amt) noexcept
{
base::decrement_by(amt);
return *this;
}
HEDLEY_NO_THROW
inline constexpr
const_iterator operator+(difference_type amt) const noexcept
{
const_iterator tmp = *this;
return tmp += amt;
}
HEDLEY_NO_THROW
inline constexpr
const_iterator operator-(difference_type amt) const noexcept
{
const_iterator tmp = *this;
return tmp -= amt;
}
HEDLEY_NO_THROW
friend constexpr const_iterator operator+(difference_type amt,
const_iterator it) noexcept
{
return it + amt;
}
HEDLEY_NO_THROW
inline constexpr
const_reference operator[](difference_type i) const noexcept
{
return *(*this + i);
}
friend class bit_array_base<ConcreteBitArrayT, WordT>;
}; // class bit_array_base::const_bit_iterator
template <std::size_t Nbits, typename WordT = psnip_uint64_t>
class alignas(utils::max_align_v) static_bit_array final
: public bit_array_base<static_bit_array<Nbits, WordT>, WordT>
{
private:
using base = bit_array_base<static_bit_array<Nbits, WordT>, WordT>;
public:
using size_type = typename base::size_type;
using word_pointer = typename base::word_pointer;
using const_word_pointer = typename base::const_word_pointer;
using word_type = typename base::word_type;
static constexpr auto bits_per_word = base::bits_per_word;
private:
/// @brief the number of `word_type`s are being used to represent the
/// `size()` bits
static constexpr size_type data_length_ = utils::quotient_ceiling(Nbits, bits_per_word);
public:
HEDLEY_NO_THROW
constexpr static_bit_array(static_bit_array &&) noexcept = default;
HEDLEY_NO_THROW
constexpr static_bit_array(const static_bit_array &) noexcept = default;
~static_bit_array() = default;
HEDLEY_NO_THROW
constexpr static_bit_array & operator=(static_bit_array &&) noexcept = default;
HEDLEY_NO_THROW
constexpr static_bit_array & operator=(const static_bit_array &) noexcept = default;
/// @brief constructs a zeroed `static_bit_array` that holds `Nbits` bits
inline constexpr static_bit_array()
: data_{}
{
data_[data_length_] = base::sentinel;
}
/// @brief constructs a `static_bit_array` from the low bits of `val`
/// @param val the `val`
inline constexpr explicit static_bit_array(std::size_t val)
: data_{}
{
if constexpr (data_length_ > 0)
{
data_[0] = static_cast<word_type>(val);
// The partial limb is the last word. Word 0 is partial only when
// the whole array fits in one word.
if constexpr (data_length_ == 1 && Nbits % bits_per_word != 0)
data_[0] &= base::low_bits_mask(Nbits % bits_per_word);
}
data_[data_length_] = base::sentinel;
}
/// @brief direct access to the underlying data array
/// @return a pointer to the start of the data array
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr word_pointer data() noexcept
{
return static_cast<word_pointer>(__builtin_assume_aligned(std::data(data_), utils::max_align_v));
}
/// @brief direct access to the underlying data array
/// @return a pointer to the start of the data array
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr const_word_pointer data() const noexcept
{
return static_cast<const_word_pointer>(__builtin_assume_aligned(std::data(data_), utils::max_align_v));
}
/// @brief length of the underlying data array
/// @return the number of elements in the underlying array (excluding a
/// non-data sentinel element)
HEDLEY_ALWAYS_INLINE
constexpr size_type data_length() const noexcept
{
return data_length_;
}
/// @brief returns the number of bits
/// @return number of bits that the `static_bit_array` holds
/// @complexity `O(1)`
HEDLEY_NO_THROW
HEDLEY_PURE
HEDLEY_ALWAYS_INLINE
constexpr size_type size() const noexcept
{
return Nbits;
}
private:
alignas(utils::max_align_v) std::array<word_type, data_length_+1> data_;
};
template <typename WordT = psnip_uint64_t>
class dynamic_bit_array
: public bit_array_base<dynamic_bit_array<WordT>, WordT>
{
private:
using base = bit_array_base<dynamic_bit_array<WordT>, WordT>;
public:
using size_type = typename base::size_type;
using word_pointer = typename base::word_pointer;
using const_word_pointer = typename base::const_word_pointer;
using word_type = typename base::word_type;
static constexpr auto bits_per_word = base::bits_per_word;
private:
using allocator = aligned_allocator<word_type, utils::max_align_v>;
using unique_ptr = typename allocator::unique_ptr;
public:
/// @brief constructs a zeroed `dynamic_bit_array` that holds `nbits` bits
/// @param nbits the width in bits
/// @param alloc the `alloc`
/// @throws std::bad_alloc if allocating storage fails
inline explicit dynamic_bit_array(std::size_t nbits,
allocator alloc = allocator{})
: num_bits_{nbits},
data_length_{utils::quotient_ceiling(nbits, bits_per_word)},
data_{alloc.allocate_unique_ptr(data_length_+1)}
{
if (HEDLEY_UNLIKELY(data_ == nullptr)) throw std::bad_alloc{};
std::fill_n(data_.get(), data_length_, word_type{0});
data_[data_length_] = base::sentinel;
}
dynamic_bit_array(const dynamic_bit_array & other)
: num_bits_{other.num_bits_},
data_length_{other.data_length_},
data_{other.data_ ? allocator{}.allocate_unique_ptr(other.data_length_ + 1)
: unique_ptr{}}
{
if (data_)
std::copy_n(other.data_.get(), data_length_ + 1, data_.get());
}
HEDLEY_NO_THROW
dynamic_bit_array(dynamic_bit_array && other) noexcept
: num_bits_{std::exchange(other.num_bits_, 0)},
data_length_{std::exchange(other.data_length_, 0)},
data_{std::move(other.data_)}
{ }
dynamic_bit_array & operator=(const dynamic_bit_array & other)
{
if (this != &other)
{
dynamic_bit_array tmp(other);
*this = std::move(tmp);
}
return *this;
}
HEDLEY_NO_THROW
dynamic_bit_array & operator=(dynamic_bit_array && other) noexcept
{
if (this != &other)
{
wipe();
data_ = std::move(other.data_);
num_bits_ = std::exchange(other.num_bits_, 0);
data_length_ = std::exchange(other.data_length_, 0);
}
return *this;
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
~dynamic_bit_array() noexcept
{
wipe();
}
/// @brief direct access to the underlying data array
/// @return a pointer to the start of the data array
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
word_pointer data() noexcept
{
auto *raw = data_.get();
if (raw == nullptr) return nullptr;
return static_cast<word_pointer>(__builtin_assume_aligned(raw, utils::max_align_v));
}
/// @brief direct access to the underlying data array
/// @param i the `i`
/// @return a pointer to the start of the data array
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
word_type & data(std::size_t i) noexcept
{
return this->data()[i];
}
/// @brief direct access to the underlying data array
/// @return a pointer to the start of the data array
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
const_word_pointer data() const noexcept
{
auto *raw = data_.get();
if (raw == nullptr) return nullptr;
return static_cast<const_word_pointer>(__builtin_assume_aligned(raw, utils::max_align_v));
}
/// @brief direct access to the underlying data array
/// @param i the `i`
/// @return a pointer to the start of the data array
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
const word_type & data(std::size_t i) const noexcept
{
return this->data()[i];
}
/// @brief length of the underlying data array
/// @return the number of elements in the underlying array (excluding a
/// non-data sentinel element)
HEDLEY_ALWAYS_INLINE
constexpr size_type data_length() const noexcept
{
return data_length_;
}
/// @brief returns the number of bits
/// @return number of bits that the `dynamic_bit_array` holds
/// @complexity `O(1)`
HEDLEY_NO_THROW
HEDLEY_PURE
HEDLEY_ALWAYS_INLINE
constexpr size_type size() const noexcept
{
return num_bits_;
}
private:
/// @brief Store zeros through `volatile` so the wipe is not deleted as a dead store.
HEDLEY_NO_THROW
void wipe() noexcept
{
if (!data_) return;
volatile word_type *p = data_.get();
for (size_type i = 0; i < data_length_ + 1; ++i)
p[i] = word_type{0};
}
size_type num_bits_;
/// @brief the number of `word_type`s are being used to represent the
/// `size()` bits
size_type data_length_;
unique_ptr data_;
};
/// @brief Exchanges the bits named by two `dynamic_bit_array` proxies.
/// @tparam WordT word used to pack bits
/// @param lhs the left-hand operand
/// @param rhs the right-hand operand
template <typename WordT>
HEDLEY_NO_THROW
inline constexpr void swap(typename dynamic_bit_array<WordT>::reference lhs,
typename dynamic_bit_array<WordT>::reference rhs) noexcept
{
bool tmp = lhs;
lhs = static_cast<bool>(rhs);
rhs = tmp;
}
/// @brief Exchanges the bits named by two `static_bit_array` proxies.
/// @tparam Nbits width in bits
/// @tparam WordT word used to pack bits
/// @param lhs the left-hand operand
/// @param rhs the right-hand operand
template <std::size_t Nbits,
typename WordT>
HEDLEY_NO_THROW
inline constexpr void swap(typename static_bit_array<Nbits, WordT>::reference lhs,
typename static_bit_array<Nbits, WordT>::reference rhs) noexcept
{
bool tmp = lhs;
lhs = static_cast<bool>(rhs);
rhs = tmp;
}
namespace utils
{
template <typename ChildT,
typename WordT>
struct is_bit_array<bit_array_base<ChildT, WordT>> : std::true_type {};
template <std::size_t Nbits,
typename WordT>
struct is_bit_array<static_bit_array<Nbits, WordT>> : std::true_type {};
template <typename WordT>
struct is_bit_array<dynamic_bit_array<WordT>> : std::true_type {};
} // namespace utils
} // namespace dpf
namespace std
{
template <typename ConcreteBitArrayT,
typename WordT>
struct iterator_traits<dpf::bit_iterator<ConcreteBitArrayT, WordT>>
{
private:
using type = dpf::bit_iterator<ConcreteBitArrayT, WordT>;
public:
using iterator_category = typename type::iterator_category;
using difference_type = typename type::difference_type;
using value_type = typename type::value_type;
using reference = typename type::reference;
using const_reference = typename type::const_reference;
using pointer = typename type::pointer;
};
template <typename ConcreteBitArrayT,
typename WordT>
struct iterator_traits<dpf::const_bit_iterator<ConcreteBitArrayT, WordT>>
{
private:
using type = dpf::const_bit_iterator<ConcreteBitArrayT, WordT>;
public:
using iterator_category = typename type::iterator_category;
using difference_type = typename type::difference_type;
using value_type = typename type::value_type;
using reference = typename type::reference;
using const_reference = typename type::const_reference;
using pointer = typename type::pointer;
};
} // namespace std
#endif // LIBDPF_INCLUDE_DPF_BIT_ARRAY_HPP__