3

I need to have two separate 16-bit integers, that can form a 32-bit integer together. But I need them to be updated whenever I change any of them. Let's say I change the value of the 32-bit, I need it to be automatically written over the two 16-bit ones and vice versa. Is this possible?

Torisoft
  • 31
  • 5
  • 1
    _"Is this possible"_ - Yes, if your compiler supports type-punning. It is undefined behavior but some compilers supports it anyway. – Ted Lyngmo Feb 08 '23 at 18:35

3 Answers3

8

You can use a proxy class to represent your 32-bit integer:

class Proxy {
private:
    uint16_t &_high, &_low;

public:
    Proxy(uint16_t &high, uint16_t &low) : _high(high), _low(low) {}

    Proxy &operator=(uint32_t whole) {
        _high = whole >> 16;
        _low = whole & 0xffff;
        return *this;
    }

    operator uint32_t() const {
        return (_high << 16) | _low;
    }
};

int main() {
    uint16_t high = 0xa, low = 0xb;
    Proxy whole(high, low);
    std::cout << std::hex;
    std::cout << whole << '\n'; // a000b
    high = 0xc;
    low = 0xd;
    std::cout << whole << '\n'; // c000d
    whole = 0xe000f;
    std::cout << high << ' ' << low << '\n'; // e f
    return 0;
}

By providing operator uint32_t, Proxy can be implicitly converted to uint32_t in most cases.

wtz
  • 426
  • 4
  • 15
1

This gets really easy with c++20 which has bit_cast. It can even be used in constexpr functions. Here are freestanding encapsulated, and really simple direct (no extra functions or classes) versions:

#include <iostream>
#include <array>
#include <cstdint>
#include <bit>
using std::uint32_t, std::uint16_t;

// Free standing functions
void place_low16(std::array<uint16_t, 2>& arr, uint16_t x) {arr[0] = x;}
void place_high16(std::array<uint16_t, 2>& arr, uint16_t x) {arr[1] = x;}
void place_32int(std::array<uint16_t, 2>& arr, uint32_t i){arr = std::bit_cast<std::array<uint16_t, 2>>(i);}
uint32_t get_ui32(const std::array<uint16_t, 2>& arr) {return std::bit_cast<uint32_t>(arr);}

// encapsulated
struct PackedInt16 {
    std::array<uint16_t, 2> two_uint32s;
    void place_low16(uint16_t x) {two_uint32s[0] = x;}
    void place_high16(uint16_t x) { two_uint32s[1] = x; }
    void place_32int(uint32_t i) { two_uint32s = std::bit_cast<std::array<uint16_t, 2>>(i); }
    uint32_t get_ui32() { return std::bit_cast<uint32_t>(two_uint32s); }
    };


int main()
{
    // free standing functions
    std::array<uint16_t, 2> packed_ints;
    place_low16(packed_ints, static_cast<uint16_t>(0xffff'ffff));   //store in low int16
    place_high16(packed_ints, static_cast<uint16_t>(0x1));          // store in high int16
    uint32_t x = get_ui32(packed_ints);                             // get 32 bit uint
    place_32int(packed_ints, x);                                    // store 32 bit uint in packed int16s
    std::cout << x << " " << packed_ints[0] << " " << packed_ints[1] << '\n';
    // ouput: 131071 65535 1

    // encapsulated
    PackedInt16 packed_ints2;
    packed_ints2.place_low16(static_cast<uint16_t>(0xffff'ffff));
    packed_ints2.place_high16(static_cast<uint16_t>(0x1));
    uint32_t x2 = packed_ints2.get_ui32();
    packed_ints2.place_32int(x2);
    std::cout << x2 << " " << packed_ints2.two_uint32s[0] << " " << packed_ints2.two_uint32s[1] << '\n';
    // ouput: 131071 65535 1

    // and now the direct approach: No functions, no classes
    std::array<uint16_t, 2> packed_ints3;
    packed_ints3[0] = static_cast<uint16_t>(0xffff'ffff);
    packed_ints3[1] = 1;
    uint32_t x3 = std::bit_cast<uint32_t>(packed_ints3);
    packed_ints3 = std::bit_cast<std::array<uint16_t, 2>>(x3);
    std::cout << x3 << " " << packed_ints3[0] << " " << packed_ints3[1] << '\n';
    // ouput: 131071 65535 1
}
doug
  • 3,840
  • 1
  • 14
  • 18
0

You could define a class that behaves similar to a uint16_t which works with a uint32_t value stored as reference.

In some cases there's a difference though, e.g. a conversion to uint16_t won't happen automatically in some cases.


class Uint32BitView16
{
    uint32_t& m_data;
    unsigned m_shift;
public:
    constexpr Uint32BitView16(uint32_t& data, unsigned shift)
        : m_data(data),
        m_shift(shift)
    {
    }

    constexpr operator uint16_t() const
    {
        return (m_data >> m_shift);
    }

    constexpr Uint32BitView16& operator=(uint16_t value)
    {
        m_data = (m_data & ~static_cast<uint32_t>(0xffff << m_shift)) | (value << m_shift);
        return *this;
    }
};

int main() {
    uint32_t data = 0x01020304;
    Uint32BitView16 v1(data, 0);
    Uint32BitView16 v2(data, 16);

    std::cout << std::hex;

    std::cout << static_cast<uint16_t>(v1) << '\n'; // 304
    std::cout << static_cast<uint16_t>(v2) << '\n'; // 102

    data = 0xffff0000;
    std::cout << static_cast<uint16_t>(v1) << '\n'; // 0
    std::cout << static_cast<uint16_t>(v2) << '\n'; // ffff

    v1 = 0xff;
    std::cout << data << '\n'; // ffff00ff
}
fabian
  • 80,457
  • 12
  • 86
  • 114