Введение
Тип enum на С++ довольно простой; он в основном просто создает кучу значений времени компиляции для меток (возможно, с правильной областью определения с помощью enum class).
Это очень привлекательно для группировки связанных констант времени компиляции вместе:
enum class Animal{
DOG, 
CAT,
COW,
...
};
// ...
Animal myAnimal = Animal::DOG;
Однако он имеет множество предполагаемых недостатков, в том числе:
- Нет стандартного способа получения количества возможных элементов
- Без итераций над элементами
- Легкая ассоциация перечисления со строкой
В этом посте я пытаюсь создать тип, который учитывает эти предполагаемые недостатки.
Идеальное решение принимает понятие знания времени компиляции констант и их ассоциаций со строками и объединяет их вместе в объект, подобный переименованию, который можно искать как с перечислением, так и с именем перечисления enum. Наконец, полученный тип использовал бы синтаксис, максимально приближенный к синтаксису enum.
В этом посте я расскажу о том, что другие пытались сделать для отдельных частей, а затем пройдут два подхода, один из которых выполняет вышеупомянутое, но имеет поведение undefined из-за порядка инициализации статических элементов и другое решение который имеет менее симпатичный синтаксис, но не поведение undefined из-за порядка инициализации.
Предварительная работа
Есть много вопросов о SO о получении количества элементов в перечислении (1 2 3) и множество других вопросов в Интернете, запрашивающих одно и то же (4 5 6) и т.д. И общий консенсус в том, что нет уверенного способа сделать это.
Тройка элемента N'th
Следующий шаблон работает только при условии, что значения перечисления положительны и увеличиваются:
enum Foo{A=0, B, C, D, FOOCOUNT}; // FOOCOUNT is 4
Но легко сломается, если вы пытаетесь закодировать какую-то бизнес-логику, которая требует произвольных значений:
enum Foo{A=-1, B=120, C=42, D=6, FOOCOUNT}; // ????
Boost Enum
И поэтому разработчики в Boost попытались решить проблему с помощью Boost.Enum, который использует некоторые довольно сложные макросы, чтобы развернуть код, который по крайней мере даст вам размер.
Итерируемые перечисления
Произошло несколько попыток повторных перечислений; enum-подобные объекты, которые можно перебрать, теоретически допуская вычисления неявного размера или даже явно в случае [7] (7 8 9,...)
Преобразование Enum в String
Попытки реализовать это обычно приводят к использованию свободно плавающих функций и использованию макросов для их надлежащего вызова. (8 9 10)
Это также относится к поисковым перечислениям по строке (13)
Дополнительные ограничения
-  Нет макросовДа, это означает отсутствие Boost.Enum или аналогичный подход 
-  Требуется int- > Enum и Enum-int Conversionдовольно уникальная проблема, когда вы начинаете отходить от реальных перечислений; 
-  Нужно иметь возможность найти enum с помощью int (или строки)Также возникает проблема, когда они удаляются от фактических перечислений. Список перечислений считается коллекцией, и пользователь хочет допросить его для определенных значений, известных в момент компиляции. (Смотрите итерационные перечисления и преобразование Enum в String) 
В этот момент становится довольно ясно, что мы больше не можем использовать перечисление. Тем не менее, мне все равно нужен интерфейс, подобный enum для пользователя.
Подход
Скажем, я считаю, что я супер умный и понимаю, что если у меня есть класс A:
struct A
{
   static int myInt;
};
int A::myInt;
Затем я могу получить доступ к myInt, сказав A::myInt.
Точно так же я получаю доступ к enum:
enum A{myInt};
// ...
// A::myInt
Я говорю себе: хорошо знаю все мои значения enum загодя, поэтому перечисление в основном выглядит следующим образом:
struct MyEnum
{
    static const int A;
    static const int B;
    // ...
};
const int MyEnum::A = 0;
const int MyEnum::B = 1;
// ...
Затем я хочу стать более привлекательным; позвольте обратиться к ограничению, в котором нам нужны преобразования std::string и int:
struct EnumValue
{
    EnumValue(std::string _name): name(std::move(_name)), id(gid){++gid;}
    std::string name;
    int id;
    operator std::string() const
    {
       return name;
    }
    operator int() const
    {
       return id;
    }
    private:
        static int gid;
};
int EnumValue::gid = 0;
И затем я могу объявить некоторый содержащий класс с static EnumValue s:
MyEnum v1
class MyEnum
{
    public:
    static const EnumValue Alpha;
    static const EnumValue Beta;
    static const EnumValue Gamma;
};
const EnumValue MyEnum::Alpha = EnumValue("Alpha")
const EnumValue MyEnum::Beta  = EnumValue("Beta")
const EnumValue MyEnum::Gamma  = EnumValue("Gamma")
Отлично! Это решает некоторые из наших ограничений, но как насчет поиска в коллекции? Hm, хорошо, если теперь добавить контейнер static, например unordered_map, тогда все становится еще круче! В некоторых #define вы можете сбросить также опечатки строк:
MyEnum v2
#define ALPHA "Alpha"
#define BETA "Beta"
#define GAMMA "Gamma"
// ...
class MyEnum
{
    public:
    static const EnumValue& Alpha;
    static const EnumValue& Beta;
    static const EnumValue& Gamma;
    static const EnumValue& StringToEnumeration(std::string _in)
    {
        return enumerations.find(_in)->second;
    }
    static const EnumValue& IDToEnumeration(int _id)
    {
        auto iter = std::find_if(enumerations.cbegin(), enumerations.cend(), 
        [_id](const map_value_type& vt)
        { 
            return vt.second.id == _id;
        });
        return iter->second;
    }
    static const size_t size()
    {
        return enumerations.size();
    }
    private:
    typedef std::unordered_map<std::string, EnumValue>  map_type ;
    typedef map_type::value_type map_value_type ;
    static const map_type enumerations;
};
const std::unordered_map<std::string, EnumValue> MyEnum::enumerations =
{ 
    {ALPHA, EnumValue(ALPHA)}, 
    {BETA, EnumValue(BETA)},
    {GAMMA, EnumValue(GAMMA)}
};
const EnumValue& MyEnum::Alpha = enumerations.find(ALPHA)->second;
const EnumValue& MyEnum::Beta  = enumerations.find(BETA)->second;
const EnumValue& MyEnum::Gamma  = enumerations.find(GAMMA)->second;
Полная рабочая демонстрация ЗДЕСЬ!
Теперь я получаю дополнительное преимущество поиска контейнера перечислений с помощью name или id:
std::cout << MyEnum::StringToEnumeration(ALPHA).id << std::endl; //should give 0
std::cout << MyEnum::IDToEnumeration(0).name << std::endl; //should give "Alpha"
НО
Все это очень плохо. Мы инициализируем много статических данных. Я имею в виду, что до недавнего времени мы могли заполнить map во время компиляции! (11)
Затем возникает проблема статично-инициализационного порядка фиаско:
Тонкий способ свернуть вашу программу.
Фиксация порядка статической инициализации очень тонкая и обычно непонятый аспект С++. К сожалению, его очень трудно обнаружить - ошибки часто возникают до начала main().
Короче говоря, предположим, что у вас есть два статических объекта x и y, которые существуют в отдельные исходные файлы, скажем, x.cpp и y.cpp. Предположим далее, что инициализация для объекта y (обычно это конструктор y-объектов) вызывает некоторый метод для объекта x.
Вот оно. Это просто.
Трагедия заключается в том, что у вас есть 50% -50% шанс умереть. Если блок компиляции для x.cpp сначала инициализируется, все Что ж. Но если сначала выполнить инициализацию единицы компиляции для y.cpp, то инициализация ys будет запущена до инициализации xs и ты тост. Например, конструктор ys может вызывать метод на x объект, но объект x еще не создан.
Я слышал, как они нанимают в McDonalds. Наслаждайтесь новой работой гамбургеры.
Если вы думаете, что его "захватывающий" играет в русскую рулетку с живыми раундами в половине палат вы можете перестать читать здесь. С другой стороны, если вам нравится улучшать свои шансы на выживание, предотвращая стихийные бедствия систематически, вы, вероятно, захотите прочитать следующий FAQ.
Примечание. Фиаско порядка статического инициализации также может, в некоторых случаях, применяются к встроенным/внутренним типам.
Который может быть опосредован с помощью функции getter, которая инициализирует ваши статические данные и возвращает их (12):
Fred& GetFred()
{
  static Fred* ans = new Fred();
  return *ans;
}
Но если я это сделаю, теперь мне нужно вызвать функцию для инициализации моих статических данных, и я потеряю симпатичный синтаксис, который вы видите выше!
  # Вопросы #
