3

I've come to a pattern when writing enums in C++. It is like this:

class Player
{
public:
    class State
    {
    public:
        typedef enum
        {
            Stopped, 
            Playing, 
            Paused
        }PossibleValues;  

        static const int Count() {return Paused+1;};
        static const PossibleValues Default() {return Stopped;};
    };

    //...
}

This solves a some of the usual issues with enums, like pollution of outside namespaces, etc. But there is still a thing I don't like: The Count() is done manually. There are only two ways I know how to do it: this one is calculated from Last+1; or write plain hardcoded.

Question is: Is there some way, like using preprocessor macros, that automatically gets the count, to put it after in the Count() method? Attention: I don't want to have a last fake element called Count inside the enum, polluting it!

Thanks in advance!

UPDATE 1:

There is an interesting discussion on Implementation of N4428 enum reflection in standard C++11 (partial) for a proposal of more advanced enums.

UPDATE 2:

Interesting document N4451- Static reflection (rev. 3) on its sections 3.16, 3.17, A.7, A.8 about MetaEnums and MetaEnumClasses.

UPDATE 3:

I came to another interesting pattern using an enum class, after I've seen https://bytes.com/topic/c/answers/127908-numeric_limits-specialization#post444962. If the enum class's enumerator list is continuously integer, by defining its maximum and its minimum, we can check whether a value belongs to it or not.

If the purpose of using the Count() method on the Player::State was to check if a value was in the enum, that purpose has also been achieved with the numeric_limits approach, and is even superior, as it is not required the enumerator list begins with a ZERO valued item!

enum class Drink
{
    Water,
    Beer,
    Wine,
    Juice,
};


#pragma push_macro("min")
#undef min

#pragma push_macro("max")
#undef max

namespace std
{
    template <> class numeric_limits < Drink >
    {
    public:
        static const/*expr*/ bool is_specialized = true;

        static const/*expr*/ Drink min() /*noexcept*/ { return Drink::Water; }
        static const/*expr*/ Drink max() /*noexcept*/ { return Drink::Juice; }

        static const/*expr*/ Drink lowest() /*noexcept*/ { return Drink::Water; }

        static const/*expr*/ Drink default() /*noexcept*/ { return Drink::Beer; }
    };
}

#pragma pop_macro("min")
#pragma pop_macro("max")

CASES OF USE:

A variable from the application:

Drink m_drink;

which in constructor is initialized with:

m_drink = numeric_limits<Drink>::default();

On the initialization of a form, I can do:

pComboDrink->SetCurSel(static_cast<int>(theApp.m_drink));

On it, for adapting the interface to changes done by the user, I can do a switch with scoped enum class values:

switch (static_cast<Drink>(pComboDrink->GetCurSel()))
{
case Drink::Water:
case Drink::Juice:
    pAlcohoolDegreesControl->Hide();
break;

case Drink::Beer:
case Drink::Wine:
    pAlcohoolDegreesControl->Show();
break;

default:
    break;
}

And on the dialog's confirmation procedure (OnOK), I can check if the value is out of boundaries, before saving it to the respective app var:

int ix= pComboDrink->GetCurSel();

if (ix == -1)
    return FALSE;

#pragma push_macro("min")
#undef min

#pragma push_macro("max")
#undef max

if (ix < static_cast<int> (std::numeric_limits<Drink>::min()) ||  ix > static_cast<int> (std::numeric_limits<Drink>::max()) )
    return FALSE;

#pragma pop_macro("min")
#pragma pop_macro("max")

theApp.m_drink= static_cast<Drink>(ix);

NOTES:

  1. The keywords constexpr (I commented /*expr*/, leaving it as const ) and noexcept are commented only because the compiler I am using (Visual C++ 2013) does not support them yet at the current version.
  2. Maybe you do not need the logic to temporary undefine the min and max macros.
  3. I know that the default() does not fit on a "numeric limits" scope; but it seemed an handy place to put it on; even it coincides with the default word that in some contexts is a keyword!
sergiol
  • 4,122
  • 4
  • 47
  • 81

5 Answers5

3

No there isn't, and if you need this, you probably shouldn't be using an enum in the first place.

