I am attempting to implement a packed_bits class using variadic templates and std::bitset.
In particular, I am running into problems writing a get function which returns a reference to a subset of the member m_bits which contains all the packed bits. The function should be analogous to std::get for std::tuple.
It should act as an reference overlay so I can manipulate a subset of packed_bits.
For instance,
using my_bits = packed_bits<8,16,4>;
my_bits b;
std::bitset< 8 >& s0 = get<0>( b );
std::bitset< 16 >& s1 = get<1>( b );
std::bitset< 4 >& s2 = get<2>( b );
UPDATE
Below is the code that has been rewritten according to Yakk’s recommendations below. I am stuck at the point of his last paragraph: not sure how to glue together the individual references into one bitset-like reference. Thinking/working on that last part now.
UPDATE 2
Okay, my new approach is going to be to let bit_slice<> do the bulk of the work:
- it is meant to be short-lived
- it will publicly subclass
std::bitset<length>, acting as a temporary buffer - on construction, it will copy from
packed_bits<>& m_parent; - on destruction, it will write to
m_parent - in addition to the reference via
m_parent, it must also know offset, length get<>will become a free-function which takes apacket_bits<>and returns abit_slice<>by value instead ofbitset<>by reference
There are various short-comings to this approach:
bit_slice<>has to be relatively short-lived to avoid aliasing issues, since we only update on construction and destruction- we must avoid multiple overlapping references while coding (whether threaded or not)
- we will be prone to slicing if we attempt to hold a pointer to the base class when we have an instance of the child class
but I think this will be sufficient for my needs. I will post the finished code when it is complete.
UPDATE 3
After fighting with the compiler, I think I have a basic version working. Unfortunately, I could not get the free-floating ::get() to compile properly: BROKEN shows the spot. Otherwise, I think it’s working.
Many thanks to Yakk for his suggestions: the code below is about 90%+ based on his comments.
UPDATE 4
Free-floating ::get() fixed.
UPDATE 5
As suggested by Yakk, I have eliminated the copy. bit_slice<> will read on get_value() and write on set_value(). Probably 90%+ of my calls will be through these interfaces anyways, so no need to subclass/copy.
No more dirtiness.
CODE
#include <cassert>
#include <bitset>
#include <iostream>
// ----------------------------------------------------------------------------
template<unsigned... Args>
struct Add { enum { val = 0 }; };
template<unsigned I,unsigned... Args>
struct Add<I,Args...> { enum { val = I + Add<Args...>::val }; };
template<int IDX,unsigned... Args>
struct Offset { enum { val = 0 }; };
template<int IDX,unsigned I,unsigned... Args>
struct Offset<IDX,I,Args...> {
enum {
val = IDX>0 ? I + Offset<IDX-1,Args...>::val : Offset<IDX-1,Args...>::val
};
};
template<int IDX,unsigned... Args>
struct Length { enum { val = 0 }; };
template<int IDX,unsigned I,unsigned... Args>
struct Length<IDX,I,Args...> {
enum {
val = IDX==0 ? I : Length<IDX-1,Args...>::val
};
};
// ----------------------------------------------------------------------------
template<unsigned... N_Bits>
struct seq
{
static const unsigned total_bits = Add<N_Bits...>::val;
static const unsigned size = sizeof...( N_Bits );
template<int IDX>
struct offset
{
enum { val = Offset<IDX,N_Bits...>::val };
};
template<int IDX>
struct length
{
enum { val = Length<IDX,N_Bits...>::val };
};
};
// ----------------------------------------------------------------------------
template<unsigned offset,unsigned length,typename PACKED_BITS>
struct bit_slice
{
PACKED_BITS& m_parent;
bit_slice( PACKED_BITS& t ) :
m_parent( t )
{
}
~bit_slice()
{
}
bit_slice( bit_slice const& rhs ) :
m_parent( rhs.m_parent )
{ }
bit_slice& operator=( bit_slice const& rhs ) = delete;
template<typename U_TYPE>
void set_value( U_TYPE u )
{
for ( unsigned i=0; i<length; ++i )
{
m_parent[offset+i] = u&1;
u >>= 1;
}
}
template<typename U_TYPE>
U_TYPE get_value() const
{
U_TYPE x = 0;
for ( int i=length-1; i>=0; --i )
{
if ( m_parent[offset+i] )
++x;
if ( i!=0 )
x <<= 1;
}
return x;
}
};
template<typename SEQ>
struct packed_bits :
public std::bitset< SEQ::total_bits >
{
using bs_type = std::bitset< SEQ::total_bits >;
using reference = typename bs_type::reference;
template<int IDX> using offset = typename SEQ::template offset<IDX>;
template<int IDX> using length = typename SEQ::template length<IDX>;
template<int IDX> using slice_type =
bit_slice<offset<IDX>::val,length<IDX>::val,packed_bits>;
template<int IDX>
slice_type<IDX> get()
{
return slice_type<IDX>( *this );
}
};
template<int IDX,typename T>
typename T::template slice_type<IDX>
get( T& t )
{
return t.get<IDX>();
};
// ----------------------------------------------------------------------------
int main( int argc, char* argv[] )
{
using my_seq = seq<8,16,4,8,4>;
using my_bits = packed_bits<my_seq>;
using my_slice = bit_slice<8,16,my_bits>;
using slice_1 =
bit_slice<my_bits::offset<1>::val,my_bits::length<1>::val,my_bits>;
my_bits b;
my_slice s( b );
slice_1 s1( b );
assert( sizeof( b )==8 );
assert( my_seq::total_bits==40 );
assert( my_seq::size==5 );
assert( my_seq::offset<0>::val==0 );
assert( my_seq::offset<1>::val==8 );
assert( my_seq::offset<2>::val==24 );
assert( my_seq::offset<3>::val==28 );
assert( my_seq::offset<4>::val==36 );
assert( my_seq::length<0>::val==8 );
assert( my_seq::length<1>::val==16 );
assert( my_seq::length<2>::val==4 );
assert( my_seq::length<3>::val==8 );
assert( my_seq::length<4>::val==4 );
{
auto s2 = b.get<2>();
}
{
auto s2 = ::get<2>( b );
s2.set_value( 25 ); // 25==11001, but only 4 bits, so we take 1001
assert( s2.get_value<unsigned>()==9 );
}
return 0;
}
I wouldn’t have
getreturn abitset, because eachbitsetneeds to manage its own memory.Instead, I’d use a
bitsetinternally to manage all of the bits, and createbitset::reference-like individual bit references, andbitset-like “slices”, whichgetcan return.A
bitslicewould have a pointer back to the originalpacked_bits, and would know the offset where it starts, and how wide it is. It’sreferencesto individual bits would bereferencesfrom the originalpacked_bits, which arereferencesfrom the internalbitset, possibly.Your
Sizeis redundant —sizeof...(pack)tells you how many elements are in the pack.I’d pack up the sizes of the slices into a seqence so you can pass it around easier. Ie:
is a type from which you can extract an arbitrary length list of
unsigned ints, yet can be passed as a single parameter to a template.As a first step, write
bit_slice<offset, length>, which takes astd::bitset<size>and producesbitset::references to individual bits, wherebit_slice<offset, length>[n]is the same asbitset[n+offset].Optionally,
bit_slicecould storeoffsetas a run-time parameter (becauseoffsetas a compile-time parameter is just an optimization, and not that strong of one I suspect).Once you have
bit_slice, working on the tuple syntax ofpacked_bitsis feasible.get<n, offset=0>( packed_bits<a,b,c,...>& )returns abit_slice<x>determined by indexing thepacked_bitssizes, with anoffsetdetermined by adding the first n-1packed_bitssizes, which is then constructed from the internalbitsetof thepacked_bits.Make sense?
Apparently not. Here is a quick
bit_slicethat represents some sub-range of bits within astd::bitset.Another useful class would be a
bit_slicewith runtime offset-and-source size. This will be less efficient, but easier to program against.