Type safe(r) bitflags in C++?
Asked Answered
P

3

23

While revising some old c++ code, I ran across several bitflags defined as enums.

enum FooFlags
{
    FooFlag1 = 1 << 0,
    FooFlag2 = 1 << 1,
    FooFlag3 = 1 << 2
    // etc...
};

This isn't uncommon, but it bothered me that as soon as you start to combine flags, you lose the type information.

int flags = FooFlag1 | FooFlag2;   // We've lost the information that this is a set of flags relating to *Foo*

Some searching on SO showed that I'm not the only one bothered by this.

One alternative is to declare flags as #defines or const integrals, so bitwise operations wouldn't transform the type (probably). The problem with this is it allows our bit set to commingle with unrelated flags, via ints or other enums.

I'm familiar with std::bitset and boost::dynamic_bitset, but neither are designed to address my issue. What I'm looking for is something like C#'s FlagsAttribute.

My question is, what other solutions are there for a (more) type safe set of bitflags?

I'll post my own solution below.

Parcel answered 19/11, 2010 at 16:17 Comment(0)
P
14

Here's my own solution, using elements of c++0x that the current version of VS2010 allows for:

#include <iostream>
#include <numeric>
#include <string>

#include <initializer_list>

template <typename enumT>
class FlagSet
{
    public:

        typedef enumT                     enum_type;
        typedef decltype(enumT()|enumT()) store_type;

        // Default constructor (all 0s)
        FlagSet() : FlagSet(store_type(0))
        {

        }

        // Initializer list constructor
        FlagSet(const std::initializer_list<enum_type>& initList)
        {
            // This line didn't work in the initializer list like I thought it would.  It seems to dislike the use of the lambda.  Forbidden, or a compiler bug?
            flags_ = std::accumulate(initList.begin(), initList.end(), store_type(0), [](enum_type x, enum_type y) { return x | y; })
        }

        // Value constructor
        explicit FlagSet(store_type value) : flags_(value)
        {

        }

        // Explicit conversion operator
        operator store_type() const
        {
            return flags_;
        }

        operator std::string() const
        {
            return to_string();
        }

        bool operator [] (enum_type flag) const
        {
            return test(flag);
        }

        std::string to_string() const
        {
            std::string str(size(), '0');

            for(size_t x = 0; x < size(); ++x)
            {
                str[size()-x-1] = (flags_ & (1<<x) ? '1' : '0');
            }

            return str;
        }

        FlagSet& set()
        {
            flags_ = ~store_type(0);
            return *this;
        }

        FlagSet& set(enum_type flag, bool val = true)
        {
            flags_ = (val ? (flags_|flag) : (flags_&~flag));
            return *this;
        }

        FlagSet& reset()
        {
            flags_ = store_type(0);
            return *this;
        }

        FlagSet& reset(enum_type flag)
        {
            flags_ &= ~flag;
            return *this;
        }

        FlagSet& flip()
        {
            flags_ = ~flags_;
            return *this;
        }

        FlagSet& flip(enum_type flag)
        {
            flags_ ^= flag;
            return *this;
        }

        size_t count() const
        {
            // http://www-graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan

            store_type bits = flags_;
            size_t total = 0;
            for (; bits != 0; ++total)
            {
                bits &= bits - 1; // clear the least significant bit set
            }
            return total;
        }

        /*constexpr*/ size_t size() const   // constexpr not supported in vs2010 yet
        {
            return sizeof(enum_type)*8;
        }

        bool test(enum_type flag) const
        {
            return (flags_ & flag) > 0;
        }

        bool any() const
        {
            return flags_ > 0;
        }

        bool none() const
        {
            return flags == 0;
        }

    private:

        store_type flags_;

};

template<typename enumT>
FlagSet<enumT> operator & (const FlagSet<enumT>& lhs, const FlagSet<enumT>& rhs)
{
    return FlagSet<enumT>(FlagSet<enumT>::store_type(lhs) & FlagSet<enumT>::store_type(rhs));
}

template<typename enumT>
FlagSet<enumT> operator | (const FlagSet<enumT>& lhs, const FlagSet<enumT>& rhs)
{
    return FlagSet<enumT>(FlagSet<enumT>::store_type(lhs) | FlagSet<enumT>::store_type(rhs));
}

template<typename enumT>
FlagSet<enumT> operator ^ (const FlagSet<enumT>& lhs, const FlagSet<enumT>& rhs)
{
    return FlagSet<enumT>(FlagSet<enumT>::store_type(lhs) ^ FlagSet<enumT>::store_type(rhs));
}

