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.)
// clang-format off
enum class TEST
{
BEGIN = __LINE__
, ONE
, TWO
, NUMBER = __LINE__ - BEGIN - 1
};
// clang-format on
auto const TEST_SIZE = TEST::NUMBER;
// or this might be better
constexpr int COUNTER(int val, int )
{
return val;
}
constexpr int E_START{__COUNTER__};
enum class E
{
ONE = COUNTER(90, __COUNTER__) , TWO = COUNTER(1990, __COUNTER__)
};
template<typename T>
constexpr T E_SIZE = __COUNTER__ - E_START - 1;
// clang-format off
constexpr auto TEST_START_LINE = __LINE__;
enum class TEST { // Subtract extra lines from TEST_SIZE if an entry takes more than one
ONE = 7
, TWO = 6
, THREE = 9
};
constexpr auto TEST_SIZE = __LINE__ - TEST_START_LINE - 3;
// clang-format on
A problem (shared with UglyCoder but not with Cameron) is that it makes newlines and comments significant ... which is unexpected. So someone could add an entry with whitespace or a comment without adjusting TEST_SIZE's calculation. This means that code formatters can break this. After evg656e's comment, I edited the answer to disable clang-format, but caveat emptor if you use a different formatter.
enum class Example : std::int32_t
{
A, B, C, D, E
};
static constexpr std::int32_t ExampleCount = 5;
This is only scratching the surface with regards to the boost preprocessor tools. For example, your macro could also define to/from string conversion utilities and ostream operators for your strongly typed enum.
As an aside, I happen to strongly agree with @FantasticMrFox that the additional Count enumerated value employed in the accepted answer will create compiler warning headaches galore if using a switch statement. I find the unhandled case compiler warning quite useful for safer code maintenance, so I wouldn't want to undermine it.
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:
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.:
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 an 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.
All specializations of get_enumerators<T> shall meet the
TransformationTrait requirements (20.10.1). The nested type named
type designates a meta-object type satisfying
ObjectSequence, containing elements which satisfy Enumerator and
reflect the enumerators of the enumeration type reflected by T.
[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 the
UnaryTypeTrait requirements (20.10.1) with a base characteristic of
integral_constant<size_t, N>, where N 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, "");
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.