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 ));
}