Создание функционального адаптера без использования анонимных функций - PullRequest
2 голосов
/ 10 мая 2019

Я пишу реализацию связанного списка и хочу создать функцию foreach. Я хочу иметь две версии этой функции: одну для внутреннего использования, которая выполняет итерации по самим узлам, и одну для конечного пользователя, который выполняет итерацию по данным, хранящимся в узлах. Поскольку код будет практически одинаковым для каждого, я бы хотел определить вторую версию в терминах первой. Прямо сейчас у меня есть два typedef для функций, используемых для итерации в каждой версии, и внутренняя версия foreach:

// The internal-use version that takes a node
typedef void(*Foreach_Node_Function)(size_t index, Node* node);

// The end-user version that takes raw data
typedef void(*Foreach_Function)(size_t index, void* data);

void static foreach_node(Linked_List* ll, Foreach_Node_Function f) {
    Node* current = ll->head;

    for (size_t i = 0; i < length(ll); i++) {
        (*f)(i, current);

        current = current->next;
    }

}

Мне нужно иметь возможность написать адаптер, который превращает Foreach_Node_Function в Foreach_Function. Если бы у меня был доступ к анонимным функциям, это было бы тривиально. Я мог бы иметь функцию, близкую к Foreach_Function, и изменить данные перед передачей. Нечто подобное (притворяясь lambda это вещь):

void foreach(Linked_List* ll, Foreach_Function f) {
    Foreach_Node_Function adapted = lambda(size_t i, Node* node) {
                                        // Only pass on the data to f
                                        (*f)(i, node->data);
                                    };

    foreach_node(ll, adapted);
}

C, похоже, не поддерживает анонимные функции, если только вы не захотите прибегнуть к специфичным для компилятора взломам , и я не могу понять, как этого можно достичь без них.

Можно ли этого достичь в C, и если да, то как?

Ответы [ 2 ]

5 голосов
/ 10 мая 2019

Обычно, когда у вас есть такая функция обратного вызова, вы добавляете дополнительный параметр void * (часто называемый данные обратного вызова или пользовательские данные ), который позволяет вызывающему абоненту передавать дополнительные данные черезк функции обратного вызова.Таким образом, вы получите:

typedef void(*Foreach_Node_Function)(size_t index, Node* node, void *userdata);
typedef void(*Foreach_Function)(size_t index, Node* node, void *userdata);
void static foreach_node(Linked_List* ll, Foreach_Node_Function f, void *f_userdata);

Затем вы можете передать адаптируемый обратный вызов и его параметр void * через обратный вызов адаптера:

struct foreach_node_adapter_userdata {
    Foreach_Function f;
    void *f_userdata;
};

static void foreach_node_adapter(size_t index, Node *node, void *userdata) {
    struct foreach_node_adapter_userdata *adapter_userdata = userdata;
    (*adapter_userdata->f)(index, node->data, adapter_userdata->f_userdata);
}

void foreach(Linked_List* ll, Foreach_Function f, void *f_userdata) {
    struct foreach_node_adapter_userdata adapter_userdata = {f, f_userdata};
    foreach_node(ll, foreach_node_adapter, &adapter_userdata);
}
1 голос
/ 10 мая 2019

Поскольку вы пометили этот C11, вы можете использовать _Generic для этой цели. _Generic хорошо, так как он безопасен от типа, в отличие от старой школы void*, что довольно опасно. Это также решает проблему отсутствия общего эквивалента void*, который можно использовать с типами указателей на функции.


У вас может быть функция «функтор», которая вызывается для каждого элемента в контейнере, например:

typedef void node_operation_t (size_t index, node_t* node);

// functions following this form:
void node_add    (size_t index, node_t* node);
void node_delete (size_t index, node_t* node);
...

Тогда вы хотите назвать это как

linked_list_t list;
traverse(&list, node_delete);

(Именование: "traverse" - это часто используемый термин для общей итерации контейнера в C, тогда как "foreach" - это скорее ключевое слово цикла в различных других языках.)

Теперь вы можете создать для этого макрос _Generic, принимая либо связанные списки, либо другие контейнеры:

#define traverse(list, func)                         \
  _Generic((func),                                   \
           node_operation_t*: traverse_linked_list,  \
           foo_operation_t*:  traverse_foo           \
          )(list, func)

Выбирает соответствующую функцию, проверяя тип указателя на функцию. Это необходимо, только если разные функции относятся к разным типам, в противном случае вы могли бы также сделать traverse функцией вместо этого.

Аналогичным образом можно вместо этого иметь несколько операций с разными входами параметров, работающих с одним и тем же типом контейнера. Много возможностей.

traverse_linked_list здесь было бы что-то вроде:

void traverse_linked_list (linked_list_t* ll, node_operation_t* op)

, который просто перебирает список и вызывает op для каждого узла. Он может быть расширен для получения большего количества параметров, если вы хотите передать параметры от вызывающей стороны до каждой отдельной операции (например, «установить для всех элементов значение 5»).

...