Если вы не возражаете против синтаксиса в стиле барокко, вы можете достичь желаемого с помощью X Macro . Создайте два отдельных списка скалярных и векторных типов:
#define SCALAR_TYPES(_) \
_(u32, uint32_t) \
_(double, double) \
_(cstr, char *)
#define VECTOR_TYPES(_) \
_(u32, uint32_t) \
_(double, double) \
_(cstr, char *)
Эти два макроса являются макросами gerenator. Они принимают другой макрос _
в качестве параметра. Этот макрос должен принимать два аргумента: NAME
для создания подходящих имен функций и TYPE
, который описывает скалярный тип или тип элементов массива.
Чтобы создать интерфейс в примере, сначала создайте необходимые макросы:
#define SCALAR_GET(N, T) T get_##N(const char *key);
#define VECTOR_GET(N, T) T *get_##N##_array(const char *key);
#define SCALAR_PUT(N, T) void put_##N(const char * key, T val);
#define VECTOR_PUT(N, T) void put_##N##_array(const char * key, T *val, uint32_t size);
Затем передайте их двум генераторам макросов:
extern "C"{
SCALAR_TYPES(SCALAR_GET)
SCALAR_TYPES(SCALAR_PUT)
VECTOR_TYPES(VECTOR_GET)
VECTOR_TYPES(VECTOR_PUT)
}
Это приведет к:
extern "C" {
uint32_t get_u32(const char *key);
double get_double(const char *key);
char *get_cstr(const char *key);
void put_u32(const char *key, uint32_t val);
void put_double(const char *key, double val);
void put_cstr(const char *key, char *val);
uint32_t *get_u32_array(const char *key);
double *get_double_array(const char *key);
char **get_cstr_array(const char *key);
void put_u32_array(const char *key, uint32_t * val, uint32_t size);
void put_double_array(const char *key, double *val, uint32_t size);
void put_cstr_array(const char *key, char **val, uint32_t size);
}
Чтобы получить список для std::variant
, используйте:
#define SCALAR_COMMA(N, T) T,
#define VECTOR_COMMA(N, T) T *,
#define VARIANT_TYPENAMES \
SCALAR_TYPES(SCALAR_COMMA) VECTOR_TYPES(VECTOR_COMMA)
Однако есть загвоздка: VARIANT_TYPPENAMES
приведет к запятой. В инициализаторах массива допускаются конечные запятые. В enum
s вы можете определить «максимальное» значение после последнего перечислимого значения.
Но для этого есть и макрос-решение, показанное в конце поста.
Вы также можете включить «класс» данных - скаляр или вектор & nda sh в макрос gererator.
#define TYPES(_) \
_(SCALAR, u32, uint32_t) \
_(SCALAR, double, double) \
_(SCALAR, cstr, char *) \
_(VECTOR, u32_array, uint32_t) \
_(VECTOR, double_array, double) \
_(VECTOR, cstr_array, char *)
Они определяют набор макросов, которые имеют либо SCALAR_
, либо ´ VECTOR_` в качестве префикса, так что вы можете создавать их имена с вставкой токена:
#define SCALAR_TYPE(T) T
#define VECTOR_TYPE(T) T *
#define SCALAR_ARG(T) T val
#define VECTOR_ARG(T) T* val, uint32_t size
Теперь ваши маро выглядят так:
#define GET(C, N, T) C##_TYPE(T) get_##N(const char *key);
#define PUT(C, N, T) void put_##N(const char * key, C##_ARG(T));
extern "C"{
TYPES(GET)
TYPES(PUT)
}
#define COMMA(C, N, T) C##_TYPE(T),
#define VARIANT_TYPENAMES TYPES(COMMA)
Они дают тот же результат, что и выше.
Наконец, по поводу этой запятой в VARIANT_TYPENAMES
: Вы можете избавиться от запятой, превратив запятую в каждом макросе в ведущую запятую, а затем отбросив запятую в голове.
#define COMMA(C, N, T) , C##_TYPE(T)
#define TAIL_(A, ...) __VA_ARGS__
#define TAIL(...) TAIL_(__VA_ARGS__)
#define VARIANT_TYPENAMES TAIL(TYPES(COMMA))
Это работает, потому что аргументы макроса могут быть пустыми, но для преобразования TAIL(TYPES(COMMA))
в TAIL_(, T1, T2, T3, ...)
.
требуется 10-шаговое расширение. Это решение требует некоторого времени, чтобы начать работать, особенно потому, что расширенные макросы имеют свои пробелы c и не очень читабельны, но когда у вас есть система, вы можете легко добавлять новые типы.
Применяются обычные предостережения при использовании макросов. Я также хотел бы указать вам на другое возможное решение: написать сценарий или программу для создания интерфейсов для вас из определений, которые лучше, чем макросы X, и включить их в процесс сборки.