Pull to refresh

Comments 15

Главный вопрос, если вы все равно генерируете xml файл prebuild скриптом, почему так же не генерировать ими и хедеры с нужными enum-ами, функциями и тп?
Никогда никакой боли с автогенерацией подобного кода в том же CMake не имел :)
Потому-что в варианте генерации enum'ов — это выглядит как: «добавили enum, запустили генератор, дописали нужный код, скомпилировали». А тут у нас всегда корректный (хоть и странный) C++ код сразу. А вещи, которые не относятся к коду, такие как XML и .rc нужны уже сильно позже, и их можно генерировать.
Не очень понимаю. Обычно правило на обновление кодогенерации прописано как зависимое от источника этой генерации)
В моем случае действия такие:
— дописал нужный enum где-то в конфиге;
— нажал build / build (target)
все собралось. Т.е. «запуск генератора» никак особо от компиляции не выделяется. Да, вы сборку два раза запускаете, если надо заприменять уже новые значения, но это знаете, минимальное неудобство, имхо.
Вот для это фрагмента
enum class counter_enum : int
{  
  #define NV_PERFCOUNTER(ctr) ctr,
  #include "perfcounters_ctr.h"
  total_counters
};
генератор должен сгенерить только список внутри фигурных скобок? И подключаем его так:
enum class counter_enum : int
{  
  #include "generated1.h"
  total_counters
};
Или должен сгенерить весь .h-файл? и тогда куча сишного кода будет включена в текст генератора.

Имхо, разбросанные по коду
#define NV_PERFCOUNTER(ctr) macro1
#include "perfcounters_ctr.h"
...
#define NV_PERFCOUNTER(ctr) macro2
#include "perfcounters_ctr.h"
...

читаются/редактируются лучше, чем
  #include "generated1.h"
...
  #include "generated2.h"
...

Если же .h или .cpp файл целиком генерится скриптом, и не содержит #include-ов, то читать его удобно, но неудобно вносить изменения в скрипт.
то читать его удобно, но неудобно вносить изменения в скрипт.

Ну видимо всё и сводится к расхождению мнений по поводу этого утверждения :)
Тут пошли субъективные вещи, поэтому привести какие-то доводы более уже не смогу. Пусть так. Мне в какой-нибудь cmake.in.h (либо в список значений где-нибудь в json или cmake) вносить изменения более чем удобно.
При грамотном и аккуратном применении макросы весьма полезны; хотя и грустно то, что они лексические, а не синтаксические, т.е. на них не распространяются области видимости, они могут конфликтовать с кодом в самых неожиданных местах и т.д.
Однако, если соблюдать принцип «неусложнения», то все ок, и на них можно делать весьма полезные вещи — например что-то типа рефлексии, например когда одновременно объявляются элементы перечисления и формируется массив строк с именами этих элементов.

К сожалению, это всё приводит к развитию велосипедостроительства, когда каждый изобретает свои макросы под свои нужды, и работа с множеством проектов становится болью.

Ну так была бы рефлексия встроенной в язык, и не было бы велосипедостроительства:)

Майкрософтовский XML поддерживает гораздо больше возможностей — множественные counter sets, разные типы счётчиков, дополнительная информация, скажем description. Макросы ОК для ограниченного случая, но использование внешнего описания даёт гораздо больше функциональности ценой всего-то запуска генератора при добавлении счётчика (что обычно довольно редко).


Я бы в укор МС поставил только использование XML вместо создания простенького DSL для счётчиков. Но время такое было, XML использовался где надо и не надо :)

В моем случае это было не нужно. Но другие типы счетчиков так же легко добавлялись дополнительным параметром в макросе.

Дополнительные параметры вряд-ли подойдут, придется при добавлении каждого нового параметра модифицировать все существующие определения., Скорее надо добавить макро — XMACRO, YMACRO и так далее, все с разным числом аргументов. Это по крайней мере можно поддерживать.
Но перед включением заголовка надо будет определять их всех. Выглядеть всё равно будет ужасно.
То есть для узкой задачи как у вас — макро подходит и наверное что-то улучшает. Для общей задачи, стоявшей перед МС — они сделали более менее правильный выбор.

