How to access a piece of packed_bits <> as std :: bitset <>?
I am trying to implement a class packed_bits
using variadic templates and std::bitset
.
In particular, I am having trouble writing a function get
that returns a reference to a subset of a member m_bits
that contains all of the packed bits. The function should be the same std::get
for std::tuple
.
It has to act as a reference overlay, so I can manipulate a subset packed_bits
.
For example,
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 to follow Yakk's guidelines below . I'm stuck at the point of his last paragraph: not sure how to glue the individual links into one bittet link. Think / work on this last part now.
UPDATE 2
Ok, my new approach is to let the bit_slice<>
bulk of the work get done:
- it must be short-lived
- it will be publicly subclass
std::bitset<length>
, acting like a temporary buffer - when building, it will copy from
packed_bits<>& m_parent;
- when destroyed, it will write
m_parent
- in addition to linking through
m_parent
, it must also know the offset, length -
get<>
will become a free function that takespacket_bits<>
and returnsbit_slice<>
by value instead ofbitset<>
by reference
There are various disadvantages to this approach:
-
bit_slice<>
should be relatively short lived to avoid alias problems as we only update construct and destroy. - we should avoid multiple overlapping references during encoding (whether with a stream or not)
- we will be prone to slicing if we try to hold a pointer to the base class when we have an instance of the child class
but I think this will be enough for my needs. I'll send the finished code when it's complete.
UPDATE 3
After struggling with the compiler, I think I have a basic version. Unfortunately I was not able to compile the free-floating ::get()
: BROKEN
shows the place correctly . Otherwise, I think it works.
Many thanks to Jakk for his suggestions: the code below is around 90% + based on his comments.
UPDATE 4
Free floating ::get()
fixed.
UPDATE 5
As evident from Yakk, I deleted the copy. bit_slice<>
will read in get_value()
and write in set_value()
. Probably 90% + of my calls will go through these interfaces, so there is no need for subclasses / copies.
More dirt.
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; }
source share
I would not have get
return a bitset
, because everyone bitset
has to manage their own memory.
Instead, I would use bitset
internally to manipulate all the bits and create a bitset::reference
-like individual bit references and bitset
-like "slices" that it get
can return.
A bitslice
will have a pointer back to the original packed_bits
and will know the offset where it starts and how large it is. This references
for individual bits will be references
from the original packed_bits
, which references
from the internal bitset
, perhaps.
Yours Size
is redundant - sizeof...(pack)
tells you how many items are in the package.
I would pack the slice sizes into a sequence to keep things simpler. I.e:
template<unsigned... Vs> struct seq {};
is a type from which you can extract an arbitrary list of lengths from unsigned int
s, but can be passed as one parameter to the template.
As a first step, write bit_slice<offset, length>
which takes std::bitset<size>
and creates bitset::reference
for the individual bits where is the bit_slice<offset, length>[n]
same as bitset[n+offset]
.
bit_slice
Can optionally be stored offset
as a run-time parameter (since offset
as a compile-time parameter this is just an optimization, not as strong as I suspect).
Once you have it bit_slice
, working on the tuple syntax packed_bits
is possible. get<n, offset=0>( packed_bits<a,b,c,...>& )
returns a bit_slice<x>
specified by indexing the dimensions packed_bits
, c offset
specified by adding the first n-1 dimensions packed_bits
, which is then constructed from the inner one bitset
packed_bits
.
Make sense?
Apparently not. Here's a quick bit_slice
one that represents some sub-range of bits within std::bitset
.
#include <bitset> template<unsigned Width, unsigned Offset, std::size_t SrcBitWidth> struct bit_slice { private: std::bitset<SrcBitWidth>* bits; public: // cast to `bitset`: operator std::bitset<Width>() const { std::bitset<Width> retval; for(unsigned i = 0; i < Offset; ++i) { retval[i] = (*this)[i]; } return retval; } typedef typename std::bitset<SrcBitWidth>::reference reference; reference operator[]( size_t pos ) { // TODO: check that pos < Width? return (*bits)[pos+Offset]; } constexpr bool operator[]( size_t pos ) const { // TODO: check that pos < Width? return (*bits)[pos+Offset]; } typedef bit_slice<Width, Offset, SrcBitWidth> self_type; // can be assigned to from any bit_slice with the same width: template<unsigned O_Offset, unsigned O_SrcBitWidth> self_type& operator=( bit_slice<Width, O_Offset, O_SrcBitWidth>&& o ) { for (unsigned i = 0; i < Width; ++i ) { (*this)[i] = o[i]; } return *this; } // can be assigned from a `std::bitset<Width>` of the same size: self_type& operator=( std::bitset<Width> const& o ) { for (unsigned i = 0; i < Width; ++i ) { (*this)[i] = o[i]; } return *this; } explicit bit_slice( std::bitset<SrcBitWidth>& src ):bits(&src) {} bit_slice( self_type const& ) = default; bit_slice( self_type&& ) = default; bit_slice( self_type&o ):bit_slice( const_cast<self_type const&>(o)) {} // I suspect, but am not certain, the the default move/copy ctor would do... // dtor not needed, as there is nothing to destroy // TODO: reimplement rest of std::bitset interface that you care about }; template<unsigned offset, unsigned width, std::size_t src_width> bit_slice< width, offset, src_width > make_slice( std::bitset<src_width>& src ) { return bit_slice< width, offset, src_width >(src); } #include <iostream> int main() { std::bitset<16> bits; bits[8] = true; auto slice = make_slice< 8, 8 >( bits ); bool b0 = slice[0]; bool b1 = slice[1]; std::cout << b0 << b1 << "\n"; // should output 10 }
Another useful class would be bit_slice
with offset and runtime size. It will be less efficient, but easier to program.
source share