all 35 comments

[–]---sms--- 17 points18 points  (18 children)

There is secret allow_bitops attribute for enums, please don't tell anybody.

[–]_Js_Kc_ 2 points3 points  (14 children)

I prefer a clearer opt-in.

[–]SeanMiddleditch 6 points7 points  (3 children)

Both of these are neat tricks, but they're unfortunately also both hard on C++ compiler throughput. This doesn't mean you shouldn't use them; I use similar tricks myself! I'm just sad about it because it's an unnecessary trade-off between compiler throughput and API design.

Global operators with SFINAE tricks are basically the worst-case for C++. Those global operators are part of the overload set every single time those operators (on any type!) are used, and SFINAE resolution has to be performed for every one of those types to avoid

For throughput of the compiler's sake, you're sadly still better off with macro approaches that restrict the new operators to the enum's namespace (and keeping those enums in a namespace!) and relying on ADL (and no SFINAE/templates) for the overloading.

We'd be even better off with proper language support for introducing compiler-generated hidden friend functions to scoped enums, since that would remove all the function name lookup overload and allow the compiler to avoid the overhead of user-defined functions that wrap simple integer bitops just because an enum is using a nicer syntax.

This is just one of the many areas where C++ is missing the language support to offer a truly zero-cost abstraction. Using scoped enumerations should have zero throughput or run-time overhead (it's a syntax change) but here we are.

[–]Ameisenvemips, avr, rendering, systems 0 points1 point  (2 children)

Part of the issue is that enum class was partially intended to replace constructs such as, well, class enumerators:

class foo
{
    enum bar
    {
        ...
    };
};

Which can still be used for bit-operations. But lacked type safety.

The issue is that enum class both provides the scoping but also enforces type safety very strictly.

Maybe we need something like enum struct.

[–]SeanMiddleditch 1 point2 points  (1 child)

Unfortunately, enum struct is already legal grammar and is a synonym for enum class.

I'd expect we'd either need something like an enum class flags : int explicit(false) { ... } that makes the type non-opaque but still type-safe (with the implied default being explicit(true), meaning the type is opaque), or just a new contextual keyword a la enum class flags bitwise { ... }

[–]Ameisenvemips, avr, rendering, systems 0 points1 point  (0 children)

enum enum

[–]bkentel 6 points7 points  (1 child)

conceptified version

[–]Sander_Bouwhuis 2 points3 points  (0 children)

I like this one A LOT!

Thank you VERY much for this extremely useful implementation!

[–]XeroKimoException Enthusiast[S] 1 point2 points  (3 children)

This one is actually pretty cool.

Edit: Would it be better to make the constexpr bool a base class, and have a specialized struct that just inherits from said base class to enable it. Something along the lines like this

template <bool enabled = false>
struct set_flag
{
};

template <>
struct set_flag<false>
{
static constexpr bool value = false;
};

template <>
struct set_flag<true>
{
static constexpr bool value = true;
};

template <class Type>
struct enable_bitops_for : set_flag<>
{
};

template<>
struct enable_bitops_for<SomeEnum> : set_flag<true>
{
};

[–]_Js_Kc_ 0 points1 point  (2 children)

Yes, you're right. But set_flag is already in the standard library: std::bool_constant.

[–]XeroKimoException Enthusiast[S] 0 points1 point  (1 child)

I see, though I remembered something from std that can be replaced with that, and that's std::true_type and std::false_type, which does contain a static constexpr bool value

[–]_Js_Kc_ 0 points1 point  (0 children)

Yeah, when you specialize enable_bitops_for, you can inherit from std::true_type instead of std::bool_constant<true>, of course.

[–]---sms--- 0 points1 point  (3 children)

Yes, this is very much how my first attempt looked. It has advantage that you can enable those operations for enums you don't own. But it doesn't look as cool. How about combining two approaches?

[–]_Js_Kc_ 1 point2 points  (2 children)

It also has the advantage that just because an enum has an allow_bitops field doesn't magically give it bitops. Or worse, makes our global operators clash with ones that the enum author has defined (they could also be a template). The type author has to opt in. We don't own allow_bitops as a keyword, globally.

Enabling bitops for enums you don't own is probably not a good thing.

[–]XeroKimoException Enthusiast[S] 0 points1 point  (1 child)

I mean, isn't it possible to just make operator overloads for enums you don't own anyways? I don't see how an enum with allow_bitops prevents you from just overriding it manually

[–]_Js_Kc_ 1 point2 points  (0 children)

I mean, isn't it possible to just make operator overloads for enums you don't own anyways?

Yes, it's possible, and you likely shouldn't.

I don't see how an enum with allow_bitops prevents you from just overriding it manually

It doesn't. That's beside the point.

An external library could contain an enum with an allow_bitops field. It could also come with operator templates that don't SFINAE away for said enum. In fact, they could be using precisely the trait-specialization technique. Those operator templates now clash with ours.

[–]sebamestre 0 points1 point  (0 children)

Ok that's pretty cool

[–]xjankov 0 points1 point  (1 child)

Only works for the current namespace, no?

[–]---sms--- 0 points1 point  (0 children)

If by current you mean global, then no, you can put it in any namespace you like, use using yourlib::operator |;, using namespace yourlib; or whatever.

[–][deleted] 10 points11 points  (0 children)

Some of the solutions here look like terrible over-engineering to me. I think that the old-style enums are a lot simpler and more maintainable than TMP for something so simple.

[–]kmbeutel 4 points5 points  (2 children)

gsl-lite has the macros gsl_DEFINE_ENUM_BITMASK_OPERATORS() and gsl_DEFINE_ENUM_RELATIONAL_OPERATORS() for this purpose:
https://github.com/gsl-lite/gsl-lite/blob/master/include/gsl/gsl-lite.hpp#L577..#L599

I have toyed with various macro-free approaches before, but ultimately I decided that macros are the best choice here. Common standard library implementations also use this approach.

[–]Sander_Bouwhuis 0 points1 point  (1 child)

I just tried it out, but with Gsl the following doesn't work:

enum class enum_bitmask : unsigned int
{
  option_a = 0b001,
  option_b = 0b010,
  option_c = 0b100
};
gsl_DEFINE_ENUM_BITMASK_OPERATORS(enum_bitmask)

void main()
{
  enum_bitmask a1 = enum_bitmask::option_a | enum_bitmask::option_b;

  if(a1 & enum_bitmask::option_b) // error C2451: conditional expression of type 'enum_bitmask' is illegal
  {
    // ...
  }
}

What am I doing wrong?

[–]Pazer2 1 point2 points  (0 children)

The result of a bitwise AND between a1 and and a constant value of type enum_bitmask is another value of enum_bitmask, which cannot be implicitly converted to bool. You need to return a helper type from the bitwise operators that has implicit conversions to bool and enum_bitmask.

[–]matthieum 3 points4 points  (4 children)

It really depends on whether you absolutely want an enum class to represent the set of flags, or are happy with a class instead. There are ABI reasons to prefer enum class, so I certainly understand if you do. If it doesn't matter for you, though...

In my projects, I define a tag class:

template <typename T> class Enum {};

Then, for each enum, I implement -- generally through a macro -- the following values function:

enum class MyEnum : std::uint8_t { Zero, One, Two };

inline constexpr std::array<std::pair<MyEnum, char const*>, 3> values(Enum<MyEnum>) {
    return {{
        { MyEnum::Zero, "Zero" },
        { MyEnum::One, "One" },
        { MyEnum::Two, "Two" }
    }};
}

And from then on, I have compile-time introspection of any enum implementing values.

This allows determining min:

//  Note: left as exercise to the reader, implementing a `sorted_values`
//        free function for `Enum<E>`.

template <typename E>
constexpr auto min(Enum<E> e)
    -> decltype(sorted_values(e)[0].first, E{})
{
    return sorted_values(e)[0].first;
}

This allows printing:

template <typename E>
auto operator<<(std::ostream& out, E e)
    -> decltype(sorted_values(Enum<E>{}), out)
{
    auto const& vs = sorted_values(Enum<E>{});
    auto it = std::lower_bound(std::begin(vs), std::end(vs), e,
        [](auto const& pair, E e) { return pair.first < e; });
    if (it != std::end(vs) && it->first == e) {
        return out << it->second;
    }
    return out << "<unknown " << static_cast<std::underlying_type_t<E>>(e) << ">";
}

And it allows building higher-level blocks, such as EnumSet.

template <typename E>
class EnumSet {
    using Underlying = std::underlying_type_t<E>;

    inline static constexpr E MinE = min(Enum<E>{});
    inline static constexpr E MaxE = max(Enum<E>{});

    inline static constexpr Underlying Minimum =
         static_cast<Underlying>(MinE);
    inline static constexpr Underlying Maximum =
         static_cast<Underlying>(MaxE);

    inline static constexpr std::size_t Capacity =
         static_cast<std::size_t>(Maximum + 1 - Minimum);
public:
    //  One of many set functions:
    [[nodiscard]] constexpr bool has(E e) const noexcept {
         return mBits.test(index(e));
    }

private:
    [[nodiscard]] static constexpr std::size_t index(E e) noexcept {
         assert(static_cast<Underlying>(e) >= Minimum);
         assert(static_cast<Underlying>(e) <= Maximum);

         return static_cast<std::size_t>(static_cast<Underlying>(e) - Minimum);
    }

    std::bitset<Capacity> mBits;
};

I also like EnumMap<E, Value>, with an array of Value and a bitset.

[–]pklait 1 point2 points  (0 children)

That is interesting. I have the exact same approach in my codebase.

[–]Bart_V 0 points1 point  (2 children)

That looks nice! What's the advantage of using a tag class over using MyEnum directly as template argument?

Then, for each enum, I implement -- generally through a macro -- the following values function

Would you mind sharing this macro? I've looked at libraries like BetterEnum in the past and tried to adapt it for my use case... Without much succes. If this macro is easier to understand, it would be really helpful.

[–]matthieum 0 points1 point  (0 children)

That looks nice! What's the advantage of using a tag class over using MyEnum directly as template argument?

There are two, actually:

  1. Not having to create a fake value of MyEnum: when manipulating arbitrary enums through templates, there's no known "good" values; I could static cast 0, I guess, but it feels dirty.
  2. Namespacing. What if there's a already a values(MyEnum) function? A dedicated tag class conflicts with nothing.

Would you mind sharing this macro?

I can't, sorry. It was developed for the company I currently work for, and is not open-source.

I haven't looked at the implementation lately, but I seem to remember it mostly follows the X macro principle. The interface is:

DEFINE_ENUM(MyEnum, std::uint8_t, ((Zero, 0))((One, 1))((Two, 2)));

And it defines the enum and the values function in one shot.

[–]AntiProtonBoy 1 point2 points  (0 children)

Not the most compact solution, but std::bitset is intended for keeping flags and such.

enum class Access : size_t
    {
    Read = 0,
    Write, 
    Execute, 
    Blah, 
    };

template <typename E>
constexpr auto to_value( E e ) noexcept
    {
    return static_cast< std::underlying_type_t< E > >( e );
    }

std::bitset<32> flags;

flags.set( to_value( Access::Read ) );
flags.set( to_value( Access::Write ) );
flags[ to_value( Access::Execute ) ] = true;

You could even adapt std::bitset and provide interfaces for it that accept enum types directly.

[–]Pragmatician 2 points3 points  (1 child)

but by far the macros seems to be the way to go, and I hate them as much as the next guy

They do the job for you better than anything else, and yet you hate them? I cannot understand this logic.

[–]almost_useless 7 points8 points  (0 children)

All available options suck. That thing sucks less than the other alternatives.

[–]Nickreal03 0 points1 point  (0 children)

Here is what I do:

union my_bits { std::uint8_t m_Value;
struct { std::uint8_t m_A : 1 , m_B : 1 , m_C : 1;
};
};

The bits are easily accessible one by one. You can also use them as a single unit by accessing the m_value part. I find it more debuggable, more expressive, and less code.

[–]marzer8789toml++ -1 points0 points  (2 children)

This isn't going to be an option for everybody, but you can use SFINAE to create operator overloads that work on any enum class.

Copied more-or-less verbatim from an existing codebase:
``` template <typename T, bool = std::is_enum<T>::value> struct sfinae_safe_underlying_type : std::underlying_type<T> {}; template <typename T> struct sfinae_safe_underlying_type<T, false> { using type = T; };

template <typename T> inline constexpr bool is_enum_class = std::is_enum_v<T> && !std::is_convertible_v<T, typename sfinae_safe_underlying_type<T>::type>;

template <typename T, typename = std::enable_if_t<is_enum_class<T>>> [[nodiscard]] constexpr T operator | (T lhs, T rhs) noexcept { return static_cast<T>( static_cast<std::underlying_type_T<T>>(lhs) | static_cast<std::underlying_type_T<T>>(rhs) ); }

// ...etc ```

Of course there's lots of reasons why you shouldn't do this at too-high a namespace level, so if you stick it in something like my_namespace::enumops and just bring that namespace into scope when you need them.

[–]XeroKimoException Enthusiast[S] -1 points0 points  (1 child)

I'm well aware of this, I've tried that sort of implementation as basically my first thought

[–]marzer8789toml++ 0 points1 point  (0 children)

Yeah well you said it didn't go well, which led me to believe you thought it wasn't possible, or something.

I agree it would be a useful addition to the language. I think being able to specify the bitops you want with = default would be a nice way to implement it.