Counting the Elements in a C++ Enum

I recently had a special requirement to determine the number of elements in an enum type, and in the process I learned the following clever tricks.

Explanation

These techniques originated with a question on Stack Overflow: “Can the number of elements in a C++ enum class be determined?”

The Craftsperson

Basic

The simplest approach is:

1
enum class Example { A, B, C, D, E, Count };

Since enumerator values increase from 0 by default, the number of elements can be obtained with static_cast<int>(Example::Count).

Advanced

The method above does not work for enums with custom values, for example:

1
enum class Example { A = 1, B = 2, C = 4, D = 8, E = 16, Count = 5 };

Having to count them manually is undeniably inconvenient and tedious.

Foolproof

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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;
}();

This is merely my opinion on the implementation: more code means more opportunities for errors.

Assessment

When you use switch with this enum class, the compiler warns that a case is missing. It is not elegant.

Moreover, the method above has limited applicability and makes the programmer the greatest risk in the system. As Laozi said: “If something can be automated, automate it.”

Macro Magic

The __LINE__ Macro

As everyone knows, __LINE__ represents the current line number. With that in mind, we can write:

1
2
3
4
5
6
7
8
9
// 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

Subtract the line numbers, and disable clang-format to ensure that formatting does not break the layout; the result is the number of elements.

GCC’s Nonstandard __COUNTER__ Macro

__COUNTER__ is a nonstandard compiler extension provided by GNU compilers. It can be thought of as a counter representing an integer. Its value is generally initialized to 0, and it is automatically incremented by 1 each time the compiler encounters it during compilation.

This lets us implement the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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;

Boost

If you use Boost’s preprocessor utilities, you can use BOOST_PP_SEQ_SIZE(...) to obtain the count.

For example, the CREATE_ENUM macro can be defined as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#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 invoke the macro:

1
CREATE_ENUM(Example, (A)(B)(C)(D)(E));

The macro expansion produces the following code:

1
2
3
4
5
enum class Example : std::int32_t 
{
   A, B, C, D, E 
};
static constexpr std::int32_t ExampleCount = 5;

This is just the tip of the iceberg when it comes to Boost’s preprocessor utilities. For example, macros can also define to/from-string conversion utilities and ostream operators for strongly typed enums.

Read more about Boost’s preprocessor utilities.

Using Variadic __VA_ARGS__

1
2
3
4
5
6
7
8
#define Enum(Name, ...)                                                        \
    struct Name {                                                              \
        enum : int {                                                           \
            __VA_ARGS__                                                        \
        };                                                                     \
        private: struct en_size { int __VA_ARGS__; };                          \
        public: static constexpr  size_t count = sizeof(en_size)/sizeof(int);  \
    }

Usage:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
struct S {

    Enum(TestEnum, a=11, b=22, c=33);

    void Print() {
        std::cout << TestEnum::a << '\n';
        std::cout << TestEnum::b << '\n';
        std::cout << TestEnum::count << '\n';
    }

};


int main()
{        

    S d;
    d.Print();

    return 0
}

Output:

1
2
3
11
22
3

Best of the Bunch—Still Variadic __VA_ARGS__

This can be solved with a trick using std::initializer_list:

1
2
3
4
5
6
7
8
9
#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:

1
2
3
4
5
6
7
8
#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;
}

A Promising Future for Reflection

The C++ Reflection Technical Specification (hereafter, the Reflection TS), especially [reflect.ops.enum]/2 in the latest draft, provides the get_enumerators and TransformationTrait operations:

[reflect.ops.enum]/2

1
template <Enum T> struct get_enumerators

All specializations of get_enumerators<T> shall meet the requirements of TransformationTrait (20.10.1). The nested type named type specifies a metaobject type satisfying ObjectSequence, containing elements that satisfy Enumerator and reflect the enumerators of the enum type reflected by T.

The draft’s [reflect.ops.objseq] covers ObjectSequence operations. In particular, [reflect.ops.objseq]/1 covers the get_size trait for extracting the number of elements in a metaobject satisfying ObjectSequence:

[reflect.ops.objseq]/1

1
template <ObjectSequence T> struct get_size;

All specializations of get_size<T> shall meet the requirements of UnaryTypeTrait (20.10.1), with a base characteristic of integral_constant<size_t,N>, where N is the number of elements in the object sequence.

Therefore, under the form proposed and implemented in the Reflection TS, the number of elements in an enum could be computed at compile time as follows:

1
2
3
4
5
enum class Example { A, B, C, D, E };

using ExampleEnumerators = get_enumerators<Example>::type;

static_assert(get_size<ExampleEnumerators>::value == 5U, "");

We may see the alias templates get_enumerators_v and get_type_v introduced to simplify reflection further:

1
2
3
4
5
enum class Example { A, B, C, D, E };

using ExampleEnumerators = get_enumerators_t<Example>;

static_assert(get_size_v<ExampleEnumerators> == 5U, "");

As described in Herb Sutter’s [trip report on the Summer ISO C++ Standards Meeting (Rapperswil)], the Reflection TS was declared feature-complete at the ISO C++ Committee’s summer meeting beginning on June 9, 2018.

Reflection TS Feature Complete: The Reflection TS has been declared feature-complete and will undergo its main comment ballot in the summer. Note again that the TS’s current template-metaprogramming syntax is merely a placeholder; the requested feedback concerns the core “guts” of the design. The committee already knows that it intends to replace the surface syntax with a simpler programming model using ordinary compile-time code rather than <>-style metaprogramming.

Originally planned for C++20, but it is currently unclear whether the Reflection TS still has a chance of making it into C++20.

References