120

Is it possible to determine the cardinality of a c++ enum class:

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

I tried to use sizeof, however, it returns the size of an enum element.

sizeof(Example); // Returns 4 (on my architecture)

Is there a standard way to get the cardinality (5 in my example) ?

Shoe
  • 74,840
  • 36
  • 166
  • 272
bquenin
  • 1,470
  • 2
  • 12
  • 12
  • 1
    I thought there might have been a specific c++ 11 mechanism – bquenin Feb 20 '13 at 20:32
  • 11
    This is not a duplicate, by the way. `enum` and `enum class`es are very different concepts. – Shoe Dec 02 '13 at 02:13
  • @Shoe ...are they really, though? – Kyle Strand Sep 06 '16 at 19:55
  • 1
    This seems like an XY problem, I know it was from long ago, but do you remember why you needed to do this? You can't iterate over an `enum class` values, so what would the benifit to knowing the number be? – Fantastic Mr Fox May 02 '19 at 13:31
  • 6
    @FantasticMrFox Probably not OP's reason, but an example would be initializing an array to the "size" of an enum, whose enumerators will then be used to index into the array. I suspect there are many more. – Ad N Dec 08 '21 at 09:05

18 Answers18

104

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.)

Zitrax
  • 19,036
  • 20
  • 88
  • 110
Cameron
  • 96,106
  • 25
  • 196
  • 225
  • 1
    The enum values are type safe in an enum class so 'Count' will be of type Example here and not int, right? You would have to cast 'Count' to an int first in order to use it for size. – Man of One Way Mar 26 '15 at 13:29
  • @Man: Yes, this trick is a bit messier with `enum class`es instead of plain `enum`s. I'll edit in a cast to be clear. – Cameron Mar 26 '15 at 13:38
  • 27
    If you use a switch statement with this enum, any decent compiler will warn you that you are missing one case. If this is used a lot that might be very annoying.. Could be better to just have a separated variable in this specific case. – Fantastic Mr Fox May 27 '15 at 15:07
  • @FantasticMrFox I agree 100%, based on experience. That compiler warning is an important one too. I've posted an alternative approach, more in line with the spirit of your recommendation. – arr_sea May 01 '19 at 22:17
43

For C++17 you can use magic_enum::enum_count from lib https://github.com/Neargye/magic_enum:

magic_enum::enum_count<Example>() -> 4.

Where is the drawback?

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.

Neargye
  • 1,870
  • 1
  • 12
  • 12
  • 2
    This is awesome! No need to modify existing code to count the number of enum members. Also this seems very elegantly implemented (just skimmed through the code)! – andreee Jul 22 '19 at 12:05
  • 2
    one huge limitation is that enum values are bounded to small range(and at most they can be expanded to short range, not to int range). And if the library fails it will not fail with a static_assert or compile time error, it will just "clip" the enum values and return you a subset of your enum values. Not that it can be done better in C++, but important limitation. Still upvoted, your lib is my favorite for cases when I can use it. – NoSenseEtAl Jun 30 '21 at 14:47
  • The impact on compile-times would be interesting, esp. with larger ranges. – Trass3r Jan 29 '22 at 04:55
  • @Trass3r I'm working on a new version, when I'm done I'll try to measure the compile time impact. – Neargye Jan 30 '22 at 17:49
  • 1
    There's probably optimization potential. Unfortunately clang's `-ftime-trace` didn't report much for constexpr code the last time I tried: https://github.com/llvm/llvm-project/issues/42754 – Trass3r Jan 31 '22 at 15:42
32
// 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