template <class charT, class traits, typename enumT>
std::basic_ostream<charT, traits> & operator << (std::basic_ostream<charT, traits>& os, const FlagSet<enumT>& flagSet)
{
    return os << flagSet.to_string();
}

The interface is modeled after std::bitset. My aim was to be true to the c++ ethos of type safety and minimal (if any) overhead. I'd welcome any feedback on my implementation.

Here's a minimal example:

#include <iostream>

enum KeyMod
{
    Alt     = 1 << 0,  // 1
    Shift   = 1 << 1,  // 2
    Control = 1 << 2   // 4
};

void printState(const FlagSet<KeyMod>& keyMods)
{
    std::cout << "Alt is "     << (keyMods.test(Alt)     ? "set" : "unset") << ".\n";
    std::cout << "Shift is "   << (keyMods.test(Shift)   ? "set" : "unset") << ".\n";
    std::cout << "Control is " << (keyMods.test(Control) ? "set" : "unset") << ".\n";
}

int main(int argc, char* argv[])
{
    FlagSet<KeyMod> keyMods(Shift | Control);

    printState(keyMods);

    keyMods.set(Alt);
    //keyMods.set(24);    // error - an int is not a KeyMod value
    keyMods.set(Shift);
    keyMods.flip(Control);

    printState(keyMods);

    return 0;
}
Parcel answered 19/11, 2010 at 16:19 Comment(4)
Any chance of a usage example for your implementation?Weathersby
@Eric, I feel like it'd be pretty straight forward. What are you looking for exactly?Parcel
A simple example of declaring an enum E, instantiating a FlagSet<E>, and using it. Sure, I could work it out, but an example would make this answer better.Weathersby
Qt has a QFlags<T> class template which wraps an enum in a typesafe manner: qt-project.org/doc/qt-5.0/qtcore/qflags.html. It's quite similar to this one, but more similar to plain old flag enums, i.e. it favors oveloaded operators over methods.Conglutinate
B
21

You can overload operators for enumeration types that return the proper typed result.

inline FooFlags operator|(FooFlags a, FooFlags b) {
  return static_cast<FooFlags>(+a | +b);
}

It should be noted that to be theoretically safe, you should declare manually the highest possible value so the enumeration type's range is guaranteed to catch all the combinations.

  • Actually that is not needed: An enumeration's range will always be able to catch all combination, because the highest positive value of an enumeration's range is always (2^N)-1 for the first N being able to represent the highest enumerator. That value has all bits 1.
Bonhomie answered 19/11, 2010 at 16:45 Comment(8)
Why the +a and +b rather than just a and b out of interest? Safety?Timber
@sgolodetz it would enter an infinite recursion (by calling the operator| that is being defined).Bonhomie
@Johannes Schaub: SO you are using the + sign as an implicit cast to integer. Why not be explicit about it and use static_cast<>()?Tanjatanjore
@Martin because I don't know the underlying type of the enumeration. It may overflow both long and unsigned long depending on the enumeration. See https://mcmap.net/q/24095/-hidden-features-of-c-closed/…Bonhomie
@Martin alternatively you can write ((a + 0) | (b + 0)), if you want.Bonhomie
Probably the only time I've seen + used in a real situation. Cool.Roadwork
@Johannes: Glad I asked, thanks :) I'd completely missed the point!Timber
@Zack, No this does not work with C++11's enum class since its strongly typed and does not do an implicit cast to an integer type.Bourn
P
14

Here's my own solution, using elements of c++0x that the current version of VS2010 allows for:

#include <iostream>
#include <numeric>
#include <string>

#include <initializer_list>

template <typename enumT>
class FlagSet
{
    public:

        typedef enumT                     enum_type;
        typedef decltype(enumT()|enumT()) store_type;

        // Default constructor (all 0s)
        FlagSet() : FlagSet(store_type(0))
        {

        }

        // Initializer list constructor
        FlagSet(const std::initializer_list<enum_type>& initList)
        {
            // This line didn't work in the initializer list like I thought it would.  It seems to dislike the use of the lambda.  Forbidden, or a compiler bug?
            flags_ = std::accumulate(initList.begin(), initList.end(), store_type(0), [](enum_type x, enum_type y) { return x | y; })
        }

        // Value constructor
        explicit FlagSet(store_type value) : flags_(value)
        {

        }

        // Explicit conversion operator
        operator store_type() const
        {
            return flags_;
        }

        operator std::string() const
        {
            return to_string();
        }

        bool operator [] (enum_type flag) const
        {
            return test(flag);
        }

        std::string to_string() const
        {
            std::string str(size(), '0');

            for(size_t x = 0; x < size(); ++x)
            {
                str[size()-x-1] = (flags_ & (1<<x) ? '1' : '0');
            }

            return str;
        }

