2

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 ?

timrau
  • 22,578
  • 4
  • 51
  • 64
Camleon
  • 113
  • 9
  • 1
    There is no standard-conforming way to do this whether you name the structs or not. Using unions like this is UB. – n. m. could be an AI Jun 06 '17 at 16:34
  • Ok, that's disappointing. But why is it undefined behaviour? I mean, what could go wrong? I already tested its functionality a few billions times and it always produced the expected result. Or is it just undefined behaviour in the sense that it is not explicitly defined but works nevertheless? Is there a way to write equivalent code without unions? – Camleon Jun 07 '17 at 17:07
  • 1
    It is not allowed to inspect an inactive union member. An active union member is the one last assigned/initialised. Your compiler is free to assume you never do that and optimise based on this assumption. – n. m. could be an AI Jun 07 '17 at 17:31
  • That is interesting, thanks! :) Are partially assigned bit fields really not an exception in this case? – Camleon Jun 07 '17 at 19:48
  • 1
    There is a nice answer to this [here](https://stackoverflow.com/a/11996970/423913). What's interesting is: **The confusion is that C explicitly permits type-punning through a union, whereas C++ (c++11) has no such permission.** – Claudiu Jun 07 '17 at 21:07
  • Thank you, this was very helpful. I have read the answer and i found (at least i think i have found) the actual paragraph in the standard. (§9.2 18). After reading this paragraph, I think my code above should be ok, since my union contains two "standard layout structs", that both have the same "initial sequence" and both are bit fields. It follows then, that the union can "inspect the common initial part of an of them". Am I reading it correctly? – Camleon Jun 07 '17 at 21:24
  • 1
    I think it says they do not have a common initial sequence: "Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types and either neither member is a bit-field or **both are bit-fields with the same width for a sequence of one or more initial member**". In your case the first struct has widths 8/8 and the second struct has widths 10/6. – Claudiu Jun 07 '17 at 22:10

1 Answers1

2

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.

Claudiu
  • 2,124
  • 4
  • 26
  • 36
  • 1
    Replace `bit_field<1, 8> a` with `bit_field<1, 8> a(){ return {word}; }` and modern compilers will likely generate **identical** code for the "legal and elegant but not efficient" implementation and for the "efficient and elegant but not legal" implementation. Of course you will have to say `w.a()` instead of `w.a` but this is a small price to pay. – n. m. could be an AI Jun 08 '17 at 06:32
  • @n.m. Very good point. I will leave the answer as is, because I wanted the "elegant" solution to have the same usage as in the question, but this would be a sacrifice I would myself make if given a choice. – Claudiu Jun 08 '17 at 08:32
  • Thank you both, I will be implementing a template version of the code :) – Camleon Jun 08 '17 at 20:54