Is it possible to determine the cardinality of a c++ enum class
:
enum class Example { A, B, C, D, E };
I tried to use si
Reflection TS, particularly [reflect.ops.enum]/2 of the latest version of the Reflection TS draft offers the get_enumerators
TransformationTrait
operation:
[reflect.ops.enum]/2
template <Enum T> struct get_enumerators
All specializations of
get_enumerators<T>
shall meet theTransformationTrait
requirements (20.10.1). The nested type namedtype
designates a meta-object type satisfyingObjectSequence
, containing elements which satisfyEnumerator
and reflect the enumerators of the enumeration type reflected byT
.
[reflect.ops.objseq] of the draft covers ObjectSequence
operations, where particularly [reflect.ops.objseq]/1 covers the get_size
trait for extracting the number of elements for a meta-object satisfying ObjectSequence
:
[reflect.ops.objseq]/1
template <ObjectSequence T> struct get_size;
All specializations of
get_size<T>
shall meet theUnaryTypeTrait
requirements (20.10.1) with a base characteristic ofintegral_constant<size_t, N>
, whereN
is the number of elements in the object sequence.
Thus, in Reflection TS were to be accepted and implemented in its current form, the number of elements of an enum can be computed, at compile time, as follows:
enum class Example { A, B, C, D, E };
using ExampleEnumerators = get_enumerators<Example>::type;
static_assert(get_size<ExampleEnumerators>::value == 5U, "");
where we are likely to see alias templates get_enumerators_v
and get_type_v
to simplify the reflection further:
enum class Example { A, B, C, D, E };
using ExampleEnumerators = get_enumerators_t<Example>;
static_assert(get_size_v<ExampleEnumerators> == 5U, "");
As stated by Herb Sutter's Trip report: Summer ISO C++ standards meeting (Rapperswil) from the June 9, 2018 ISO C++ committee summer meeting, Reflection TS has been declared as feature-complete
Reflection TS is feature-complete: The Reflection TS was declared feature-complete and is being sent out for its main comment ballot over the summer. Note again that the TS’s current template metaprogramming-based syntax is just a placeholder; the feedback being requested is on the core “guts” of the design, and the committee already knows it intends to replace the surface syntax with a simpler programming model that uses ordinary compile-time code and not
<>
-style metaprogramming.
and was initially planed for C++20, but it's somewhat unclear if Reflection TS will still have a chance to make it into the C++20 release.
There is another way that doesn’t rely on line counts or templates. The only requirement is sticking the enum values in their own file and making the preprocessor/compiler do the count like so:
my_enum_inc.h
ENUMVAL(BANANA)
ENUMVAL(ORANGE=10)
ENUMVAL(KIWI)
...
#undef ENUMVAL
my_enum.h
typedef enum {
#define ENUMVAL(TYPE) TYPE,
#include "my_enum_inc.h"
} Fruits;
#define ENUMVAL(TYPE) +1
const size_t num_fruits =
#include "my_enum_inc.h"
;
This allows you to put comments with the enum values, re-assign values and does not inject an invalid 'count' enum value that needs to be ignored/accounted for in code.
If you don't care about comments you don't need an extra file and can do as someone above mentioned, e.g.:
#define MY_ENUM_LIST \
ENUMVAL(BANANA) \
ENUMVAL(ORANGE = 7) \
ENUMVAL(KIWI)
and replace the #include "my_enum_inc.h"
directives with MY_ENUM_LIST but you'll need to #undef ENUMVAL
after each use.
No , you have to write it in the code.
You can also consider static_cast<int>(Example::E) + 1
which eliminates the extra element.
Not directly, but you could use the following trick:
enum class Example { A, B, C, D, E, Count };
Then the cardinality is available as static_cast<int>(Example::Count)
.
Of course, this only works nicely if you let values of the enum be automatically assigned, starting from 0. If that's not the case, you can manually assign the correct cardinality to Count, which is really no different from having to maintain a separate constant anyway:
enum class Example { A = 1, B = 2, C = 4, D = 8, E = 16, Count = 5 };
The one disadvantage is that the compiler will allow you to use Example::Count
as an argument for an enum value -- so be careful if you use this! (I personally find this not to be a problem in practice, though.)
Another kind of "stupid" solution to this is:
enum class Example { A, B, C, D, E };
constexpr int ExampleCount = [] {
Example e{};
int count = 0;
switch (e) {
case Example::A:
count++;
case Example::B:
count++;
case Example::C:
count++;
case Example::D:
count++;
case Example::E:
count++;
}
return count;
}();
By compiling this with -Werror=switch
you make sure to get a compiler warning if you omit or duplicate any switch case. It's also constexpr so this is computed at compile time.
But note that even for a en enum class
the default initialized value is 0 even if the first value of the enum is not 0. So you have to either start on 0 or explicitly use the first value.