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 takes packet_bits<>

    and returns bit_slice<>

    by value instead of bitset<>

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

      

+3


source share


1 answer


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.

+2


source







All Articles