Итак, теперь я, наконец, обошел свои вопросы:
- Будьте честны, насколько плох этот подход? С точки зрения обеспечения порядка инициализации безопасности и ремонтопригодности?
- Какие альтернативы у меня есть, которые все еще хороши для конечного пользователя? С >
ИЗМЕНИТЬ
Комментарии к этому сообщению, похоже, указывают на сильное предпочтение статическим функциям доступа, чтобы обойти проблему инициализации статического порядка:
 public:
    typedef std::unordered_map<std::string, EnumValue> map_type ;
    typedef map_type::value_type map_value_type ;
    static const map_type& Enumerations()
    {
        static map_type enumerations {
            {ALPHA, EnumValue(ALPHA)}, 
            {BETA, EnumValue(BETA)},
            {GAMMA, EnumValue(GAMMA)}
            };
        return enumerations;
    }
    static const EnumValue& Alpha()
    {
        return Enumerations().find(ALPHA)->second;
    }
    static const EnumValue& Beta()
    {
         return Enumerations().find(BETA)->second;
    }
    static const EnumValue& Gamma()
    {
        return Enumerations().find(GAMMA)->second;
    }
Полная рабочая демонстрация v2 ЗДЕСЬ
Вопросы
Мои следующие вопросы:
- Есть ли другой путь вокруг проблемы инициализации статического порядка?
-  Есть ли способ использовать функцию accessor только для инициализации unordered_map, но все же (безопасно) иметь возможность доступа к значениям "enum" с синтаксисом типа enum? например:.MyEnum::Enumerations()::Alpha
или
MyEnum::Alpha
Вместо того, что у меня есть:
MyEnum::Alpha()
Что касается награды:
Я считаю, что ответ на этот вопрос также решит проблемы, связанные с перечислениями, которые я разработал в сообщении (Enum в кавычках, потому что результирующий тип не будет перечислением, но мы хотим enum-like поведение):
- получение размера "перечисления"
- строка для преобразования "enum"
- поисковое "перечисление".
В частности, если мы могли бы сделать то, что я уже сделал, но каким-то образом выполняем синтаксис, похожий на перечисление, при соблюдении статического порядка инициализации, я думаю, что это было бы приемлемо
