Pull to refresh

Comments 14

Я писал очень похожую статью habrahabr.ru/post/236403 но у моего метода тоже есть проблемы
Вот еще одна реализация github.com/aantron/better-enums там ограничение только на включение внутрь класса. тем не менее очень хорошо сделана в том числе и compile time преобразования.
Я использую метод на макросах. По сравнению с предложенным в статье, этот метод не имеет оверхеда, а также дает дополнительные возможности.
// объявляем список
#define MY_ENUM_DESC \
    EITM( vasya ) \
    EITM( petya ) \
    EITM( vova ) \

// генерируем enum
enum my_enum {
#define EITM(itm) itm,
    MY_ENUM_DESC
#undef EITM
    my_enum_max
};
// функция для получения строки
const char * my_enum_s(my_enum e) {
    static const char * tbl[] = {
    #define EITM(itm) #itm,
        MY_ENUM_DESC
    #undef EITM
    "" };
    return tbl[e];
}

Кроме того, по списку можно делать практически все что угодно: объявлять другие типы, создавать функции/классы, делать сериализацию и т.п.
Недостаток — трудночитаемый код. Но мне кажется, это приемлемая цена за надежность.
Метод интересный, но очень уж громоздкий. Особенно с учётом того, что нумераторов в проекте могут быть десятки. И вот это:

Недостаток — трудночитаемый код. Но мне кажется, это приемлемая цена за надежность


на мой взгляд, большой недостаток. Будет море копи-пасты — ведь подобные группы из макроса, нумератора и функции придётся копи-пастить каждый тип (засунуть под один макрос эти три штуки я особо не представляю как).
Если много enum-ов, требующих рефлексии, то да, громоздко.
Метод на макросах лучше применять не часто. По моему опыту, даже крупные проекты имеют не более 5 таких списков.
Кстати, на макросах — это даже не рефлексия. Это больше похоже на мета программирование. enum — это порождение списка и лишь малая часть возможностей.
Например, в своем проекте я сделал список таблиц, которые читаются из sqlite базы. Для каждой таблицы создаются свои классы обработки (по списку инстанцируются шаблоны), всего 8 мест генерации. Добавляя в список новую таблицу, я экономлю кучу времени на написании оснастки этой таблицы — весь код генерирует за меня компилятор.
Да. Я работал в компании, где тоже в одном месте такой приём использовался. Сильный механизм, который, тем не менее, стоит использовать не больше нескольких раз в проекте — очень уж громоздкий.

На самом деле, на мой взгляд, подобные ходули — так же как 90% извращений на шаблонно-темплейтной чёрной магии в С++ — возникают потому, что нет нормальной возможности редактирования AST программы на этапе компиляции. Нужен полноценный язык (скорее всего — функциональный) с отладчиком — для выполнения кодогенерации для С++. Задача очень непростая (прежде всего потому, что требуется сохранить читабельность runtime-кода), но, на мой взгляд, рано или поздно придётся это сделать — иначе язык утонет в разнотипных шаблонных конструкциях.

О необходимости возможности редактирования AST говорят такие ребята, как Эрик Ниблер (один из экспертов в С++, я как раз тут эту мысль встретил и заболел ею). Эту штуку я обсуждал ещё позже с ребятами вот тут — как продолжение дискуссии…

Вы не встречали нигде подобных обсуждений? Интересно было бы глянуть что ещё люди думают по этому поводу.
Конечно, это далеко не редактирование AST, но возможно в C++17 добавят настоящую compile-time рефлексию. К примеру, для перечислений: N3815
Статья безусловно получилась. Даже независимо от практического результата вышло очень изящно, просто приятно почитать.
Рекомендую сделать header-only решение.
Я так и не понял, зачем нужен val_t и пляски вокруг него, при том что и без этого все отлично работает ( о чем я писал еще 4 года назад)

std::vector<std::string> parse(const char* args);

template<typename T, typename ...Ts>
std::map<T, std::string> make_map(const char* text, Ts... args)
{
    std::vector<T> keys{args...};
    std::vector<std::string> vals = parse(text);
    auto k = keys.cbegin();
    auto v = vals.cbegin();
    std::map<T, std::string> r;
    for (; k != keys.cend(); k++, v++) {
        r.emplace(*k, *v);
    }
    return r;

}

#define ENUM(name, ...)                                                              \
enum name                                                                            \
{                                                                                    \
    __VA_ARGS__                                                                      \
};                                                                                   \
static std::string to_string(const name v) {                                         \
    static std::map<name, std::string> m {make_map<name>(#__VA_ARGS__, __VA_ARGS__)};\
    return m.at(v);                                                                  \
}
В вашей версии нельзя присваивать произвольные значения константам перечисления.
Допустим, если я допишу A=8 в ваш пример с ideone, то получаем ошибку компиляции:
prog.cpp: In static member function 'static std::string X::to_string(X::Y)':
prog.cpp:60:13: error: lvalue required as left operand of assignment
     ENUM(Y,A=8,B,C)
             ^
Ещё есть способ, вместо enuma использовать структуру со статическими членами:
struct Nums
{
	static const std:string One;
	static const std:string Two;
};

const static const std:string Nums:One = "One";
const static const std:string Nums:Two = "Two";


Вызов не сильно отличается от enuma Nums:One, но вместо цифры мы передадим строку.
При этом итерироваться по элементам не выйдет.
Sign up to leave a comment.

Articles