fuenfundachtzig
fuenfundachtzig

Reputation: 8352

(How) can I count the items in an enum?

This question came to my mind, when I had something like

enum Folders {FA, FB, FC};

and wanted to create an array of containers for each folder:

ContainerClass*m_containers[3];
....
m_containers[FA] = ...; // etc.

(Using maps it's much more elegant to use: std::map<Folders, ContainerClass*> m_containers;)

But to come back to my original question: What if I do not want to hard-code the array size, is there a way to figure out how many items are in Folders? (Without relying on e.g. FC being the last item in the list which would allow something like ContainerClass*m_containers[FC+1] if I'm not mistaken.)

Upvotes: 117

Views: 172306

Answers (8)

How about traits, in an STL fashion? For instance:

enum Foo
{
    Bar,
    Baz
};

write an

std::numeric_limits<enum Foo>::max()

specialization (possibly constexpr if you use c++11). Then, in your test code provide any static assertions to maintain the constraints that std::numeric_limits::max() = last_item.

For example for this enum the specialization would be:

namespace std {
    template<> class numeric_limits<enum Foo> {
    public:
       static size_t max() noexcept { return Foo::Baz; }
       static size_t size() noexcept { return max() + 1; }
    };
}

If you want to make it general, you'll have to use an enum class, because enums cannot have duplicate identifiers:

for example https://godbolt.org/z/aEq3MfWee:

#include <limits>
#include <type_traits>
#include <stdio.h>

enum class test { a, b, Size };
enum class test2 { a, b, c, Size };
enum test3 { a, v, q, r, Size };
enum test4 { z, size };

    template <typename T>
    concept is_enumeration = requires {  std::is_enum_v<T>; }; 


namespace std {
    template<is_enumeration T> class numeric_limits<T>  {
    public:
       static size_t max() noexcept requires is_enumeration<T> { return size_t(T::Size); }
       static size_t size() noexcept requires is_enumeration<T> { return max() + 1; }
    };
}


int main() {
    const auto max = std::numeric_limits<test>::max();
    const auto size = std::numeric_limits<test>::size();
    printf("max = %i, size = %i\n", max, size);
    const auto size2 = std::numeric_limits<test3>::size();
    printf("size = %i\n", size2);
    //const auto size3 = std::numeric_limits<test4>::size(); //error
    //printf("size = %i\n", size3);
}

Upvotes: 10

Anton Krug
Anton Krug

Reputation: 1781

Similar to @wich answer, but slightly cleaner:

enum class Fruit:int {  // Assumes there are no gaps in the enum
    Apple = 0, 
    Banana,
    Orange,
    LAST_ENTRY_OF_ENUM
};

constexpr int FruitLastEntry = static_cast<int>(Fruit::LAST_ENTRY_OF_ENUM);

int stocksOfFruit[FruitLastEntry];

Using more modern constexpr and static_cast to get easy-to-use int value which can be used for array sizes, etc...

The name explicitly states that it's the LAST ENTRY and not SIZE, to ensure this doesn't have an implied meaning that it's bulletproof SIZE value and that it can be used only when the enum is sequential and there are no gaps in the numbering.

Godbolt link to play with: https://godbolt.org/z/7eKadd7T5

Upvotes: 2

user9512877
user9512877

Reputation: 13

Here is the best way to do it in compilation time. I have used the arg_var count answer from here.

#define PP_NARG(...) \
     PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...) \
         PP_ARG_N(__VA_ARGS__)

#define PP_ARG_N( \
          _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
         _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
         _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
         _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
         _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
         _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
         _61,_62,_63,N,...) N
#define PP_RSEQ_N() \
         63,62,61,60,                   \
         59,58,57,56,55,54,53,52,51,50, \
         49,48,47,46,45,44,43,42,41,40, \
         39,38,37,36,35,34,33,32,31,30, \
         29,28,27,26,25,24,23,22,21,20, \
         19,18,17,16,15,14,13,12,11,10, \
         9,8,7,6,5,4,3,2,1,0

