2

I'm writing a Linked List implementation and want to create a foreach function. I want to have two versions of this function: one for internal use that iterates over the nodes themselves, and one for the end user that iterates over the data held in the nodes. Since the code will be nearly the same for each, I'd like to define the second version in terms of the first. Right now, I have two typedefs for the functions used to iterate in each version, and the internal version of 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;
    }

}

I need to be able to write an adapter that turns a Foreach_Node_Function into a Foreach_Function. If I had access to anonymous functions, this would be trivial. I could have a function close over a Foreach_Function and modify the given data before passing it along. Something like (pretending lambda is a thing):

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 doesn't seem to support anonymous functions though unless you're willing to resort to compiler specific hacks, and I can't see how this could possibly be achieved without them.

Is this possible to achieve in C, and if so, how?

Carcigenicate
  • 43,494
  • 9
  • 68
  • 117
  • Why do you need it to be anonymous? Can't it just be a regular C function that you define and pass as a callback? – SHG May 09 '19 at 23:44
  • I'm not sure how that would be done. A standalone function would need to return a function to be used by `foreach_node`, and that just kicks the can down the road a bit. – Carcigenicate May 09 '19 at 23:58

2 Answers2

5

Typically when you have a callback function like this, you add an extra void * parameter (often called callback data or user data) which lets the caller pass extra data through to the callback function. So you would have:

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

Then, you can pass the callback being adapted, and its void * parameter, through to the adapter callback:

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);
}
user253751
  • 57,427
  • 7
  • 48
  • 90
  • Thank you. I might have to revist this with fresh eyes tomorrow though. I think I'm too tired right now to process this. I've been staring at this since you posted it, and I'm not at all understanding how this works or how it would be used. What is the initial `f_userdata` passed to `foreach`? Would that just be `NULL`? Could that be omitted as an argument to `foreach` if I didn't anticipate more wrappers? I'll play around with this in the IDE tomorrow. My brains fried right now. Again, thank you for the answer. – Carcigenicate May 10 '19 at 00:28
  • @Carcigenicate It can be omitted if the caller doesn't need it, but when you write a big enough program that actually calls this, at some point you will find yourself needing it. Suppose you wanted to use foreach to write all the nodes to a file; how would the callback get the file handle (FILE*)? – user253751 May 10 '19 at 00:33
  • Thanks, I'll think I'll need to sleep on this. I'm used to calls to higher order functions as being able to be written with an inline anonymous function that could just close over a `FILE*`, but ya, now that I think about it, that wouldn't be possible since the passed function would need to be written externally and would need to be manually passed the `FILE*` pointer, thus the userdata. This actually makes more sense now. – Carcigenicate May 10 '19 at 00:39
1

Since you tagged this C11, you can use _Generic for this purpose. _Generic is good since it is type safe, unlike old school void* which is pretty dangerous. It also solves the problem of not having a generic equivalent to void* that can be used with function pointer types.


You can have a "functor" function that gets called for every item in the container, like for example:

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

Then you want to call this like

linked_list_t list;
traverse(&list, node_delete);

(Naming: "traverse" being a commonly used term for generic iteration of a container in C, whereas "foreach" is rather a loop keyword in various other languages.)

You can now make a _Generic macro for this, accepting either linked lists or other containers:

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

This picks the appropriate function by checking the function pointer type. This is only necessary if the different functions are of different types, otherwise you could as well have made traverse a function instead.

Similarly, it is possible to instead have several operations with different parameter inputs working with the same container type. Lots of possibilities.

traverse_linked_list here would be something like:

void traverse_linked_list (linked_list_t* ll, node_operation_t* op)

Which just loops over the list and calls op for every node. It can be expanded to take more parameters, if you want to pass parameters from the caller all the way to each individual operation (like "set all items to value 5").

Lundin
  • 195,001
  • 40
  • 254
  • 396