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
There is one trick based on X()-macros: image, you have the following enum:
enum MyEnum {BOX, RECT};
Reformat it to:
#define MyEnumDef \
X(BOX), \
X(RECT)
Then the following code defines enum type:
enum MyEnum
{
#define X(val) val
MyEnumDef
#undef X
};
And the following code calculates number of enum elements:
template <typename ... T> void null(T...) {}
template <typename ... T>
constexpr size_t countLength(T ... args)
{
null(args...); //kill warnings
return sizeof...(args);
}
constexpr size_t enumLength()
{
#define XValue(val) #val
return countLength(MyEnumDef);
#undef XValue
}
...
std::array<int, enumLength()> some_arr; //enumLength() is compile-time
std::cout << enumLength() << std::endl; //result is: 2
...
One trick you can try is to add a enum value at the end of your list and use that as the size. In your example
enum class Example { A, B, C, D, E, ExampleCount };
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;
This is derived from UglyCoder's answer but improves it in three ways.
BEGIN
and SIZE
) (Cameron's answer also has this problem.)
It retains UglyCoder's advantage over Cameron's answer that enumerators can be assigned arbitrary values.
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.
If you make use of boost's preprocessor utilities, you can obtain the count using BOOST_PP_SEQ_SIZE(...)
.
For example, one could define the CREATE_ENUM
macro as follows:
#include <boost/preprocessor.hpp>
#define ENUM_PRIMITIVE_TYPE std::int32_t
#define CREATE_ENUM(EnumType, enumValSeq) \
enum class EnumType : ENUM_PRIMITIVE_TYPE \
{ \
BOOST_PP_SEQ_ENUM(enumValSeq) \
}; \
static constexpr ENUM_PRIMITIVE_TYPE EnumType##Count = \
BOOST_PP_SEQ_SIZE(enumValSeq); \
// END MACRO
Then, calling the macro:
CREATE_ENUM(Example, (A)(B)(C)(D)(E));
would generate the following code:
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.
More on boost preprocessor tools here: https://www.boost.org/doc/libs/1_70_0/libs/preprocessor/doc/AppendixA-AnIntroductiontoPreprocessorMetaprogramming.html
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.
For C++17 you can use magic_enum::enum_count
from lib https://github.com/Neargye/magic_enum:
magic_enum::enum_count<Example>()
-> 4.
This library uses a compiler-specific hack (based on __PRETTY_FUNCTION__
/ __FUNCSIG__
), which works on Clang >= 5, MSVC >= 15.3 and GCC >= 9.
We go through the given interval range, and find all the enumerations with a name, this will be their count. Read more about limitations
Many more about this hack in this post https://taylorconor.com/blog/enum-reflection.
It can be solved by a trick with std::initializer_list:
#define TypedEnum(Name, Type, ...) \
struct Name { \
enum : Type{ \
__VA_ARGS__ \
}; \
static inline const size_t count = []{ \
static Type __VA_ARGS__; return std::size({__VA_ARGS__}); \
}(); \
};
Usage:
#define Enum(Name, ...) TypedEnum(Name, int, _VA_ARGS_)
Enum(FakeEnum, A = 1, B = 0, C)
int main()
{
std::cout << FakeEnum::A << std::endl
<< FakeEnun::count << std::endl;
}