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

Creating a function adapter without the use of anonymous functions

我正在编写链表实现并想创建一个 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 中实现,如果可以,如何实现?

通常当您有这样的回调函数时,您会添加一个额外的 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);
}

因为你标记了这个 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);

(命名:"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。如果您想将参数从调用者一直传递到每​​个单独的操作(如 "set all items to value 5"),它可以扩展为接受更多参数。