在不使用匿名函数的情况下创建函数适配器

时间:2019-05-09 23:33:16

标签: c functional-programming c11

我正在编写一个链表实现,并想创建一个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似乎并不支持匿名函数,除非您愿意诉诸compiler specific hacks,而且我不知道如果没有匿名函数,该怎么实现。

这可以用C实现吗?如果可以,怎么办?

2 个答案:

答案 0 :(得分:5)

通常,当您具有这样的回调函数时,可以添加一个额外的void *参数(通常称为 callback data user data ),该参数可以使调用者将额外的数据传递给回调函数。因此,您将拥有:

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 :(得分:1)

自从您标记了此C11以来,您可以为此使用_Generic_Generic很好,因为它是类型安全的,而不像老学校void*那样非常危险。它还解决了不能与函数指针类型一起使用的void*泛型的问题。


您可以使用“ functor”函数来对容器中的每个项目进行调用,例如:

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);

(命名:“遍历”是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”),则可以扩展它以获取更多参数。