In your particular case, what's a use case where you'd ever want to call Count?

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • 1
    Really, in standard C++ it's unavailable. You can use macros wrapper to create and define enums (i've seen one of implementation on codeproject.com) but it looking discouraged. Any other method have pitfalls. – Torsten Oct 19 '12 at 11:27
  • In this particular case, there is not much use of the Count() method, but I can assure you there are LOTS of cases of enumerations where I wrote code like this and I make use of the Count() function. – sergiol Nov 27 '13 at 14:13
  • @sergiol hard coded value at the end, or if the values start from `0` and are consecutive, just add as the last one `PossibleValues_COUNT`. (in the year it's been since this answer, I too have encountered situations like this, but I still think better alternatives exist) – Luchian Grigore Nov 27 '13 at 15:36
  • @LuchianGrigore: What alternatives do you know? – sergiol Nov 27 '13 at 17:24
  • @LuchianGrigore: Enum is after all short for enumeration.. What if you want to do an enumeration of all the elements in your enum and you don't want to explicitly reference the last item (`Paused` in this case) because experience has taught you that the enum may grow in the future and you will have a bug.. – Andreas Magnusson Mar 23 '15 at 09:37
  • Answers that boil down to "if you need this you're doing it wrong" make me sad. If the OP shouldn't be using an enum, what are his or her alternatives? – Ian Ni-Lewis May 11 '16 at 13:34
  • The Swift programming language built iterating all cases of an enum (and retrieving their count) directly into the language (in 4.2) ([link](https://developer.apple.com/documentation/swift/caseiterable)). They clearly recognised it was useful. – wardw Jan 13 '19 at 12:03
  • @wardw You mention Swift as if it's the first time someone's thought of this. Ada (1983) included support for this sort of thing; enums have attributes 'first, 'last etc, as well as being usable to define the indices of an array (see https://learn.adacore.com/courses/intro-to-ada/chapters/arrays.html) – cosimo193 Jul 25 '22 at 15:20
3

AFAIK there's no automatic compiler-supported keyword to get the total amount of elements in enum. OTOH this usually doesn't make sense: you may have multiple values with the same value, as long as the values don't have to have consequent values (i.e. you may assign the values manually, rather than rely on automatic numbering).

One common practice is to declare the enum in the following way:

  typedef enum
    {
        Stopped, 
        Playing, 
        Paused,

        count

    }PossibleValues;  

This way, if count is always defined last - it will give you the count of the enum elements, assuming the numbering starts from 0 and is consequent.

valdo
  • 12,632
  • 2
  • 37
  • 67
3

Reposting an answer from a similar question (What is the best way for non sequencial integer c++ enums) because it was kind of relevant to an otherwise pretty much unanswered question.

A pattern you could use to get what you want is to use an std::initializer_list to store all the values of your enum.

namespace PossibleValues
{
    enum Type
    {
        ZERO= 0,
        PLUS180= 180,
        PLUS90= 90,
        MINUS90= -90
    };

    constexpr auto Values = {ZERO, PLUS180, PLUS90, MINUS90};
    size_t Count() { return Values.size(); }
    Type Default() { return *begin(Values); }
}

This also has the benefit of being able to iterate of the values of the enum even if they don't have linear values.

And I think you could possibly generate both the enum, the initializer list and the functions from a single macro with a variadic macro, though in the best of worlds this kind of thing should be in the standard.

Edit: When I used PossibleValues as an enum or used a struct for PossibleValues, my compiler would complain about incomplete type. Using a namespace for an enum is a bit unusual, but it works fine.

Community
  • 1
  • 1
KABoissonneault
  • 2,359
  • 18
  • 17
  • 1
    But this has a drawback: you have to remember to add new enums in two places instead of one! Yet another "manual counting" approach. – Ruslan Jan 09 '16 at 10:19
  • @Ruslan Fallback to a variadic macro: COUNTED_ENUM(). This would not work for non-sequential values though. – user877329 May 28 '17 at 07:22
1

The solution from https://stackoverflow.com/a/60216003/12894563 can be improved. We can save enums expressions in a static vector and iterate, obtain min/max and etc

Usage:

#include <type_traits>
#include <algorithm>
#include <vector>
#include <iostream>

#define make_enum(Name, Type,  ...)                                              \
    struct Name {                                                                \
        enum : Type {                                                            \
            __VA_ARGS__                                                          \
        };                                                                       \
        static auto count() { return values.size(); }                            \
                                                                                 \
        static inline const std::vector<Type> values = [] {                      \
            static Type __VA_ARGS__; return std::vector<Type>({__VA_ARGS__});    \
        }();                                                                     \
        static Type min()                                                        \
        {                                                                        \
            static const Type result = *std::min_element(values.begin(), values.end()); \
            return result;                                                       \
        }                                                                        \
        static Type max()                                                        \
        {                                                                        \
            static const Type result = *std::max_element(values.begin(), values.end()); \
            return result;                                                       \
        }                                                                        \
}



make_enum(FakeEnum, int, A = 1, B = 0, C = 2, D);

int main(int argc, char *argv[])
{
    std::cout << FakeEnum::A     << std::endl
              << FakeEnum::min() << std::endl
              << FakeEnum::max() << std::endl
              << FakeEnum::count() << std::endl;
    return 0;
}
arved
  • 4,401
  • 4
  • 30
  • 53
ixjxk
  • 121
  • 1
  • 4
0

Must the type of PossibleValues be an enum? If you just need something that behaves like an enum, you can do the following:

#include <iostream>

#include <functional>
#include <set>


template <typename Representation, typename T>
class Iterable_Strong_Enum
{
private:
  struct T_Ptr_Less : public std::binary_function<T const *, T const *, bool>
  {
    bool operator()(T const * x, T const * y) const
    {
      return x->get_representation() < y->get_representation();
    }
  };

public:
  typedef std::set<T const *, T_Ptr_Less> instances_list;
  typedef typename instances_list::const_iterator const_iterator;

  Representation const & get_representation() const { return _value; }

  static Representation const & min() { return (*_instances.begin())->_value; }

  static Representation const & max() { return (*_instances.rbegin())->_value; }

  static T const * corresponding_enum(Representation const & value)
  {
    const_iterator it = std::find_if(_instances.begin(), _instances.end(), [&](T const * e) -> bool
    {
      return e->get_representation() == value;
    });
    if (it != _instances.end())
    {
      return *it;
    }
    else
    {
      return nullptr;
    }
  }

  bool operator==(T const & other) const { return _value == other._value; }
  bool operator!=(T const & other) const { return _value != other._value; }
  bool operator< (T const & other) const { return _value <  other._value; }
  bool operator<=(T const & other) const { return _value <= other._value; }
  bool operator> (T const & other) const { return _value >  other._value; }
  bool operator>=(T const & other) const { return _value >= other._value; }

  static bool is_valid_value(Representation const & value) { return corresponding_enum(value) != nullptr; }

  static typename instances_list::size_type size() { return _instances.size(); }

  static const_iterator begin() { return _instances.begin(); }

  static const_iterator end() { return _instances.end(); }

protected:
  explicit Iterable_Strong_Enum(Representation const & value);

private:
  Representation _value;

  static instances_list _instances;
};

template <typename Representation, typename T>
Iterable_Strong_Enum<Representation, T>::Iterable_Strong_Enum(Representation const & value)
: _value(value)
{
  _instances.insert(static_cast<T const *>(this));
}

class PossibleValues : public Iterable_Strong_Enum<int, PossibleValues>
{
public:
  static const PossibleValues Stopped;
  static const PossibleValues Playing;
  static const PossibleValues Pause;
protected:
private:
  explicit PossibleValues(int value);
};

PossibleValues::PossibleValues(int value) : Iterable_Strong_Enum<int, PossibleValues>(value) { }

// you need to call that explicitly
Iterable_Strong_Enum<int, PossibleValues>::instances_list Iterable_Strong_Enum<int, PossibleValues>::_instances;

const PossibleValues PossibleValues::Stopped(0);
const PossibleValues PossibleValues::Playing(1);
const PossibleValues PossibleValues::Pause(2);

void stackoverflow()
{
  std::cout << "There are " << PossibleValues::size() << " different possible values with representation: " << std::endl;
  for (auto pv = PossibleValues::begin(); pv != PossibleValues::end(); ++pv)
  {
    PossibleValues possible_value = **pv;
    std::cout << possible_value.get_representation() << std::endl;
  }
}

I'm kinda torn about that solution. On the one hand, its pretty general and on the other hand its a big hammer for a small problem.

MadScientist
  • 3,390
  • 15
  • 19