Рассчитывать на enum C++ автоматически

Я пришел к шаблону при написании перечислений в C++. Это так:

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;};
    };

    //...
}

Это решает некоторые обычные проблемы с перечислениями, такие как загрязнение внешних пространств имен и т. Д. Но есть еще одна вещь, которая мне не нравится: Count() выполняется вручную. Есть только два способа, которыми я знаю, как это сделать: этот рассчитывается исходя из Last+1; или напишите просто в жестком коде.

Вопрос: есть ли какой-то способ, например, использование макросов препроцессора, который автоматически получает счетчик, чтобы поместить его после в метод Count()? Внимание: я не хочу, чтобы в перечислении был последний фальшивый элемент с именем Count, загрязняющий его!

Заранее спасибо!

ОБНОВЛЕНИЕ 1:

Существует интересная дискуссия о реализации отражения перечисления N4428 в стандарте C++11 (частичное) для предложения более продвинутых перечислений.

ОБНОВЛЕНИЕ 2:

Интересный документ N4451- Статическое отражение (ред. 3) в его разделах 3.16, 3.17, A.7, A.8 о MetaEnums и MetaEnumClasses.

ОБНОВЛЕНИЕ 3:

Я пришел к другому интересному шаблону, используя класс enum, после того как увидел https://bytes.com/topic/c/answers/127908-numeric_limits-specialization. Если список перечислителей класса enum непрерывно целочисленный, определив его максимум и минимум, мы можем проверить, принадлежит ли ему значение или нет.

Если цель использования Count() метод на Player::State чтобы проверить, было ли значение в перечислении, эта цель также была достигнута с помощью подхода numeric_limits и даже превосходит его, поскольку не требуется, чтобы список перечислителя начинался с элемента с нулевым значением!

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

СЛУЧАИ ИСПОЛЬЗОВАНИЯ:

Переменная из приложения:

Drink m_drink;

который в конструкторе инициализируется с:

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

На инициализацию формы я могу сделать:

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

На нем, для адаптации интерфейса к изменениям, сделанным пользователем, я могу сделать переключение со значениями класса enum:

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;
}

И на диалоге подтверждения процедуры (OnOK), Я могу проверить, находится ли значение вне границ, прежде чем сохранить его в соответствующем приложении 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);

ЗАМЕТКИ:

  1. Ключевые слова constexpr (Я прокомментировал /*expr*/оставляя это как const) а также noexcept комментируются только потому, что используемый мной компилятор (Visual C++ 2013) пока не поддерживает их в текущей версии.
  2. Возможно, вам не нужна логика для временного определения макросов min и max.
  3. Я знаю что default() не вписывается в область "числовые пределы"; но это казалось удобным местом, чтобы надеть это; даже это совпадает с default Слово, которое в некоторых контекстах является ключевым словом!

4 ответа

AFAIK нет автоматического ключевого слова, поддерживаемого компилятором, чтобы получить общее количество элементов в enum, OTOH это обычно не имеет смысла: у вас может быть несколько значений с одним и тем же значением, если значения не обязательно должны иметь последовательные значения (т.е. вы можете назначать значения вручную, а не полагаться на автоматическую нумерацию).

Одной из распространенных практик является объявление enum следующим образом:

  typedef enum
    {
        Stopped, 
        Playing, 
        Paused,

        count

    }PossibleValues;  

Таким образом, если count всегда определяется последним - он подсчитывает количество элементов перечисления, предполагая, что нумерация начинается с 0 и является последовательной.

Нет, нет, и если вам это нужно, вы, вероятно, не должны использовать enum на первом месте.

В вашем конкретном случае, в каком случае вы бы хотели позвонить? Count?

Повторное размещение ответа на аналогичный вопрос ( Каков наилучший способ для непоследовательных целых чисел C++), потому что он был как бы релевантным в остальном практически без ответа на вопрос.

Шаблон, который вы можете использовать, чтобы получить то, что вам нужно, это использовать std::initializer_list для хранения всех значений вашего перечисления.

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); }
}

Это также имеет то преимущество, что можно перебирать значения перечисления, даже если они не имеют линейных значений.

И я думаю, что вы могли бы сгенерировать и перечисление, и список инициализаторов, и функции из одного макроса с переменным макросом, хотя в лучшем из миров такие вещи должны быть в стандарте.

Редактировать: когда я использовал возможное значение в качестве перечисления или использовал структуру для возможного значения, мой компилятор жаловался на неполный тип. Использование пространства имен для перечисления немного необычно, но работает нормально.

Решение stackru.com/a/60216003/12894563 можно улучшить. Мы можем сохранять выражения перечислений в статическом векторе и выполнять итерацию, получать минимальное / максимальное значение и т. Д.

Применение:

#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;
}

Должен ли тип возможных значений быть перечислением? Если вам просто нужно что-то похожее на enum, вы можете сделать следующее:

#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 stackru()
{
  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;
  }
}

Я вроде как разрываюсь из-за этого решения. С одной стороны, он довольно общий, а с другой - большой молоток для небольшой проблемы.

Другие вопросы по тегам