Search code examples
c++bitbitmask

Create a single mask to check if bits are set, unset or accept either


Given a value, I want to check if some of its bits are set and some of them unset at the same time, while I don't care about the value of others.

I can do this with two bit masks, one for set bits one for unset bits like so:

#include <iostream>

bool checkMask(uint8_t value, uint8_t setmask, uint8_t unsetmask)
{
    return (value & setmask) == setmask && (~value & ~unsetmask) == ~unsetmask;
}

int main() {
    uint8_t setmask = 0b0100'0001;
    uint8_t unsetmask = 0b1111'1011;
    
    uint8_t valueMatches = 0b01101'1011;
    uint8_t valueFails1 = 0b00101'1010;
    uint8_t valueFails2 = 0b01101'1111;
    std::cout << "matches " <<  checkMask(valueMatches, setmask, unsetmask) << std::endl;
    std::cout << "fails1 " <<  checkMask(valueFails1, setmask, unsetmask) << std::endl;
    std::cout << "fails2 " <<  checkMask(valueFails2, setmask, unsetmask) << std::endl;
}

(May be buggy, it's an example)

Of course, this can also be done with a string, where I can represent more than 0 and 1 with a wildcard value like:

string bitmask = ".1...001";

and then check bit by bit from the string, ignoring '.' and checking 0s and 1s match.

In my solutions there's a tradeoff, either using 2 values, which makes it less intuitive (specially the unset mask), or ussing a string which is more inefficient, but really clear.

Are there other options?


Solution

  • Some mostly generic possibilities based off of @KamilCuk comment

    #include <iostream>
    #include <utility>
    #include <array>
    #include <bitset>
    #include <string>
    
    constexpr uint8_t BYTE_BITS = 8;
    
    template <typename D>
    class BitMask
    {
        public:
        constexpr BitMask(const D setmask, const D unsetmask) : 
            m_setmask(setmask), m_unsetmask(unsetmask) 
        {}
    
        constexpr BitMask(const char * bits)
        {
            m_setmask = 0;
            m_unsetmask = 0;
    
            for (int i = 0; i < sizeof(D)*BYTE_BITS; i++)
            {
                m_setmask = m_setmask << 1;
                m_unsetmask = m_unsetmask << 1;
                m_setmask += bits[i] == '1' ? 1 : 0;
                m_unsetmask += bits[i] == '0' ? 0 : 1;
            }
        }
    
        constexpr BitMask(const std::array<D,sizeof(D)*BYTE_BITS>& bits)
        {
            m_setmask = 0;
            m_unsetmask = 0;
    
            for (int i = 0; i < sizeof(D)*BYTE_BITS; i++)
            {
                m_setmask = m_setmask << 1;
                m_unsetmask = m_unsetmask << 1;
                m_setmask += bits[i] == 1 ? 1 : 0;
                m_unsetmask += bits[i] == 0 ? 0 : 1;
            }
        }
    
        constexpr bool check(const D value) const 
        {
            return BitMask::check(value, m_setmask, m_unsetmask);
        }
    
        static bool check(const D value, const D setmask, const D unsetmask)
        {
            return (value & setmask) == setmask && (~value & ~unsetmask) == ~unsetmask;
        }
    
        void print() const
        {
            std::cout << "Set mask: " << std::bitset<sizeof(D)*BYTE_BITS>(m_setmask) << '\n'
                      << "Unset mask: " << std::bitset<sizeof(D)*BYTE_BITS>(m_unsetmask) << '\n';
        }
    
        private:
        D m_setmask;
        D m_unsetmask;
    };
    
    
    int main() {
        constexpr BitMask<uint8_t> mask1(0b0100'0001, 0b1111'1011);
        constexpr BitMask<uint8_t> mask2(std::array<uint8_t, sizeof(uint8_t)*BYTE_BITS>{2,1,2,2,2,0,2,1});
        constexpr BitMask<uint8_t> mask3("?1???0?1");
            
        uint8_t valueMatches = 0b01101'1011;
        uint8_t valueFails1 = 0b00101'1010;
        uint8_t valueFails2 = 0b01101'1111;
        std::cout << "matches " <<  mask1.check(valueMatches) << std::endl;
        std::cout << "fails1 " <<  mask1.check(valueFails1) << std::endl;
        std::cout << "fails2 " <<  mask1.check(valueFails2) << std::endl;
    
        mask1.print();
        mask2.print();
        mask3.print();
    
        return 0;
    }
    

    It may be that the string option is not as slow as I initially thought. At least given how intuitive it is to use.

    Problems include, no checking the string length. In constexpr it will just not compile if the string is too short, which is good, but will ignore if it's longer. Can't create std::string in constexpr context to check the size matches the type, though strlen should be constexpr.