split and concat for std::bitset
When working with binary data, one often needs to split a larger std::bitset into several smaller std::bitsets. One example is if you have a memory-mapped register read into a std::bitset.
The reverse is also needed to concatenate sub-bitsets into a larger bitset.
Currently, splitting and concatenating these requires using mask and shift operations on the bitset, which can be complicated to read and thus, error-prone. I find that the usability of std::bitset is limited as you still need to do a lot of unnecessary bit-twiddling.
I recently authored two utility functions (split and concat) for std::bitset which does exactly this.
template<size_t... slices, size_t num_bits_in_source>
constexpr auto split(std::bitset<num_bits_in_source> source)
template<size_t num_bits, size_t... num_bits_rest>
constexpr auto concat(std::bitset<num_bits> bits,
std::bitset<num_bits_rest>... rest)
Isolating bits in registers
The use of these functions could be as follows:
std::bitset<8> uxlcr{"10010111"};
auto [divisor_latch_access, break_ctrl, parity, parity_enable,
stopbit, word_length] = split<1,1,2,1,1,2>(uxlcr);
word_length = 0b10;
uxlcr = concat(divisor_latch_access, break_ctrl, parity,
parity_enable, stopbit, word_length);
Working with base64-encoded data
Another use-case is extracting sextets to generate base-64 encoded data:
To base64
const auto datablock = std::bitset<24>{...};
const auto sextets = std::split<6,6,6,6>(data);
const auto ascii = sextets2chars(sextets);
From base64
const auto chars = std::array<char, 4>{...};
const auto sextets = chars2sextets(chars);
const auto binary = std::concat(sextets);
sextets will in these examples be a tuple of 4 std::bitset<6>
Just thought I would share.
Code
The code can be seen below or in compiler explorer.
#include <iostream>
#include <type_traits>
#include <utility>
#include <bitset>
template<size_t NumBitsLhs, size_t NumBitsRhs>
constexpr auto concat(std::bitset<NumBitsLhs> lhs, std::bitset<NumBitsRhs> rhs)
{
std::bitset<NumBitsLhs + NumBitsRhs> res;
for (int index = 0; index < NumBitsRhs; index++) {
res[index] = rhs[index];
}
for (int index = 0; index < NumBitsLhs; index++) {
res[index + NumBitsRhs] = lhs[index];
}
return res;
}
template<size_t NumBits, size_t... NumBitsRest>
constexpr auto concat(std::bitset<NumBits> bits, std::bitset<NumBitsRest>... rest)
{
return concat(bits, concat(rest...));
}
template<size_t Offset, size_t NumBits, size_t NumBitsInSource>
constexpr auto slice(std::bitset<NumBitsInSource> source)
{
std::bitset<NumBits> dst;
for (int index = 0; index < NumBits; index++) {
dst[index] = source[index + Offset];
}
return dst;
}
template<size_t NumBitsInSource, size_t NumBits1>
constexpr auto split2(std::bitset<NumBitsInSource> source)
{
std::bitset<NumBits1> dst;
for (int index = 0; index < NumBits1; index++) {
dst[NumBits1 - index - 1] = source[NumBitsInSource - index - 1];
}
std::bitset<NumBitsInSource - NumBits1> dst2;
for (int index = 0; index < NumBitsInSource - NumBits1; index++)
{
dst2[NumBitsInSource - NumBits1 - index - 1] = source[NumBitsInSource - NumBits1 - index - 1];
}
return std::make_tuple(dst, dst2);
}
template<size_t NumBitsInSource, size_t Slice, size_t... Slices>
constexpr auto split_impl(std::bitset<NumBitsInSource> source)
{
const auto spl = split2<NumBitsInSource, Slice>(source);
if constexpr(sizeof...(Slices) == 1) {
return spl;
} else {
return std::tuple_cat(
std::make_tuple(std::get<0>(spl)),
split_impl<NumBitsInSource - Slice, Slices...>(std::get<1>(spl))
);
}
}
template<size_t... Slices, size_t NumBitsInSource>
constexpr auto split(std::bitset<NumBitsInSource> source)
{
static_assert( (Slices + ...) == NumBitsInSource, "All slice length must add up to source bit length");
return split_impl<NumBitsInSource, Slices...>(source);
}
int main(int, char* argv[])
{
std::bitset<1> bs0{"1"};
std::bitset<2> bs1{"00"};
std::bitset<3> bs2{"101"};
std::bitset<4> bs3{"1111"};
std::bitset<5> bs4{"00000"};
std::cout << "bs0 " << bs0 << "\n"; // bs0 1
std::cout << "bs1 " << bs1 << "\n"; // bs1 00
std::cout << "bs2 " << bs2 << "\n"; // bs2 101
std::cout << "bs3 " << bs3 << "\n"; // bs3 1111
std::cout << "bs4 " << bs4 << "\n"; // bs4 00000
const auto bits = concat(bs0, bs1, bs2, bs3, bs4);
std::cout << "concatenated: " << bits << "\n"; // concatenated: 100101111100000
const auto [bs0o, bs1o, bs2o, bs3o, bs4o] = split<1,2,3,4,5>(bits);
std::cout << "Re-split:\n";
std::cout << "bs0 " << bs0o << "\n"; // bs0 1
std::cout << "bs1 " << bs1o << "\n"; // bs1 00
std::cout << "bs2 " << bs2o << "\n"; // bs2 101
std::cout << "bs3 " << bs3o << "\n"; // bs3 1111
std::cout << "bs4 " << bs4o << "\n"; // bs4 00000
static_assert(std::get<0>(split<1,2,3>(std::bitset<6>("101001"))) == std::bitset<1>(0b1));
static_assert(std::get<1>(split<1,2,3>(std::bitset<6>("101001"))) == std::bitset<2>(0b01));
static_assert(std::get<2>(split<1,2,3>(std::bitset<6>("101001"))) == std::bitset<3>(0b001));
}