This is derived from UglyCoder's answer but improves it in three ways.

  • There are no extra elements in the type_safe enum (BEGIN and SIZE) (Cameron's answer also has this problem.)
    • The compiler will not complain about them being missing from a switch statement (a significant problem)
    • They cannot be passed inadvertently to functions expecting your enum. (not a common problem)
  • It does not require casting for use. (Cameron's answer has this problem too.)
  • The subtraction does not mess with the size of the enum class type.

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. 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.

Eponymous
  • 6,143
  • 4
  • 43
  • 43
  • 3
    tools like clang-format can easily break this too – evg656e Mar 16 '21 at 16:57
  • 1
    Thank you @evg656e. I added a workaround for `clang-format` and mentioned code formatters explicitly as part of the problem about adding whitespace. – Eponymous Mar 16 '21 at 20:57
9
// 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;
Eponymous
  • 6,143
  • 4
  • 43
  • 43
UglyCoder
  • 199
  • 1
  • 9
  • Clever! Can't have any comments or unusual spacing, of course, and for really large source files, the underlying value type might be larger than it would be otherwise. – Kyle Strand Sep 05 '16 at 00:46
  • @Kyle Strand: there is that issue: using char and you have more than 256 enumerators too. But the compiler has the good manners to notify you of truncations etc. __LINE__ is an integer literal and using #line has a limit of [1, 2147483647] – UglyCoder Sep 06 '16 at 18:35
  • Ah, okay. Still, even an enum that would otherwise be a `short` might be bumped up to `int` e.g. when doing a unity build. (I'd say this is more of an issue with unity builds than with your proposed trick, though.) – Kyle Strand Sep 06 '16 at 19:55
  • Trick? :-) I use it, but rarely and with due judgement. Like everything in coding we need to way up the pros and cons and especially long-term maintenance implications. I've used it recently to create an enum class from a list of C #defines (OpenGL wglExt.h). – UglyCoder Sep 08 '16 at 05:51
8

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;
}
ixjxk
  • 121
  • 1
  • 4
6

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
...
Kirill Suetnov
  • 145
  • 1
  • 5
  • This can be made easier by removing the comma from `#define MyEnumDef` (and putting it in `#define X(val) val`), which allows you to count the number of elements using just `#define X(val) +1` `constexpr std::size_t len = MyEnumDef;`. – HolyBlackCat Oct 28 '20 at 06:29
4

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 };
David Nehme
  • 21,379
  • 8
  • 78
  • 117
  • 1
    Compared to the behavior with plain `enum`s, this will not work as `ExampleCount` is of type `Example`. To obtain the number of elements in `Example`, `ExampleCount` would have to be cast to an integer type. – applesoup Dec 18 '18 at 17:23
4

Reflection TS: static reflection of enums (and other types)

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 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, "");

Status on Reflection TS

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.

dfrib
  • 70,367
  • 12
  • 127
  • 192
2

No , you have to write it in the code.

2

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.

arr_sea
  • 841
  • 10
  • 16
  • @FantasticMrFox Thanks for pointing out a concerning problem with the accepted answer. I've provided an alternative approach here that is more in line with the spirit of your recommendation. – arr_sea May 01 '19 at 22:15
2

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.

2

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 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.

Zitrax
  • 19,036
  • 20
  • 88
  • 110
1

This is the solution that worked for me as in 2020, using visual studio 2019

#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:

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:

11
22
3
The Oathman
  • 125
  • 7
0

You can also consider static_cast<int>(Example::E) + 1 which eliminates the extra element.

Fabio A. Correa
  • 1,968
  • 1
  • 17
  • 26
  • 8
    This answer is correct for this specific programming problem, but is in general far from elegant and error prone. The enum can be extended with new values in the future that can replace `Example::E` as last value in the enum. Even if this is not the case, `Example::E`'s literal value may change. – Matthias May 15 '17 at 14:58
0

One really cool solution uses X() macros:

#define EXAMPLE_ITEMS(X) \
    X(A, 7)              \
    X(B, 18)             \
    X(C, 22)             \
    X(D, 91)             \
    X(E, 10)
#define GENERATE_ITEM(name, value) name = value,
#define ADD_ONE(name, value) + 1

enum class Example
{
    EXAMPLE_ITEMS(GENERATE_ITEM)
    Count = 0 EXAMPLE_ITEMS(ADD_ONE)
};

#undef EXAMPLE_ITEMS
#undef GENERATE_ITEM
#undef ADD_ONE

int main()
{
    cout << "A: " << static_cast<int>(Example::A) << '\n';
    cout << "B: " << static_cast<int>(Example::B) << '\n';
    cout << "C: " << static_cast<int>(Example::C) << '\n';
    cout << "D: " << static_cast<int>(Example::D) << '\n';
    cout << "E: " << static_cast<int>(Example::E) << '\n';
    cout << "Count: " << static_cast<int>(Example::Count) << '\n';
}

This code will print out:

A: 7
B: 18
C: 22
D: 91
E: 10
Count: 5

It works similarly to HolyBlackCat's suggestion to Kirill Suetnov's answer. Basically, the macros generate the enum's contents, using both the names and values, and then add one more item: Count. Count is set to 0 followed by a +1 for every item in the enum, resulting in the total number of items in the enum.

The pros of this method include its simplicity and the fact that it can compile in older versions of C++ as well because it does not use newer features, such as constexpr.

The cons include its use of preprocessor macros, which many might advise against. However, I still think it is a cool solution worth adding here in case anyone wants to use it in the future.

-1

If you don't have too many values in your enum, and these values are rather stable, you can go low tech:

enum Continent{
    NORTH AMERICA,
    SOUTH AMERICA,
    ASIA,
    AFRICA,
    EUROPE
};
const unsigned int NumberOfContinents=5;
Ludovic Aubert
  • 9,534
  • 4
  • 16
  • 28
-1

Not a trick and depending on how many entries do you have you might not want to do this. But I'm using it and if it needs any tweak I will update it


#include <iostream>
#include <string>
#include <sstream>

