2
\$\begingroup\$

The purpose for this class is to pack two 4 bit unsigned integer into 8 bit unsigned integer, e.g. uint8_t.

It is made in a way, so the class to resemble array of two contiguous elements.

Once this work correctly, one may program continuous array of 4 bit integers and to use it for example for counting bloom filter.

A "special" proxy class is used in a way similar to std::vector, so you can do:

    UINT4 x;

    // set parts
    x[0] =  1; // set first  part, OK
    x[1] = 20; // set second part, OK

    // convert to uint8_t
    uint8_t i = x[0]; // OK

    // arithmetic mostly works.
    ++x[0]; // OK

    x[0] += 10; // OK

    --x[1]; // OK

    x[1] -= 10; // OK

    // not implemented yet, mostly because I don't need it
    // x[1] = x[0] + 10;

#include <cstdint>
//#include <type_traits>
//#include <cstdio>



class UINT4{
    uint8_t _0 : 4;
    uint8_t _1 : 4;

    class Proxy;

public:
    constexpr explicit UINT4() : UINT4{ 0, 0 }{
    }

    constexpr UINT4(uint8_t _0, uint8_t _1) : _0{ _0 }, _1{ _1 }{
    }

    constexpr
    Proxy operator[](std::size_t i);

    constexpr uint8_t operator[](std::size_t i) const{
            if (i == 0)
                return _0;
            else
                return _1;
    }
};

static_assert(sizeof(UINT4) == 1);



class UINT4::Proxy{
    UINT4 &d;
    uint8_t i;

public:
    // c-tor

    constexpr Proxy(UINT4 &d, std::size_t i) :
                                    d(d                 ),
                                    i(i == 0 ? 0 : 1    ){
    }

    // convert

    constexpr operator uint8_t() const{
        // we can use UINT4::operator[](size_t) const

        if (i == 0)
            return d._0;
        else
            return d._1;
    }

    // compare

    constexpr bool operator==(uint8_t x) const{
        if (i == 0)
            return d._0 == x;
        else
            return d._1 == x;
    }

    constexpr bool operator!=(uint8_t x) const{
        return !( *this == x );
    }

    constexpr bool operator<(uint8_t x) const{
        if (i == 0)
            return d._0 < x;
        else
            return d._1 < x;
    }

    constexpr bool operator<=(uint8_t x) const{
        if (i == 0)
            return d._0 <= x;
        else
            return d._1 <= x;
    }

    constexpr bool operator>(uint8_t x) const{
        return !( *this <= x );
    }

    constexpr bool operator>=(uint8_t x) const{
        return !( *this < x );
    }

    // assign

    constexpr
    auto &operator=(uint8_t x){
        if (i == 0)
            d._0 = x;
        else
            d._1 = x;

        return *this;
    }

    // +++

    constexpr
    auto &operator+=(uint8_t x){
        if (i == 0)
            d._0 += x;
        else
            d._1 += x;

        return *this;
    }

    constexpr
    auto &operator++(){
        return *this += uint8_t{ 1 };
    }

    // can not be done easily, because there are references
    // auto operator++(int);

    // ---

    constexpr
    auto &operator-=(uint8_t x){
        return *this += - x;
    }

    constexpr
    auto &operator--(){
        return *this -= uint8_t{ 1 };
    }

    // can not be done easily, because there are references
    // auto operator--(int);

    // todo add multiplication and division
};



constexpr auto UINT4::operator[](std::size_t i) -> Proxy{
    return Proxy{ *this, i };
}


#include <cstdio>

int main(){
    UINT4 x{ 15, 0 };
    x[1] = 2;

    uint8_t a = x[0];

    printf("%u\n", a);

    printf("%u %u\n", uint16_t{ x[0] }, uint16_t{ x[1] } );

    x[0] -= 10;
    x[1] += 10;

    printf("%u %u\n", uint16_t{ x[0] }, uint16_t{ x[1] } );

    ++x[0];
    --x[1];

    printf("%u %u\n", uint16_t{ x[0] }, uint16_t{ x[1] } );
}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Make it work like std::vector<bool>

Your class shares similarities with std::vector<bool>. I recommend that you make it work more like that. In particular, instead of just providing two 4-bit integers, make a class that acts like a vector of them. You already mentioned that the use case is for contiguous arrays, but if you have to write:

std::vector<UINT4> values;

Then it actually becomes very awkward to access the n-th 4-bit integer, because you'd have to write something like:

std::size_t n = …;
auto value = values[n / 2][n & 1];

Instead it would be much nicer to be able to write:

vector_uint4 values;
…
std::size_t n = …
auto value = values[n];

std::vector<bool>::reference uses a proxy class just like your class. It is a bit simpler since it only needs to deal with operations that make sense for booleans. But note that it not only allows bool arguments, it also has overloads that take other std::vector<bool>::references as parameters. This allows you to, say, assign the value of one reference to another. You might want to allow something similar in your class, so you can add one 4-bit value of the vector to another one. Of course, if you are sure you are not going to need it, I would not implement that, but if you wanted to make a generic library that can be used for other purposes than your bloom filter, this is something you might want to think about.

\$\endgroup\$
1
  • \$\begingroup\$ Yes, I already have class similar to vector. I like this one values[n / 2][n & 1] \$\endgroup\$
    – Nick
    Commented Mar 28, 2023 at 15:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.