Я играю с созданием псевдо-универсального типа в C. По сути, я пытаюсь клонировать Rust's Option<T>
с предопределенным, ограниченным списком типов, допустимым как T
.
Очевидно, что C на самом деле не подходит для этого - я делаю это в первую очередь, чтобы увидеть, как далеко я могу пойти (в отличие от того, что я ожидал бы использовать в реальном производственном коде). Для этого любые уродливые хаки являются честной игрой.
То, что у меня есть, создает отдельный набор функций, специфичных для внутреннего типа, для всех предоставленных типов. Это выглядит примерно так:
Заголовок:
#pragma once
#define ALL_OPTIONS \
OPTION_INSTANCE(option_bool, bool) \
OPTION_INSTANCE(option_double, double) \
OPTION_INSTANCE(option_int, int)
#define OPTION_INSTANCE(name, inner) \
typedef struct { \
bool is_some; \
inner val; \
} name##_t;
ALL_OPTIONS
#undef OPTION_INSTANCE
#define OPTION_INSTANCE(name, inner) \
name##_t name##_some(inner val); \
name##_t name##_none(void); \
bool name##_is_some(name##_t self); \
bool name##_is_none(name##_t self); \
ALL_OPTIONS
#undef OPTION_INSTANCE
Реализация:
#include "option.h"
#define OPTION_INSTANCE(name, inner) \
name##_t name##_some(inner val) { \
return (name##_t) { \
.is_some = true, \
.val = val, \
}; \
} \
\
name##_t name##_none(void) { \
return (name##_t) { \
.is_some = false, \
}; \
} \
\
bool name##_is_some(name##_t self) { \
return self.is_some; \
} \
\
bool name##_is_none(name##_t self) { \
return !self.is_some; \
}
ALL_OPTIONS
#undef OPTION_INSTANCE
Обратите внимание, что в моем реальном коде у меня есть намного больше функций, определенных для сгенерированных типов.
Это работает достаточно хорошо, хотя в первую очередь все, что я сделал, это уменьшил шаблон реализации. Следующим шагом будет внедрение option_is_some
(без указания типа), которое может принимать любой option_<inner>_t
Я могу сделать это достаточно хорошо с помощью ручного макроса, используя дженерики C11:
#define option_is_some(self) \
_Generic((self), \
option_bool_t: option_bool_is_some, \
option_double_t: option_double_is_some, \
option_int_t: option_int_is_some, \
)(self)
, но это обязательно дублирует список типов, определенных в ALL_OPTIONS
. То, что я действительно хотел бы сделать, было бы что-то вроде
#define OPTION_INSTANCE(name, inner) \
name##_t: name##_is_some,
#define option_is_some(self) \
_Generic((self), \
ALL_OPTIONS \
default: false \
)(self)
#undef OPTION_INSTANCE
, но это не удается, поскольку ALL_OPTIONS
раскрывается при использовании option_is_some
(где OPTION_INSTANCE
будет неопределенным).
Итак, я ищу альтернативы. Я бы с радостью перешел к совершенно другому методу определения общего списка типов (вместо взлома ALL_OPTIONS
) - однако я хочу сохранить свойство, которое для добавления нового поддерживаемого внутреннего типа требует только изменения в одно местоположение.