namespace overflow
{
    // clang-format off
    enum class SupportedPlatforms: unsigned int { LINUX, WINDOWS, MACOS, ANDROID, IOS };
    std::ostream &operator<<(std::ostream &os, const SupportedPlatforms day)
    {
        switch (day)
        {
        case SupportedPlatforms::LINUX:      os << "Linux";      break;
        case SupportedPlatforms::WINDOWS:    os << "Windows";    break;
        case SupportedPlatforms::MACOS:      os << "Mac OS X";   break;
        case SupportedPlatforms::ANDROID:    os << "Android";    break;
        case SupportedPlatforms::IOS:        os << "iOS";        break;
        // default: os.setstate(std::ios_base::failbit);
        }
        return os;
    }

    enum class Test: unsigned int { };
    inline std::ostream &operator<<(std::ostream &os, const Test x)
    {
        switch (x)
        default: break;
        return os;
    }

    enum class Seasons: unsigned int { AUTUMN, WINTER, SPRING, SUMMER };
    inline std::ostream &operator<<(std::ostream &os, const Seasons day)
    {
        switch (day)
        {
        case Seasons::AUTUMN:   os << "Autumn"; break;
        case Seasons::WINTER:   os << "Winter"; break;
        case Seasons::SPRING:   os << "Spring"; break;
        case Seasons::SUMMER:   os << "Summer"; break;
        // default: os.setstate(std::ios_base::failbit);
        }
        return os;
    }
    // clang-format on

    /**
     * @brief This class will allow you to count the elements of ordered 
     * unsigned int enums that had implemented the stream insertion operator.
     *
     * IMPORTANT: Please note that the lack of a definition in the switch/case 
     * or the absense of a normal unsigned index e.g enum class {A=1, B=3, C=5, X=400} 
     * will result in an incorrect count.
     *
     * @tparam T Reperesents the enum type you are going to count.
     */
    template <typename T>
    struct EnumsLengthCounter
    {
        unsigned int getCount()
        {
            unsigned int count{};
            std::string result{};
            do
            {
                std::stringstream results;
                results << (T)count;
                count++;
                result = results.str();
            } while (result.length());

            return --count;
        };
    };
} // namespace overflow

int main()
{
    using namespace overflow;
    EnumsLengthCounter<SupportedPlatforms> platforms_ctr;
    std::cout << "How many platforms? " << platforms_ctr.getCount() << std::endl;

    EnumsLengthCounter<Seasons> seasons_ctr;
    std::cout << "How many seasons? " << seasons_ctr.getCount() << std::endl;

    EnumsLengthCounter<Test> test_ctr;
    std::cout << "How many tests? " << test_ctr.getCount() << std::endl;
}

/*
    TL;DR | About the switches:
    Notice that while having a `default` will "interrupt" the stream
    (avoiding empty strings getting printed to the console), it won't
    prevent you from picking up empty values. The following loop for
    exaple:
        std::vector<SupportedPlatforms> bucket;
        for (int i = 0; i < 10; i++)
            bucket.push_back((SupportedPlatforms)i);

    will collect 10 items while the enum only contains 5. And if,
    say, you're making buttons/labels out of this "tags" then you will
    discover that you're going to need a conditional to make this work.
    However, not having a `default` case is very useful for spotting
    compiler warnings about missing cases in the switch.
*/

Output:

~/overflow$ ./build/test
How many platforms? 5
How many seasons? 4
How many test? 0

Keep in mind that it is an object oriented language. The enum class SupportedPlatforms for example, seems to be clearly a bad design

Karmavil
  • 863
  • 10
  • 13
-1

Or borrow the idea from std::numeric_limits to create your own enum-extension class, and now you can use those enums as template parameters and derive the count from them (and lowest value, highest value, to_string() and any other generic need you may have from enums...) You don't even need to implement all the features all the time, the compiler will tell you that you need to define specific members

// generic class
template <typename T>
struct enum_info
{
    using integral_type = std::underlying_type_t<T>;

    static_assert(std::is_enum_v<T>, "enum_info requires enum type");

    static constexpr size_t count();// no implementation
};

enum a { aa, ab, ac }; // old enum
template<>
struct enum_info<a> // specialize for enum a
{
    static constexpr size_t count()
    {
        return 3;
    }
};

enum class b { ba, bb, bc, bd }; // enum class
template<>
struct enum_info<b> // specialize for enum class b
{
    static constexpr size_t count()
    {
        return 4;
    }
};

enum class c { ca, cb };

// usage:
size_t count_a = enum_info<a>::count(); // ==3
size_t count_b = enum_info<b>::count(); // ==4
//size_t count_c = enum_info<c>::count(); // C4878 no definition for enum_info<c>::count()
oreubens
  • 329
  • 1
  • 9