Comments 15
Никогда никакой боли с автогенерацией подобного кода в том же CMake не имел :)
В моем случае действия такие:
— дописал нужный 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 и так далее, все с разным числом аргументов. Это по крайней мере можно поддерживать.
Но перед включением заголовка надо будет определять их всех. Выглядеть всё равно будет ужасно.
То есть для узкой задачи как у вас — макро подходит и наверное что-то улучшает. Для общей задачи, стоявшей перед МС — они сделали более менее правильный выбор.
#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, но оно заставит это сделать.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());
}
Применение X-Macro в модерновом C++ коде