Параметризованные универсальные макросы C - PullRequest
0 голосов
/ 07 сентября 2018

Я играю с созданием псевдо-универсального типа в 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) - однако я хочу сохранить свойство, которое для добавления нового поддерживаемого внутреннего типа требует только изменения в одно местоположение.

...