Символические типы C и типы непересекающихся объединений?

Отказ от ответственности: я программист на Haskell, изучаю C. В Haskell у нас есть объявления данных, такие как

data No = NO

где NO не имеет никакой интерпретации как число. Если бы у нас было что-то эквивалентное в C, мы могли бы сделать

union MaybeInt { enum No no; int just;};

Который можно использовать для таких вещей, как массив, который инициализируется как No.

int A[k];
for (int i = 0; i < k; i++)
    A[i] = NO;

Это было бы полезно при выполнении мемоизации, потому что часто используется некоторый рекурсивный алгоритм, который ищет вещи в массиве и на основе найденного значения либо выполняет рекурсивный вызов, либо нет. Например: (для чисел Фибоначчи)

fibMem (int k){
    if (FIB[k] == NO)
      compute fibMem(k-1) + fib(k-2) and store the result in FIB[k]
    return FIB[k]
}

Теперь, конечно, я мог бы инициализировать FIB[i] каким-нибудь абсурдным значением, например -100, и это работает для этой проблемы; однако, учитывая произвольную запомненную процедуру, где я не знаю диапазона значений, такое решение не сработает.

Проблема с использованием типа перечисления: Первое, что я увидел, что заставило меня вскочить со стула и сказать «да», это типы перечисления. Я подумал, а почему бы не сделать что-то вроде enum No {no}; Ну тут проблема с инициализацией используемого для мемоизации массива с nos. Проблема в том, что no определяется как 0 или какая-то числовая константа по моему выбору, если мне нравится. Это неудовлетворительно, потому что, если значение, хранящееся в массиве, предполагается равным нулю (или той константе по моему выбору), то, когда я выполняю проверку, A[i] == no может быть так и должно быть! Таким образом, я в конечном итоге выполню ненужную рекурсию.

Это подводит нас к вопросу 1: как я могу получить символическую константу в C, которая обрабатывается как флаг, несравнимая ни с чем другим типом?

Теперь проблема с профсоюзами. Объединение хранит все свои поля в одном единственном адресе. Так, например, обновление mayInt.just влияет на значение mayInt.no. Например,

union MaybeInt maybeInt;
maybeInt.just=9;
printf("%d",maybeInt.just);
printf("%d",maybeInt.no);

печатает 99. Было бы неплохо, если бы в C существовал какой-то непересекающийся тип объединения, так что если бы я использовал одно из значений объединения, другое стало бы недоступным.

Это подводит нас ко второму и последнему вопросу: как можно получить тип несвязного объединения в C — это тип, который имеет много возможных вариантов, но только один в любой момент времени. Я хотел бы, чтобы что-то можно было сделать что-то вроде:

disjoint T {type1 name1 , .... };

и если установлен T.name2, то ссылка на T.name1 выдает ошибку. Или, что еще лучше, любая ссылка на T должна пройти какое-то различие в падежах.

Если это невозможно сделать красиво, пожалуйста, объясните, почему.


person Jonathan Gallagher    schedule 24.09.2013    source источник
comment
Что плохого в добавлении абстракции? Используйте указатель или второй параллельный массив, который указывает статус инициализации данной записи.   -  person Carl Norum    schedule 25.09.2013
comment
Итак, позвольте мне посмотреть, понимаю ли я: у меня есть два массива одинакового размера. Второй массив в позиции i имеет значение, подобное True или False, указывающее, был ли инициализирован первый массив в позиции i или нет?   -  person Jonathan Gallagher    schedule 25.09.2013
comment
Существует boost.org/libs/variant для суммирующих типов (также известных как объединения с тегами, варианты типов, непересекающиеся союзы - en.wikipedia.org/wiki/Tagged_union#1970s_.26_1980s ) с гарантиями времени компиляции (en.wikipedia.org/wiki/Tagged_union#2000s ), но это для C++.   -  person JJJ    schedule 25.09.2013


Ответы (1)


Размеченные союзы — очень стандартная идиома Си. Вам просто нужно отделить тег от данных:

struct Data
{
    enum DataType
    {
        NotSet,
        Integer,
        Infinity,
        Message
    } tag;
    union ValueType
    {
        int n;
        char const * msg;
    } data;
};

Теперь вам просто нужно поддерживать дисциплину тегов, то есть читать только значение, подходящее для данного тега, и обновлять тег после записи члену союза. Например:

void foo(struct Data const * x)
{
    switch (x->tag)
    {
    case NotSet:      // ...
    case Integer:     // use x->data.n
    case Infinity:    // ...
    case Message:     // use x->data.msg
    };

    x->data.msg = "Thank you!";
    x->tag = Message;
}
person Kerrek SB    schedule 24.09.2013
comment
Да, после комментария Норума я подумал о чем-то вроде struct Pair {int val, int setYes}. Однако это решение намного сексуальнее. Кажется, что это требует небольшой осторожности со стороны программиста. Например, можно сделать x -> data.msg = Hello; x -> тег=NotSet. Есть ли способ форсировать правильную координацию (скажем, во время компиляции). - person Jonathan Gallagher; 25.09.2013
comment
@JonathanGallagher: Да, конечно, ты можешь сломать все, если захочешь. Вы просто не должны :-) Вы можете сделать функции-обертки, такие как set_message(void * obj, char const * msg), и скрыть реализацию. - person Kerrek SB; 25.09.2013
comment
@JonathanGallagher ANSI C не поддерживает уровень абстракции, который вы ищете. Вы можете создать файл C и заголовок, который будет реализовывать ADT как структуру, включающую поле тега и поле данных, как показал Керрек, и вы даже можете помешать людям возиться со структурой, ограничив то, что предоставляется заголовочным файлом до непрозрачный указатель и некоторые функции (а-ля умные конструкторы). Тем не менее, люди по-прежнему будут способны выполнять операции с памятью на основе указателей (помимо прочего, из-за слабой типизации C). - person Thomas M. DuBuisson; 25.09.2013