Подсчитайте на enum С++ automatic
Я пришел к шаблону при записи перечислений на С++. Это примерно так:
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 внутри enum, загрязняя его!
Спасибо заранее!
ОБНОВЛЕНИЕ 1:
Существует интересное обсуждение Внедрение отражения N4428 в стандартном С++ 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#post444962. Если список перечислений класса перечисляется непрерывно целым числом, определяя его максимум и его минимум, мы можем проверить, принадлежит ли это значение или нет.
Если целью использования метода Count()
на Player::State
было проверить, было ли значение в перечислении, эта цель также была достигнута с помощью метода numeric_limits и даже выше, поскольку это не требуется список перечислителей начинается с нулевого значения ZERO!
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));
В нем, для адаптации интерфейса к изменениям, сделанным пользователем, я могу сделать переключатель с облачными значениями класса перечисления:
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);
ПРИМЕЧАНИЯ:
- Ключевые слова
constexpr
(я прокомментировал /*expr*/
, оставив его как const
) и noexcept
прокомментирован только потому, что компилятор, который я использую (Visual С++ 2013), еще не поддерживает их в текущей версии.
- Возможно, вам не нужна логика для временного определения минимальных и максимальных макросов.
- Я знаю, что
default()
не подходит для области с числовыми ограничениями; но это было удобное место, чтобы надеть его; даже он совпадает с словом default
, что в некоторых контекстах есть ключевое слово!
Ответы
Ответ 1
В AFAIK нет автоматического ключевого слова, поддерживающего компилятор, чтобы получить общее количество элементов в enum
. OTOH это обычно не имеет смысла: вы можете иметь несколько значений с одним и тем же значением, если значения не должны иметь последовательных значений (т.е. Вы можете назначать значения вручную, а не полагаться на автоматическую нумерацию).
Общей практикой является объявление enum
следующим образом:
typedef enum
{
Stopped,
Playing,
Paused,
count
}PossibleValues;
Таким образом, если count
всегда определяется последним - он даст вам счет элементов перечисления, если нумерация начинается с 0 и является следствием.
Ответ 2
Нет, нет, и если вам это нужно, вы, вероятно, не должны использовать enum
в первую очередь.
В вашем конкретном случае, какой прецедент, когда вы хотели бы позвонить Count
?
Ответ 3
Отправка ответа из аналогичного вопроса (Каков наилучший способ для несевернальных целых С++-перечислений), потому что это было похоже на другой, в значительной степени оставшийся без ответа вопрос.
Образец, который вы можете использовать для получения того, что вы хотите, - это использовать 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); }
}
Это также имеет преимущество итерации значений перечисления, даже если они не имеют линейных значений.
И я думаю, что вы могли бы генерировать как перечисление, список инициализаторов, так и функции из одного макроса с переменным макросом, хотя в лучших мирах это должно быть в стандарте.
Изменить: Когда я использовал VariableValues в качестве перечисления или использовал конструкцию для VariableValues, мой компилятор будет жаловаться на неполный тип. Использование пространства имен для перечисления немного необычно, но оно отлично работает.
Ответ 4
Должен ли тип Возможных значений быть перечислением? Если вам просто нужно что-то, что ведет себя как перечисление, вы можете сделать следующее:
#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;
}
}
Я как бы разорвал это решение. С одной стороны, его довольно общий, а с другой стороны, большой молот для небольшой проблемы.