#define TypedEnum(Name, ...)                                      \
struct Name {                                                     \
    enum {                                                        \
        __VA_ARGS__                                               \
    };                                                            \
    static const uint32_t Name##_MAX = PP_NARG(__VA_ARGS__);      \
}

#define Enum(Name, ...) TypedEnum(Name, __VA_ARGS__)

To declare an enum:

Enum(TestEnum, 
Enum_1= 0,
Enum_2= 1,
Enum_3= 2,
Enum_4= 4,
Enum_5= 8,
Enum_6= 16,
Enum_7= 32);

the max will be available here:

int array [TestEnum::TestEnum_MAX];
for(uint32_t fIdx = 0; fIdx < TestEnum::TestEnum_MAX; fIdx++)
{
     array [fIdx] = 0;
}

Upvotes: 1

BuvinJ
BuvinJ

Reputation: 11046

I like to use enums as arguments to my functions. It's an easy means to provide a fixed list of "options". The trouble with the top voted answer here is that using that, a client can specify an "invalid option". As a spin off, I recommend doing essentially the same thing, but use a constant int outside of the enum to define the count of them.

enum foobar { foo, bar, baz, quz };
const int FOOBAR_NR_ITEMS=4;

It's not pleasant, but it's a clean solution if you don't change the enum without updating the constant.

Upvotes: 2

user4425844
user4425844

Reputation:

I really do not see any way to really get to the number of values in an enumeration in C++. Any of the before mention solution work as long as you do not define the value of your enumerations if you define you value that you might run into situations where you either create arrays too big or too small

enum example{ test1 = -2, test2 = -1, test3 = 0, test4 = 1, test5 = 2 }

in this about examples the result would create a array of 3 items when you need an array of 5 items

enum example2{ test1 , test2 , test3 , test4 , test5 = 301 }

in this about examples the result would create a array of 301 items when you need an array of 5 items

The best way to solve this problem in the general case would be to iterate through your enumerations but that is not in the standard yet as far as I know

Upvotes: 2

wich
wich

Reputation: 17137

There's not really a good way to do this, usually you see an extra item in the enum, i.e.

enum foobar {foo, bar, baz, quz, FOOBAR_NR_ITEMS};

So then you can do:

int fuz[FOOBAR_NR_ITEMS];

Still not very nice though.

But of course you do realize that just the number of items in an enum is not safe, given e.g.

enum foobar {foo, bar = 5, baz, quz = 20};

the number of items would be 4, but the integer values of the enum values would be way out of the array index range. Using enum values for array indexing is not safe, you should consider other options.

edit: as requested, made the special entry stick out more.

Upvotes: 142

Kevin Doyon
Kevin Doyon

Reputation: 3578

Add a entry, at the end of your enum, called Folders_MAX or something similar and use this value when initializing your arrays.

ContainerClass* m_containers[Folders_MAX];

Upvotes: 3

Josh Kelley
Josh Kelley

Reputation: 58362

For C++, there are various type-safe enum techniques available, and some of those (such as the proposed-but-never-submitted Boost.Enum) include support for getting the size of a enum.

The simplest approach, which works in C as well as C++, is to adopt a convention of declaring a ...MAX value for each of your enum types:

enum Folders { FA, FB, FC, Folders_MAX = FC };
ContainerClass *m_containers[Folders_MAX + 1];
....
m_containers[FA] = ...; // etc.

Edit: Regarding { FA, FB, FC, Folders_MAX = FC} versus {FA, FB, FC, Folders_MAX]: I prefer setting the ...MAX value to the last legal value of the enum for a few reasons:

  1. The constant's name is technically more accurate (since Folders_MAX gives the maximum possible enum value).
  2. Personally, I feel like Folders_MAX = FC stands out from other entries out a bit more (making it a bit harder to accidentally add enum values without updating the max value, a problem Martin York referenced).
  3. GCC includes helpful warnings like "enumeration value not included in switch" for code such as the following. Letting Folders_MAX == FC + 1 breaks those warnings, since you end up with a bunch of ...MAX enumeration values that should never be included in switch.
switch (folder) 
{
  case FA: ...;
  case FB: ...;
  // Oops, forgot FC!
}

Upvotes: 39

Related Questions