Silverspur
Silverspur

Reputation: 921

Define enum element value using an operation on another enum values

Here is what I need to do: define, inside a class, two enumerations, the second having elements defined using elements values from the first.

So something like this:

class MyClass
{
    public:
        enum class Elem {
            A=1, B=2, C=4, D=8
        };
        enum class Group {
            first = Elem::A | Elem::B,
            second = Elem::A | Elem::C,
            //...
        }; <-- compilation error
};

However, this does not compile due to the fact that | is not defined by default for enum classes.

I tried to define the | operator for Elem enum, outside of the MyClass class (after the class body), but the operator is then not known at the time the Group enum is defined.

So I then tried the following, i.e. defining a constexpr function inside the class:

class MyClass
{
    public:
        enum class Elem {
            A=1, B=2, C=4, D=8
        };

        constexpr static unsigned int merge(
            std::initializer_list<MyClass::Elem> list)
        {
            //Test only: should be an '|' on all list elements
            return 1;
        }

        enum class Group {
            first = merge({Elem::A,Elem::B}),
            second = merge({Elem::A,Elem::C}),
            /*...*/
        };
};

But I get the following error:

error: static constexpr unsigned int merge(std::initializer_list list) called in a constant expression

I understood from here and there that the merge method is considered declared and usable only after the class has been fully declared.

The last solution I can think of is using macros like this:

#define MERGE2ELEMS( a, b ) static_cast<unsigned int>(a) | static_cast<unsigned int>(b)
#define MERGE3ELEMS( a, b, c ) static_cast<unsigned int>(a) | MERGE2ELEMS( b, c )
#define MERGE4ELEMS( a, b, c, d ) static_cast<unsigned int>(a) | MERGE3ELEMS( b, c, d )
#define MERGE5ELEMS( a, b, c, d, e ) static_cast<unsigned int>(a) | MERGE4ELEMS( b, c, d, e )
...

But I need to be able to merge up to 20 Elems and writing 20 macros like this does not seem to be a suitable solution.

What would be the way to go here?

Upvotes: 3

Views: 1147

Answers (3)

Drop
Drop

Reputation: 13005

You may play with the order of definition:

class MyClass {
   public:
    enum class Elem { A = 1, B = 2, C = 4, D = 8 };
    enum class Group;
};

constexpr MyClass::Elem operator|(
    const MyClass::Elem& l, const MyClass::Elem& r) {
    return static_cast<MyClass::Elem>(
              static_cast<int>(l) | static_cast<int>(r)
    );
}

enum class MyClass::Group {
    first = Elem::A | Elem::B,
    second = Elem::A | Elem::C,

};

Live demo

But the entire idea sorta goes against the concept of type-safe enum. Maybe you could just use the good old unsafe ones?

Upvotes: 2

user7860670
user7860670

Reputation: 37486

You need a static cast to the integral type corresponding to enumerator so there will be built-in operator |:

class MyClass
{
    public: enum class Elem: unsigned int
    {
        A=1, B=2, C=4, D=8
    };
    public: enum class Group: unsigned int
    {
        first  = static_cast<unsigned int>(Elem::A) | static_cast<unsigned int>(Elem::B)
    ,   second = static_cast<unsigned int>(Elem::A) | static_cast<unsigned int>(Elem::C)
    };
};

Upvotes: 1

max66
max66

Reputation: 66200

What about casting the values of Elem enum?

        first = int(Elem::A) | int(Elem::B),
        second = int(Elem::A) | int(Elem::C),

Upvotes: 1

Related Questions