How to access a slice of packed_bits<> as a std::bitset<>?
bit-manipulation, c++, c++11, templates, variadic-templates
Solution
I wouldn't have `get` return a `bitset`, because each `bitset` needs to manage its own memory.
Instead, I'd use a `bitset` internally to manage all of the bits, and create `bitset::reference`-like individual bit references, and `bitset`-like "slices", which `get` can return.
A `bitslice` would have a pointer back to the original `packed_bits`, and would know the offset where it starts, and how wide it is. It's `references` to individual bits would be `references` from the original `packed_bits`, which are `references` from the internal `bitset`, possibly.
Your `Size` is 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:
template<unsigned... Vs>
struct seq {};
is a type from which you can extract an arbitrary length list of `unsigned int`s, yet can be passed as a single parameter to a template.
As a first step, write `bit_slice<offset, length>`, which takes a `std::bitset<size>` and produces `bitset::reference`s to individual bits, where `bit_slice<offset, length>[n]` is the same as `bitset[n+offset]`.
Optionally, `bit_slice` could store `offset` as a run-time parameter (because `offset` as 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 of `packed_bits` is feasible. `get<n, offset=0>( packed_bits<a,b,c,...>& )` returns a `bit_slice<x>` determined by indexing the `packed_bits` sizes, with an `offset` determined by adding the first n-1 `packed_bits` sizes, which is then constructed from the internal `bitset` of the `packed_bits`.
Make sense?
Apparently not. Here is a quick `bit_slice` that represents some sub-range of bits within a `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's 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 a `bit_slice` with runtime offset-and-source size. This will be less efficient, but easier to program against.
Problem
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 a `packet_bits<>` and returns a `bit_slice<>` by value instead of `bitset<>` 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; } ```