Был как-то у меня в жизни проект, где в хидере писался DSL, и файл включался 3 или 4 раза с разными определениями слов в DSL. Никогда больше…
Для шаблонизации регулярных структур можно намного более читаемый подход использовать.

#define DEFINE_ENUM_MEMBER(name) name,
#define DEFINE_ENUM(name, list) enum name { \
    list(DEFINE_ENUM_MEMBER) \
    };

#define DEFINE_ENUM_CASE(name) case name: return #name;
#define DEFINE_ENUM_TO_STRING(name, list) \
    const char* name ## _tostring(name v) { \
        switch (v) { \
            list(DEFINE_ENUM_CASE) \
        } \
        return "?"; \
    }

// user code

#define enum_test1(handler)\
    handler(field1) \
    handler(field2) \

DEFINE_ENUM(test1, enum_test1)
DEFINE_ENUM_TO_STRING(test1, enum_test1)

int main()
{
    LOG_TRACE(test1_tostring(field1));
    return 0;
}


Тут применяется возможность передать имя макроса в качестве параметра в другой макрос.
Можно, только с первого взгляда непонятно наверняка, что это и что оно делает. Как и со второго. А в боевых условиях основная задача ж обычно стоит даже не в том, чтоб понять код, а в том, чтобы сделать что-то полезное, да еще и ничего не отломать.

Если я правильно понимаю, что это писалось для того, чтобы автоматически переводить enum в string, то не лучше ли более простое решение?

enum class MyEnum
{
    first,
    second, 

    _count
};

const char* to_string(MyEnum value)
{
    static const char* const s_values[] = 
    {
        "first", 
        "second"
    };

    static_assert(std::size(s_values) == static_cast<size_t>(MyEnum::_count), 
        "Please add all enum values in the list above");

    return s_values[static_cast<size_t>(value)];
}
Да, оно требует синхронизировать массив со значениями enum, но оно заставит это сделать.
На случай, если кто-то не верит в человечество, и считает, что нужно защитить программиста от ошибочной перестановки строк местами, есть код
с красивыми шаблонами и constexpr
enum class MyEnum
{
    first,
    second, 
    third,

    _count
};

 // specialize this for your enum
template <typename Enum> constexpr auto enum_to_strings();

template <> constexpr auto enum_to_strings<MyEnum>() 
{
    using EnumStringPair = const std::pair<MyEnum, const char*>;

    // constexpr auto s_values = std::to_array<EnumStringPair> in C++2a
    return std::array<EnumStringPair, (size_t)MyEnum::_count>   
    {
        EnumStringPair {MyEnum::third,  "third"},
        EnumStringPair {MyEnum::first,  "first"},
        EnumStringPair {MyEnum::second, "second"}
    };
}

// --- implementation details --- 

template <typename Enum, typename ArrayIt>
constexpr const char* find_constexpr(Enum what, ArrayIt begin, ArrayIt end)
{
    assert(begin != end && "value not found");
    return begin == end ? "?" :
    (
        begin->first == what ? begin->second 
                             : find_constexpr(what, ++begin, end)
    );
}

template <typename Enum>
constexpr const char* to_string(Enum e)
{
    constexpr auto values = enum_to_strings<Enum>();    

    static_assert(std::size(values) == static_cast<size_t>(MyEnum::_count), 
        "Please ensure that all enum values has their string representation");

    return find_constexpr(e, values.begin(), values.end());
}

Который даже с отладочной оптимизаций довольно неплохо пахнет «на выходе» из компилятора: https://godbolt.org/z/HRFveS, а в релизе так вообще обнять и плакать: https://godbolt.org/z/J2e-H6
Sign up to leave a comment.

Articles