        FlagSet& set()
        {
            flags_ = ~store_type(0);
            return *this;
        }

        FlagSet& set(enum_type flag, bool val = true)
        {
            flags_ = (val ? (flags_|flag) : (flags_&~flag));
            return *this;
        }

        FlagSet& reset()
        {
            flags_ = store_type(0);
            return *this;
        }

        FlagSet& reset(enum_type flag)
        {
            flags_ &= ~flag;
            return *this;
        }

        FlagSet& flip()
        {
            flags_ = ~flags_;
            return *this;
        }

        FlagSet& flip(enum_type flag)
        {
            flags_ ^= flag;
            return *this;
        }

        size_t count() const
        {
            // http://www-graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan

            store_type bits = flags_;
            size_t total = 0;
            for (; bits != 0; ++total)
            {
                bits &= bits - 1; // clear the least significant bit set
            }
            return total;
        }

        /*constexpr*/ size_t size() const   // constexpr not supported in vs2010 yet
        {
            return sizeof(enum_type)*8;
        }

        bool test(enum_type flag) const
        {
            return (flags_ & flag) > 0;
        }

        bool any() const
        {
            return flags_ > 0;
        }

        bool none() const
        {
            return flags == 0;
        }

    private:

        store_type flags_;

};

template<typename enumT>
FlagSet<enumT> operator & (const FlagSet<enumT>& lhs, const FlagSet<enumT>& rhs)
{
    return FlagSet<enumT>(FlagSet<enumT>::store_type(lhs) & FlagSet<enumT>::store_type(rhs));
}

template<typename enumT>
FlagSet<enumT> operator | (const FlagSet<enumT>& lhs, const FlagSet<enumT>& rhs)
{
    return FlagSet<enumT>(FlagSet<enumT>::store_type(lhs) | FlagSet<enumT>::store_type(rhs));
}

template<typename enumT>
FlagSet<enumT> operator ^ (const FlagSet<enumT>& lhs, const FlagSet<enumT>& rhs)
{
    return FlagSet<enumT>(FlagSet<enumT>::store_type(lhs) ^ FlagSet<enumT>::store_type(rhs));
}

template <class charT, class traits, typename enumT>
std::basic_ostream<charT, traits> & operator << (std::basic_ostream<charT, traits>& os, const FlagSet<enumT>& flagSet)
{
    return os << flagSet.to_string();
}

The interface is modeled after std::bitset. My aim was to be true to the c++ ethos of type safety and minimal (if any) overhead. I'd welcome any feedback on my implementation.

Here's a minimal example:

#include <iostream>

enum KeyMod
{
    Alt     = 1 << 0,  // 1
    Shift   = 1 << 1,  // 2
    Control = 1 << 2   // 4
};

void printState(const FlagSet<KeyMod>& keyMods)
{
    std::cout << "Alt is "     << (keyMods.test(Alt)     ? "set" : "unset") << ".\n";
    std::cout << "Shift is "   << (keyMods.test(Shift)   ? "set" : "unset") << ".\n";
    std::cout << "Control is " << (keyMods.test(Control) ? "set" : "unset") << ".\n";
}

int main(int argc, char* argv[])
{
    FlagSet<KeyMod> keyMods(Shift | Control);

    printState(keyMods);

    keyMods.set(Alt);
    //keyMods.set(24);    // error - an int is not a KeyMod value
    keyMods.set(Shift);
    keyMods.flip(Control);

    printState(keyMods);

    return 0;
}
Parcel answered 19/11, 2010 at 16:19 Comment(4)
Any chance of a usage example for your implementation?Weathersby
@Eric, I feel like it'd be pretty straight forward. What are you looking for exactly?Parcel
A simple example of declaring an enum E, instantiating a FlagSet<E>, and using it. Sure, I could work it out, but an example would make this answer better.Weathersby
Qt has a QFlags<T> class template which wraps an enum in a typesafe manner: qt-project.org/doc/qt-5.0/qtcore/qflags.html. It's quite similar to this one, but more similar to plain old flag enums, i.e. it favors oveloaded operators over methods.Conglutinate
B
8

Thought I might add a c++11 version for enum class

FooFlags operator|(FooFlags a, FooFlags b)
{
  typedef std::underlying_type<FooFlags>::type enum_type;
  return static_cast<FooFlags>(static_cast<enum_type>(a) | static_cast<enum_type>(b));
}

If you c++11 version supports it I guess this would be a prime candidate for constexpr

Bourn answered 15/8, 2012 at 20:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.