Different bit mappings with anonymous structs

122 views Asked by At

I would like to implement different mappings for some number of bits. I came up with this solution that works reasonably well for me:

union myBits{
 // constructors

 struct{ // Mapping 1
   uint16_t a : 8, b : 8;
 };
 struct{ // Mapping 2
   uint16_t c : 10, d : 6;
 };
};

I can then access the different bit mappings simply by

myBits mb(/**/);
mb.c = 10;
mb.a = 2;

I think this functionality is very elegant, although it is not considered standard C++. g++ and clang produce warnings (Telling me that anonymous structs are not explicity allowed in the standard), Visual Studio compiles the code without producing any warnings.

My question is: Is there a modern C++1x way of writing the same code with the same functionality that does not include just naming the structs ?

1

There are 1 answers

3
Claudiu On BEST ANSWER

Due to the constraints C++ has in comparison to C, I don't see any efficient, legal and elegant way to do this in standard C++. You can choose only two of the three attributes :)

Efficient and legal, but not elegant

struct myBits
{
    void set_bits(size_t start_bit, size_t end_bit, uint16_t value)
    {
        size_t len = end_bit - start_bit + 1;
        uint16_t mask = ((1 << len) - 1) << (start_bit - 1);
        word = (word & ~mask) | ((value << (start_bit - 1)) & mask);
    }
    uint16_t get_bits(size_t start_bit, size_t end_bit)
    {
        size_t len = end_bit - start_bit + 1;
        return (word >> (start_bit - 1)) & ((1 << len) - 1);
    }
    uint16_t word;
};

To use it like in your scenario:

myBits mb;
mb.set_bits(1, 10, 10);
mb.set_bits(1, 8, 2);

This is possibly not really as efficient as the code in the question, it really depends on how the compiler generates the code for accessing bit fields vs the code above. The upside of this compared to the initial solution is that you can access any subset of bits without defining new structs and bit-fields for it.

Legal and elegant, but not efficient

template<size_t start_bit, size_t end_bit>
struct bit_field
{
    bit_field(uint16_t& w) : word(w) {}

    operator uint16_t() const
    {
        return (word >> (start_bit - 1)) & ((1 << len) - 1);
    }

    bit_field& operator=(uint16_t value)
    {
        uint16_t mask = ((1 << len) - 1) << (start_bit - 1);
        word = (word & ~mask) | ((value << (start_bit - 1)) & mask);
        return *this;
    }

    const size_t len = end_bit - start_bit + 1;
    uint16_t &word;
};

struct Word
{
    Word(uint16_t w) : word(w), a(word), b(word), c(word), d(word) {}

    uint16_t word;
    bit_field<1, 8> a;
    bit_field<9, 16> b;
    bit_field<1, 10> c;
    bit_field<11, 16> d;
};

The usage in this case is as elegant as in your example:

Word w(0);
w.c = 10;
w.a = 2;

The problem with this approach is that it's really space inefficient. You must have a struct declared for every bit field, compared to the initial approach of just storing bits.

Efficient and elegant, but not legal

This would basically be the way you described in the question.