5

In my project I have a lot of enumerations that need to have additional attributes associated with the enumeration members and auxiliary static methods associated with the enumeration type.

As much as I know, this is not possible to have with the standard enum class MyItem {...}, so for each enum class in my project I have an auxiliary class MyItemEnum that encapsulates these auxiliary static methods and also instantiates auxiliary instances of itself, so that I can access their methods in order to get additional attributes.

Bellow an example (simplified as much as possible but I believe all the features to be discussed stayed there).

MyItem.h

enum class MyItem : unsigned int {
    Item1   = 1,
    Item2   = 5
};

class MyItemEnum {
private:
    MyItem myItem;
    size_t extInfo;

    MyItemEnum(const MyItem& myItem, size_t extInfo);
    ~MyItemEnum();
public:
    static MyItemEnum Item1;
    static MyItemEnum Item2;
    static const MyItemEnum &get(MyItem myItem);

    operator MyItem() const;
    size_t getExt() const;
    bool hasNext() const;
    MyItem next() const;
};

I think the meaning is obvious and I don't need to provide here the .cpp part... I use the MyItem as an argument to be passed in the interfaces and MyItemEnum when I need to access the extended functionality.

My first question is, is the approach above ok, or I should consider something completely different?

My second question concerns an optimization of this enumeration that I am trying to do using constexpr:

enum class MyItem : unsigned int {
    Item1   = 1,
    Item2   = 5
};

class MyItemEnum {
private:
    MyItem myItem;
    size_t extInfo;

    constexpr MyItemEnum(const MyItem& myItem, size_t extInfo);
public:
    static MyItemEnum Item1;
    static MyItemEnum Item2;
    static constexpr MyItemEnum &get(MyItem myItem);

    constexpr operator MyItem();
    constexpr size_t getExt();
    constexpr bool hasNext();
    constexpr MyItem next();
};

It compiles but apparently the constexpr doesn't get chance to get used because if I access:

MyItemEnum::Item1.getExt()

so the compiler doesn't know what values was Item1 instantiated with. Is there a chance that the expression above will be evaluated as constexpr during the link time optimization? Alternatively I could use

static constexpr MyItemEnum Item1 = MyItemEnum(MyItem::Item1, 123);

This would active the constexpr compile time optimizations but I am afraid that in some cases, when the constexpr is not possible to be compile-time evaluated, the compiler would have to create a local instance of MyItemEnum (instead of using reference to a single global static instance) and I am afraid that this could lead to a performance penalty (my real enums has more attributes than just a single member so the local instantiation can take some time?). Is this a justified concern?

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
David L.
  • 3,149
  • 2
  • 26
  • 28

2 Answers2

0

I haven't direct experience with the use of constexpr and the resulting compiler optimizations yet, but I can tell you that simply using const on the members of the class itself or the instances will both VS2012 and g++ 4.7 compilers to do cross-module optimization:

class MyItemEnum {
private:
    // make sure to put const here...
    const MyItem myItem;
    const size_t extInfo;

    MyItemEnum(const MyItem& myItem, size_t extInfo);
    ~MyItemEnum();
public:
    // and put const in here too...
    static const MyItemEnum Item1;
    static const MyItemEnum Item2;
};

The caveat is that the constructor must use the C++ style initializer list syntax, which shouldn't be a problem if you're just populating them with constant values anyway. (initializer lists only become a pain when non-trivial setup is required).

I have not verified this on Clang/LLVM, so if that's your toolchain then I strongly recommend you take this simplified example and disasm the result yourself. Disassembly of simple test cases can be pretty easy to parse even if you're not familiar with assembly languages. And in this case you can compile two builds: one set in a single module, and one split into two modules - and compare the results to make sure LTO is doing the job you need it to.

jstine
  • 3,234
  • 20
  • 21
0

I came across your question while trying to search for something else (using modern C++ attributes on enum members). I realize it has been almost ten years since you asked your question, but since I'm here, may as well try and leave the area better than I found it.

So, using VS2017 and C++14 (I realize the original question was for C++11, but it is 2022, and I already had a test project setup in this way), I had to change the code get to get to compile

    enum class MyItem : unsigned int {
        Item1 = 1,
        Item2 = 5
    };

    class MyItemEnum {
    private:
        MyItem myItem;
        size_t extInfo;

        constexpr MyItemEnum(MyItem myItemIn, size_t extInfoIn)
            : myItem(myItemIn)
            , extInfo(extInfoIn)
        {}
        constexpr MyItemEnum() : MyItemEnum(MyItem::Item1, ~0U) {}
    public:
        static constexpr MyItemEnum getItem1() { return { MyItem::Item1, 1337 }; }
        static constexpr MyItemEnum getItem2() { return { MyItem::Item2, 0xDEAD }; }
        static constexpr MyItemEnum get(MyItem myItem)
        {
            if (myItem == MyItem::Item1) return getItem1();
            if (myItem == MyItem::Item1) return getItem2();

            // error...
            return MyItemEnum{};
        }

        constexpr operator MyItem() const { return myItem; }
        constexpr size_t getExt() const { return extInfo; }
        constexpr bool hasNext() const { return false; }
        constexpr MyItem next() const
        {
            if (myItem == MyItem::Item1) return getItem2();

            // error...
            return MyItemEnum{};
        }
    };

    static_assert(MyItemEnum::getItem1().getExt()==1337, "Expected 1337");
    static_assert(MyItemEnum::getItem2().getExt()==0xDEAD, "Expected 0xDEAD");
  1. I had to add a default constructor, as I don't know how you handled errors.
  2. I had to change the static Item1 and Item2 to be constexpr and be methods, as static members require an in-class initializer, but the compiler didn't recognize MyItemEnum, complaining use of undefined type 'MyItemEnum' when it tried to compile static constexpr MyItemEnum Item1 { MyItem::Item1, 1337 };. Perhaps this isn't the case in C++17 or later.
  3. Just a note, constexpr implies inline
  4. When you're using constexpr, the definition must already be in scope, so none of MyItemEnum can be defined in a .cpp or be defined after the callsite if you want to use them as constexpr
  5. With these changes, the code compiles and the static asserts don't fail.
  6. Even with constexpr, the compiler may still decide to not evaluate it at compile time. I recommend when you're concerned about codegen that you inspect the assembly before and after. There are ways to coerce the compiler to do things at compile time if you find it isn't cooperating.
  7. For LTCG cases, you may have to inspect the assembly in the final executable since, in VC++ at least, the .obj contains undocumented data that is specific to the compiler and isn't collapsed into native code until link time (as the name implies).
kornman00
  • 808
  • 10
  • 27