1

What is the best way to create a constructor for a struct(which has a union member, does it matter?) to convert uint8_t type into the struct?

Here is my example to clarify more:

struct twoSixByte
{
    union {
        uint8_t fullByte;
        struct
        {
            uint8_t twoPart : 2;
            uint8_t sixPart : 6;
        } bits;
    };
};

uint32_t extractByte(twoSixByte mixedByte){
  return mixedByte.bits.twoPart * mixedByte.bits.sixPart;
}

uint8_t tnum = 182;
print(extractByte(tnum)); // must print 2 * 54 = 108

P.S. Finding from comments & answers, type-punning for unions is not possible in C++.

The solutions given are a little bit complicated specially where there are lots of these structures in the code. There are even situations where a byte is divided into multiple bit parts(more than two). So without taking advantage of unions and instead using bitsets ans shifting bits adds a lot of burden to the code.

Instead, I managed for a much simpler solution. I just converted the type before passing it to the function. Here is the fixed code:

struct twoSixByte
    {
        union {
            uint8_t fullByte;
            struct
            {
                uint8_t twoPart : 2;
                uint8_t sixPart : 6;
            } bits;
        };
    };

    uint32_t extractByte(twoSixByte mixedByte){
      return mixedByte.bits.twoPart * mixedByte.bits.sixPart;
    }

    uint8_t tnum = 182;
    twoSixByte mixedType;
    mixedType.fullByte = tnum;
    print(extractByte(mixedByte)); // must print 2 * 54 = 108
Rev
  • 304
  • 1
  • 5
  • 11
  • How will you decide which part of the `union` is active? – wally May 04 '18 at 15:55
  • 1
    Your problem statement is a little contradictory. You say you want to convert an `int` to a `twoSixByte` type, but in the example, you're converting a `uint8_t` into a `twoSixByte`, which is a different type. – Xirema May 04 '18 at 15:55
  • yes, I want to convert uint8_t into twoSixByte. the value is passed by argument and needs a constructor. First two bits as twoPart and the remaining 6 bits as sixPart. – Rev May 04 '18 at 16:00
  • 3
    `print(...)`?? Is that your own function? – R Sahu May 04 '18 at 16:00
  • 2
    [Type punning in C++ is not ok](https://stackoverflow.com/a/25672839/1460794). – wally May 04 '18 at 16:01
  • 2
    You could provide a constructor e.g. `twoSixByte(uint8_t value = 0): fullByte(value) { }`. Actually, it is not really allowed in C++ (but still works fine in our (similar) code - compiled in VS2013). ;-) Link: [SO: Unions and type-punning](https://stackoverflow.com/a/25672839/7478597) – Scheff's Cat May 04 '18 at 16:03
  • 2
    `182` in binary is `1011 0110`. Why do you think you should get `3` and `54` from that? It is `2` and `54`. – R Sahu May 04 '18 at 16:09
  • just fixed that Sahu. print is not a real function, just used it for maximum simplicity. – Rev May 04 '18 at 16:15
  • 1
    Hey, if you absolutely need type punning (e.g. from uint8_t to a struct full of bitfields), try the `memcpy` technique: https://stackoverflow.com/a/17790026/8120642 – hegel5000 May 04 '18 at 17:09
  • 1
    One issue with type punning and bitsets: the arrangement of bitset members in a struct are implementation defined. `sixPart` ended up being the upper six bits for me, which is the opposite of what I'd expect. It's the same whether I use the `union` or `memcpy` techniques. It might or might not go the other way on your platform (I compiled using GCC 6.2.1 for x86, with -O0). – hegel5000 May 04 '18 at 17:48

2 Answers2

4

Unless there is a pressing need for you to use a union, don't use it. Simplify your class to:

struct twoSixByte
{
   twoSixByte(uint8_t in) : twoPart((in & 0xC0) >> 6), sixPart(in & 0x3F) {}
   uint8_t twoPart : 2;
   uint8_t sixPart : 6;
};

If there is a need to get the full byte, you can use:

uint8_t fullByte(twoSixByte mixedByte)
{
   return ((mixedByte.twoPart << 6) | mixedByte.sixPart);
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • I have lots of structures like this. Unions really make it easy to extract full data bytes. Resources are limited in microcontrollers and It requires to save flags in bit level. – Rev May 04 '18 at 16:46
  • 1
    @Rev, My answer uses bit fields. It avoids use of a `union`. – R Sahu May 04 '18 at 16:49
2

You could avoid the union and type punning and use a struct with the relevant member function. Note that we don't need a constructor if the struct is regarded as an aggregate to be initialized:

#include <cstdint>

struct twoSixByte {
    uint8_t fullByte; // no constructor needed, initializing as an aggregate
    uint32_t extractByte(){
        return ((fullByte & 0b1100'0000) >> 6) * (fullByte & 0b0011'1111);
    }
};


int main()
{
    twoSixByte tnum{182};
    auto test = tnum.extractByte(); // test == 2 * 54 == 108
}
wally
  • 10,717
  • 5
  • 39
  • 72
  • 2
    Note that this is just as performant as using bitfields. Accessing a bitfield compiles down to the same shifting and anding that's shown here. – hegel5000 May 04 '18 at 16:23
  • This is really great but a little difficult to implement where the byte is separated into more than two parts. Imagine there are lots of such structures in code. Unions could really make it easier – Rev May 04 '18 at 16:49