Skip to content

Commit

Permalink
✨ Add bitset::to<T> for conversion to unsigned integral
Browse files Browse the repository at this point in the history
Remove `to_uint64_t`; it's superseded.
  • Loading branch information
elbeno authored and lukevalenty committed Apr 18, 2024
1 parent 7808e7a commit 014dddd
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 35 deletions.
13 changes: 12 additions & 1 deletion docs/bitset.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ platform.

* Stream input and output operators are not implemented.
* A `std::hash` specialization is not implemented.
* `to_string`, `to_ulong` and `to_ullong` are not implemented; `to_uint64_t` is available.
* `to_string`, `to_ulong` and `to_ullong` are not implemented

A bitset has two template parameters: the size of the bitset and the storage
element type to use. The storage element type must be unsigned.
Expand Down Expand Up @@ -57,3 +57,14 @@ auto bs1 = stdx::bitset<8>{"1100"sv}; // bits 2 and 3 set
auto bs2 = stdx::bitset<8>{"1100"sv, 0, 2}; // bits 0 and 1 set
auto bs3 = stdx::bitset<8>{"AABB"sv, 0, 2, 'A'}; // bits 0 and 1 set
----

To convert a bitset back to an integral type, `to<T>` is available where `T` is
an unsigned integral type large enough to fit all the bits. And `to_natural`
produces the smallest such unsigned integral type.

[source,cpp]
----
auto bs = stdx::bitset<11>{0b101}; // 11 bits, value 5
auto i = bs.to<std::uint64_t>(); // 5 (a std::uint64_t)
auto j = bs.to_natural(); // 5 (a std::uint16_t)
----
59 changes: 34 additions & 25 deletions include/stdx/bitset.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <stdx/bit.hpp>
#include <stdx/compiler.hpp>
#include <stdx/concepts.hpp>
#include <stdx/type_traits.hpp>
#include <stdx/udls.hpp>

Expand All @@ -22,6 +23,22 @@ struct all_bits_t {};
constexpr inline auto all_bits = all_bits_t{};

