3

A couple of years ago I asked a question using an example to simulate a rich Java like enum class in C++0x. I am asking for suggestions on how to improve the design with a goal towards (1) machieving constexpr and (2) avoiding the manual steps where I effectively (a) declare the nested enum values, (b) declare and hopefully soon constexpr initialize the static fields, and (c) initialize the set of strict weak ordered enums. I wonder if templates may help in this area.

The code is pretty straight forward and is shown in the following coliru live demo.

Specifically, I wanted to be able to:

- Switch on values.

The code snippet below shows how this works, the restriction was that instead of switching on the BetterEnum::EnumValue1, I had to instead switch on the nested enum value - this is not obvious but it uses the integral cast operator:

//! Integral cast operator for switch statements (cast to named enum).
constexpr operator const Value() const noexcept {
    return mValue;
}

I am not sure how to work around this restriction.

BetterEnum val = BetterEnum::EnumValue1;

switch (val) {
case BetterEnum::enumvalue1:
    std::cout << BetterEnum::EnumValue1.getStringVal() << std::endl;
    break;
case BetterEnum::enumvalue2:
    std::cout << BetterEnum::EnumValue2.getStringVal() << std::endl;
    break;
case BetterEnum::enumvalue3:
    std::cout << BetterEnum::EnumValue3.getStringVal() << std::endl;
    break;
default:
    ;
}
std::cout << BetterEnum::EnumValue1 << std::endl;

- Perform string based enum lookup.

//! Lookup Enum by <code>aStringVal</code>.
static BetterEnum valueOf(const std::string_view aStringVal) {
    for (const auto& rNext : getValues()) {
        if (rNext.getStringVal() == aStringVal) {
            return rNext;
        }
    }
    throw std::invalid_argument(
        std::string("Illegal Argument: ") + std::string(aStringVal));
}

- Perform value based enum lookup.

//! Lookup Enum by <code>aValue</code>.
static BetterEnum valueOf(const Value aValue) {
    for (const auto& next : getValues()) {
        if (next.mValue == aValue) {
            return next;
        }
    }
    throw std::invalid_argument(
        std::string("Illegal Argument: ") + std::to_string(aValue));
}

- Implement as header only.

I recently added support for this by using inline const initializers at the bottom of the class.

inline const BetterEnum BetterEnum::EnumValue1(BetterEnum::enumvalue1, "EnumValue1"sv);
inline const BetterEnum BetterEnum::EnumValue2(BetterEnum::enumvalue2, "EnumValue2"sv);
inline const BetterEnum BetterEnum::EnumValue3(BetterEnum::enumvalue3, "EnumValue3"sv);

- Add constexpr support.

Although partially added - use of the std::set<T> restricts the number of members that I can make constexpr as this would require std::set to be constexpr (which it is not, although std::array<T, N> is but that doesn't guarantee the uniqueness of the emum entries ordered via strict weak ordering via the operator<() operator).

My original non-constexpr design used strings - I had to change these to string_views to the constexpr constructor - however I am not sure how to avoid the using namespace std::literals; in the header file to avoid polluting the namespace for every file that includes this header.

- Add stream insertion operator support.

friend std::ostream& operator<<(std::ostream& os, const BetterEnum& rhs) {
    os << rhs.getStringVal();
    return os;
}

We have moved on a few years and are currently at c++17 with C++20 looming on the horizon. I wanted to see if I could improve on the the class which I have been dragging along on various projects over the years.

Specifically I would like to know if there is some way to make the static members constexpr. C++17 introduced the notion of improved static variable initialization. This allowed me to improve the class to a header only implementation, however if I attempt to initialize the static class members inside the class definition

class BetterEnum final {
public:
    enum Value {
        enumvalue1, enumvalue2, enumvalue3
    };

    static constexpr BetterEnum EnumValue1 = BetterEnum(BetterEnum::enumvalue1, "EnumValue1"sv);
    static constexpr BetterEnum EnumValue2 = BetterEnum(BetterEnum::enumvalue2, "EnumValue2"sv);
    static constexpr BetterEnum EnumValue3 = BetterEnum(BetterEnum::enumvalue3, "EnumValue3"sv);

...
}

I get the following errors:

main.cpp:15:95: error: invalid use of incomplete type 'class BetterEnum'
     static constexpr BetterEnum EnumValue1 = BetterEnum(BetterEnum::enumvalue1, "EnumValue1"sv);
                                                                                               ^
main.cpp:9:7: note: definition of 'class BetterEnum' is not complete until the closing brace
 class BetterEnum final {
Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
johnco3
  • 2,401
  • 4
  • 35
  • 67
  • 2
    Upvoted for reminding me why A) I hate C++ and prefer programming in Java and B) I am really jealous about many of the cool things you can do in C++. – GhostCat May 21 '19 at 14:10
  • I used to be the other way around but the evolving standard has made me change my mind – johnco3 May 21 '19 at 14:15
  • I'm not sure I understand the C++ code or the explanation in the link. `Previously only methods/functions could be specified as inline, but now you can do the same with variables` Is that what you want to emulate? Some other feature? The only way to emulate it 100% is to use C++. – markspace May 21 '19 at 14:37
  • Good point, I am using C++ and I made a slight tweak based on https://stackoverflow.com/questions/29432283/c-static-constexpr-field-with-incomplete-type which looks promising – johnco3 May 21 '19 at 14:47

0 Answers0