8
\$\begingroup\$

Today I had some time and tried to improve my own Bitmask adapter class.

It's common practice to eliminate sets of boolean variables with something like a flag system. The old fashioned way was to create a enum and store those values in an unsigned int with some bit operations. It isn't a very complicated task, but at least error prone, tedious and less readable. With the addition of enum class we got more type safety which I make usage of. In the end this class is a simple wrapper around some bit operations which fulfills the explained task.

I use this functionality in a lot of projects, thus my goal is to implement it as good as possible. Sure, it isn't one of the heaviest classes out there, but perhaps someone has some pretty adjustment in mind, which makes it even better.

As you can probably see on the fold expressions, I already updated to c++17, so feel free to suggest any improvements within the newest standard.

In the end I am interested in any opinions about usability and further improvements ;)

#include <type_traits>

namespace
{
    template <class FlagType, class... Args>
    using enable_if_all_same_as_T = std::enable_if_t<std::is_same_v<std::common_type_t<Args...>, FlagType>>;

    template <class T, class... Args>
    constexpr T combine(Args... _args) noexcept
    {
        T mask = (... | static_cast<T>(_args));
        return mask;
    }
}

template <class FlagType, class StorageType = unsigned int>
class Bitmask
{
    static_assert(std::is_enum_v<FlagType> || (std::is_integral_v<FlagType> && std::is_unsigned_v<FlagType>), "FlagType must be an unsigned integer or enum type.");
    static_assert(std::is_integral_v<StorageType> && std::is_unsigned_v<StorageType>, "StorageType must be an unsigned integer type.");

public:
    constexpr explicit Bitmask() noexcept = default;

    template <class... Args, typename = enable_if_all_same_as_T<FlagType, Args...>>
    constexpr explicit Bitmask(Args... _args) noexcept :
        m_Mask(combine<StorageType>((_args, ...)))
    {}

    constexpr Bitmask(const Bitmask& _other) noexcept = default;
    constexpr Bitmask& operator =(const Bitmask& _other) noexcept = default;

    constexpr StorageType operator*() const noexcept
    {
        return m_Mask;
    }

    template <class... Args, typename = enable_if_all_same_as_T<FlagType, Args...>>
    constexpr bool contains(Args... _args) const noexcept
    {
        auto mask = combine<StorageType>((_args, ...));
        return (m_Mask & mask) == mask;
    }

    template <class... Args, typename = enable_if_all_same_as_T<FlagType, Args...>>
    constexpr void apply(Args... _args) noexcept
    {
        m_Mask |= combine<StorageType>((_args, ...));
    }

    template <class... Args, typename = enable_if_all_same_as_T<FlagType, Args...>>
    constexpr void remove(Args... _args) noexcept
    {
        m_Mask &= ~combine<StorageType>((_args, ...));
    }

    constexpr Bitmask& operator &=(const Bitmask& _mask) noexcept
    {
        m_Mask &= _mask.m_Mask;
        return *this;
    }

    constexpr Bitmask& operator |=(const Bitmask& _mask) noexcept
    {
        m_Mask |= _mask.m_Mask;
        return *this;
    }

    constexpr Bitmask& operator ^=(const Bitmask& _mask) noexcept
    {
        m_Mask ^= _mask.m_Mask;
        return *this;
    }

private:
    StorageType m_Mask = 0;
};

template <class FlagType, class StorageType>
constexpr Bitmask<FlagType, StorageType> operator &(const Bitmask<FlagType, StorageType>& _lhs, const Bitmask<FlagType, StorageType>& _rhs) noexcept
{
    auto tmp(_lhs);
    tmp &= _rhs;
    return tmp;
}

template <class FlagType, class StorageType>
constexpr Bitmask<FlagType, StorageType> operator |(const Bitmask<FlagType, StorageType>& _lhs, const Bitmask<FlagType, StorageType>& _rhs) noexcept
{
    auto tmp(_lhs);
    tmp |= _rhs;
    return tmp;
}

template <class FlagType, class StorageType>
constexpr Bitmask<FlagType, StorageType> operator ^(const Bitmask<FlagType, StorageType>& _lhs, const Bitmask<FlagType, StorageType>& _rhs) noexcept
{
    auto tmp(_lhs);
    tmp ^= _rhs;
    return tmp;
}

template <class FlagType, class StorageType>
constexpr bool operator ==(const Bitmask<FlagType, StorageType>& _lhs, const Bitmask<FlagType, StorageType>& _rhs) noexcept
{
    return *_lhs == *_rhs;
}

template <class FlagType, class StorageType>
constexpr bool operator !=(const Bitmask<FlagType, StorageType>& _lhs, const Bitmask<FlagType, StorageType>& _rhs) noexcept
{
    return !(_lhs == _rhs);
}

int main()
{
    enum class Test
    {
        a = 0b0001,
        b = 0b0010,
        c = 0b0100,
        d = 0b1000,
        some = c | a,
        all = a | b | c | d
    };
    Bitmask<Test> mask(Test::a, Test::b, Test::all);
    auto other(mask);

    if (mask.contains(Test::some))
    {
        mask.remove(Test::a);

        if (mask.contains(Test::some))
        {
            mask.remove(Test::all);
        }
    }

    other ^= mask;
    auto other2 = mask | other;
    auto check = other != mask;
}
\$\endgroup\$
6
\$\begingroup\$

The test for enable_if_all_same_as_T seems a bit over-restrictive - surely all the arguments only need to be assignable to FlagType, not exactly the same? That's achievable with an initialiser-list constructor:

constexpr explicit Bitmask(std::initializer_list<FlagType> args) noexcept;

Yes, that means you no longer get to play with fold expressions, but that shouldn't be a goal. Just because you have a hammer, you shouldn't assume that everything is a nail!


In the free operators, you can avoid making a copy of lhs if you pass it by value. If you're given an rvalue, that saves an unnecessary copy (for an lvalue, it just moves where the necessary copying is done). For instance:

template <class FlagType, class StorageType>
constexpr Bitmask<FlagType, StorageType>
operator^(Bitmask<FlagType, StorageType> lhs,
          const Bitmask<FlagType, StorageType>& rhs) noexcept
{
    return lhs ^= rhs;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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