namespace detail {
template <std::size_t N, typename S> CONSTEVAL auto select_storage() {
if constexpr (not std::is_same_v<S, void>) {
static_assert(std::is_unsigned_v<S>,
"Underlying storage of bitset must be an unsigned type");
return S{};
} else if constexpr (N <= std::numeric_limits<std::uint8_t>::digits) {
return std::uint8_t{};
} else if constexpr (N <= std::numeric_limits<std::uint16_t>::digits) {
return std::uint16_t{};
} else if constexpr (N <= std::numeric_limits<std::uint32_t>::digits) {
return std::uint32_t{};
} else {
return std::uint64_t{};
}
}

template <std::size_t N, typename StorageElem> class bitset {
constexpr static auto storage_elem_size =
std::numeric_limits<StorageElem>::digits;
Expand Down Expand Up @@ -149,23 +166,31 @@ template <std::size_t N, typename StorageElem> class bitset {
}
}

[[nodiscard]] constexpr auto to_uint64_t() const -> std::uint64_t {
if constexpr (std::is_same_v<StorageElem, std::uint64_t>) {
template <typename T> [[nodiscard]] constexpr auto to() const -> T {
static_assert(unsigned_integral<T>,
"Conversion must be to an unsigned integral type!");
static_assert(N <= std::numeric_limits<T>::digits,
"Bitset too big for conversion to T");
if constexpr (std::is_same_v<StorageElem, T>) {
return storage[0] & lastmask;
} else {
static_assert(N <= std::numeric_limits<std::uint64_t>::digits,
"Bitset too big for conversion to std::uint64_t");
std::uint64_t result{};
for (auto i = std::size_t{}; i < storage_size - 1; ++i) {
result <<= storage_elem_size;

T result{highbits()};
for (auto i = storage_size - 2u; i < storage_size; --i) {
result = static_cast<T>(result << storage_elem_size);
result |= storage[i];
}
result <<= storage_elem_size;
result |= highbits();
return result;
}
}

[[nodiscard]] constexpr auto to_natural() const {
using T = decltype(select_storage<N, void>());
static_assert(N <= std::numeric_limits<T>::digits,
"Bitset too big for conversion to T");
return to<T>();
}

constexpr static std::integral_constant<std::size_t, N> size{};

[[nodiscard]] constexpr auto operator[](std::size_t pos) const -> bool {
Expand Down Expand Up @@ -387,22 +412,6 @@ constexpr auto for_each(F &&f, bitset<M, S> const &...bs) -> F {
return f;
}
}

template <std::size_t N, typename S> CONSTEVAL auto select_storage() {
if constexpr (not std::is_same_v<S, void>) {
static_assert(std::is_unsigned_v<S>,
"Underlying storage of bitset must be an unsigned type");
return S{};
} else if constexpr (N <= std::numeric_limits<std::uint8_t>::digits) {
return std::uint8_t{};
} else if constexpr (N <= std::numeric_limits<std::uint16_t>::digits) {
return std::uint16_t{};
} else if constexpr (N <= std::numeric_limits<std::uint32_t>::digits) {
return std::uint32_t{};
} else {
return std::uint64_t{};
}
}
} // namespace detail

template <std::size_t N, typename StorageElem = void>
Expand Down
33 changes: 26 additions & 7 deletions test/bitset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,31 @@ TEMPLATE_TEST_CASE("construct with a substring", "[bitset]", std::uint8_t,
stdx::bitset<4, TestType>{0b1010ul});
}

TEMPLATE_TEST_CASE("convert to uint64_t", "[bitset]", std::uint8_t,
std::uint16_t, std::uint32_t, std::uint64_t) {
TEMPLATE_TEST_CASE("convert to unsigned integral type (same underlying type)",
"[bitset]", std::uint8_t, std::uint16_t, std::uint32_t,
std::uint64_t) {
constexpr auto bs = stdx::bitset<3, TestType>{255ul};
constexpr auto val = bs.to_uint64_t();
static_assert(std::is_same_v<decltype(val), std::uint64_t const>);
static_assert(val == 7ul);
constexpr auto val = bs.template to<TestType>();
static_assert(std::is_same_v<decltype(val), TestType const>);
static_assert(val == 7u);
}

TEMPLATE_TEST_CASE(
"convert to unsigned integral type (different underlying type)", "[bitset]",
std::uint16_t, std::uint32_t, std::uint64_t) {
constexpr auto bs =
stdx::bitset<11, std::uint8_t>{stdx::place_bits, 3, 7, 10};
constexpr auto val = bs.to<TestType>();
static_assert(std::is_same_v<decltype(val), TestType const>);
static_assert(val == 0b100'1000'1000u);
}

TEST_CASE("convert to type that fits", "[bitset]") {
constexpr auto bs =
stdx::bitset<11, std::uint8_t>{stdx::place_bits, 3, 7, 10};
constexpr auto val = bs.to_natural();
static_assert(std::is_same_v<decltype(val), std::uint16_t const>);
static_assert(val == 0b100'1000'1000u);
}

TEMPLATE_TEST_CASE("all", "[bitset]", std::uint8_t, std::uint16_t,
Expand Down Expand Up @@ -366,12 +385,12 @@ TEMPLATE_TEST_CASE("set/reset all bits with size at type capacity", "[bitset]",

constexpr auto bs1 = stdx::bitset<sz, TestType>{stdx::all_bits};
static_assert(bs1.all());
static_assert(bs1.to_uint64_t() == expected);
static_assert(bs1.template to<TestType>() == expected);

auto bs2 = stdx::bitset<sz, TestType>{};
bs2.set();
CHECK(bs2.all());
CHECK(bs2.to_uint64_t() == expected);
CHECK(bs2.template to<TestType>() == expected);
bs2.reset();
CHECK(bs2.none());
}
Expand Down
4 changes: 2 additions & 2 deletions test/fail/bitset_to_uint64_over_64_bits.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#include <stdx/bitset.hpp>

// EXPECT: Bitset too big for conversion to std::uint64_t
// EXPECT: Bitset too big for conversion to T

auto main() -> int {
auto b = stdx::bitset<65, unsigned char>{};
auto i = b.to_uint64_t();
auto i = b.to<std::uint64_t>();
}

0 comments on commit 014dddd

Please sign in to comment.