tags:

views:

237

answers:

6

I have some struct containig a bitfield, which may vary in size. Example:

struct BitfieldSmallBase {
    uint8_t a:2;
    uint8_t b:3;
    ....
}

struct BitfieldLargeBase {
    uint8_t a:4;
    uint8_t b:5;
    ....
}

and a union to access all bits at once:

template<typename T>
union Bitfield 
{
    T bits;
    uint8_t all;    // <-------------  Here is the problem

    bool operator & (Bitfield<T> x) const {
        return !!(all & x.all);
    }
    Bitfield<T> operator + (Bitfield<T> x) const {
        Bitfield<T> temp;
        temp.all = all + x.all;   //works, because I can assume no overflow will happen
        return temp;
    }
    ....
}

typedef Bitfield<BitfieldSmallBase> BitfieldSmall;
typedef Bitfield<BitfieldLargeBase> BitfieldLarge;

The problem is: For some bitfield base classes, an uint8_t is not sufficient. BitfieldSmall does fit into a uint8_t, but BitfieldLarge does not. The data needs to be packed as tightly as possible (it will be handled by SSE instructions later), so always using uint16_t is out of question. Is there a way to declare the "all" field with an integral type, whose size is the same as the bitfield? Or another way to access bits as a whole?

I can of course forego the use of the template and declare every kind of bitfield explicitly, but I would like to avoid code repetition (there is quite a list of operators und member functions).

+3  A: 

You could make the integral type a template parameter as well.

template<typename T, typename U>
union Bitfield 
{
    T bits;
    U all;
}

typedef Bitfield<BitfieldSmallBase, uint8_t>  BitfieldSmall;
typedef Bitfield<BitfieldLargeBase, uint16_t> BitfieldLarge;
John Kugelman
Sometimes the solution is so simple :-)
drhirsch
+1  A: 

I've learnt the hard way that whilst the bit width on vars that you're using is a convenient way of getting the compiler to do your masking and shifting for you, you cannot make assumptions about the order and padding of the members in the struct. Its compiler dependent and the compiler really does change the order and such dependent upon the other code in your project.

If you want to treat a byte as discrete fields, you really have to do it the hard way.

Will
Portability is secondary, because I need to use SSE anyway, so this is limited to x86_64 (and possibly x86). For the compilers I am using (gcc, icc) I can make safe assumptions about the layout of the structures, it is defined in the ABI.
drhirsch
but it is not guaranteed to be the same in the two compilers. Or in the *next* version of these compilers.
jalf
Both icc and gcc comply to the x86_64 ABI. There have been ABI breakages from time to time, but they usually try very hard to minimize the impact. If you find a case, where the structure layout of icc differs from gcc, you may even file a bug, because icc trys hard to be as gcc-compatible as possible.
drhirsch
Well it was GCC and x86_64 where I got the problems myself, and I think I was using 4.4.something. Just my data point.
Will
Can you elaborate where the problem was? I know bitfields are shunned by even some of the gcc developers, but sometimes they are handy for low level stuff
drhirsch
+1  A: 

you can use template metaprogramming to define a template function that maps from BitfieldSmallBase, BitfieldLargeBase, etc into another type - uint8_t by default and to uint16_t for BitfieldLargeBase as a template specialization and then use that like this:

union Bitfield 
{
    T bits;
    typename F<T>::holder_type all;
};
catwalk
Another nice solution. But I prefer John Kugelmans solution, its even simpler.
drhirsch
John has a better solution if there are few of BitfieldSmall* classes; mine is better if there are many of them but one or two stand out and require, say uint16_t vs default uint8_t.
catwalk
There are 3 bitfield classes, one of them two-byte sized (so far..). Because the declaration of F<T>::holder_type and its specialization is more than 3 lines, Johns solution is better in this case, albeit by a very slight margin ;-)
drhirsch
+1  A: 

You might want to consider std::bitset or boost::dynamic_bitset rather than rolling your own. In any case, steer clear of std::vector<bool>!

D.Shawley
Thanks for the warning ;-) The arrays I use in this part of the program are very fixed in size (contrary to the underlying data structures), so I avoid std::vector anyway. And boost would not simplify things, because I need to access the data via SSE (__m128i and the like) to do the actual work.
drhirsch
A: 

Make the number of bytes you need part of the template parameters:

template <typename T, int S=1>
struct BitField 
{
   union
   {
      T bits;
      unsigned char bytes[S];
   };
};

typedef Bitfield<BitfieldSmallBase, 1>  BitfieldSmall;
typedef Bitfield<BitfieldLargeBase, 2> BitfieldLarge;
A: 

How about this?

#include <limits.h>

template <class T>
union BitField
{
    T bits;
    unsigned all : sizeof(T) * CHAR_BIT;
};
Pavel Minaev