Disclaimer: shameless plug – I am the author.
Reflective enums are possible in C++. I wrote a header-only library that captures a bunch of "patterns" at compile time and gives you syntax like this:
ENUM(Class1, int, FOO_1 = 2, FOO_X = 1, FOO_BAR = 6)
size_t count = Class1::_size;
for (size_t index = 0; index < Class1::_size; ++index)
do_anything(Class1::_values()[index]);
What it does internally is use the macro to generate an array of the values that you have declared, kind of like in your question, and use a bunch of other tricks to allow you to use initializers naturally. It then provides iterators and other things on top of the array.
Here is a link: https://github.com/aantron/better-enums
EDIT – internals
Here is a pseudocode sketch of what it does internally. The reason I am only giving a "sketch" is because there are a bunch of issues to consider when doing this portably. I will touch on all the most important elements.
ENUM(Class1, int, FOO_1 = 2, FOO_X = 1, FOO_BAR = 6)
notionally expands to
struct Class1 {
enum _enumerated { FOO_1 = 2, FOO_X = 1, FOO_BAR = 6 };
// Fairly obvious methods for how to iterate over _values and
// _names go here. Iterators are simply pointers into _values
// and _names below.
static size_t _size = sizeof(_values) / sizeof(int);
int _value;
};
int _values[] = {(fix_t<Class1>)Class1::FOO_1 = 2,
(fix_t<Class1>)Class1::FOO_X = 1,
(fix_t<Class1>)Class1::FOO_BAR = 6};
const char *_names[] = {"FOO_1 = 2", "FOO_X = 1", "FOO_BAR = 6"};
This is done by using variadic macros and stringization. The methods that deal with strings treat not only \0
, but also space and equals as terminators, which allows them to ignore the initializers in the stringized constants that you see in _names
.
The type fix_t
is necessary because having assignments inside an array initializer is not valid C++. What that type does is take on the value of the enum, then ignore the assignment by an overloaded assignment operator, and then return the original value. A sketch:
template <typename Enum>
struct fix_t {
Enum _value;
fix_t(Enum value) : _value(value) { }
const fix_t& operator =(int anything) const { return *this; }
operator Enum() const { return _value; }
};
This makes the _values
array possible declare even in the presence of initializers.
Of course, these arrays need to be prefixed so that you can have more than one enum like this. They also need to have the same as "extern inline" linkage for functions, so that they are shared between multiple compilation units.