views:

256

answers:

5

Hi all! I'm doing this class assignment, using classic C, and am stuck with this problem about callback functions that take variable arguments count and type.

Basically, I'm working on a Hashed Tree (a tree where each of the nodes is a hash tree), and I have a certain traversal strategy that will be used multiple times for different purposes, so I implemented it as ht_walk(HashTree tree, (*callback)(Element e)), so that the function called as callback will process the Element whatever way necessary.

Problem being, in most situations in my problem the callback function will have to take different arguments. I know how to design a function with a variable argument list using 'variadic' functions (using stdarg, printf-way), but I don't know how to 'repass' these arguments to the callback function.

Let me provide a concrete example: suppose I have a callback function called addToList(Element e, List list), and that my ht_walk declaration is now ht_walk(HashTree tree, (*callback)(Element e), ...). Consider I want to use ht_walk like in the following snippet:

HashTree my_tree = ht_create();
/* run some algorithm that populates the tree somehow */
List my_list = list_create();
ht_walk(my_tree, addToList, my_list);

Is there a way to do this? Thanks in advance!

A: 

I believe you use the va_list macro. See the man page for stdarg.

It has been a while since I tried this though...

dmckee
A: 

I would pass in a void *, as I don't think you can use variable number of arguments in a callback function. I could be wrong though.

typedef void (*tree_walk_callback)(Element e, void *data);

add_element(Element e, void *data)
{
    List *list = (List *)data;
    add_element_to_list(list, e);
}

...in your function...
List my_list;
ht_walk(my_tree, &add_element, (void *)&my_list);

If the user wants to pass multiple arguments, then they would pass a struct through containing whatever they need.

FryGuy
+1  A: 

There is a series of functions and macros for dealing with variadic arguments (e.g., va_start) GNU has a good guide here

That being said, you seem to be describing sort of a visitor pattern. I am not sure I like the use of variadic argument lists here. If you can't typically establish what parameters your callback would be getting consistently for all nodes, you are in trouble to begin with.

Uri
+2  A: 

You can solve this using one of two methods.

The most common, understandable, and clean method is to use a 'user' structure:

void ht_walk(HashTree tree, void (*callback)(Element e, void *user), void *user);

void addToList(Element e, void *arg)
{
    STATIC_ASSERT(sizeof(void *) >= sizeof(List));

    List list = arg;

    /* ... */
}

HashTree my_tree = ht_create();
/* run some algorithm that populates the tree somehow */
List my_list = list_create();
ht_walk(my_tree, addToList, my_list);


Another method is to accept va_list:

#include <stdarg.h>

void ht_walk(HashTree tree, void (*callback)(Element e, va_list args), ...)
{
    for(..)
    {
        va_list args;
        va_start(args, callback);
        callback(element, args);
        va_end(args);
    }
}

void addToList(Element e, va_list args)
{
    List list = va_arg(args, List);

    /* ... */
}

HashTree my_tree = ht_create();
/* run some algorithm that populates the tree somehow */
List my_list = list_create();
ht_walk(my_tree, addToList, my_list);
strager
I think the first ht_walk should be: void ht_walk(HashTree tree, void (*callback)(Element e, void *user), void *user); because the callback needs a return type and because you have to pass the user data to ht_walk so it can convey it onwards. The call then needs adjusting too.
Jonathan Leffler
The second ht_walk also needs the return type on the callback. I guess that your conception of List includes a pointer: typedef struct ListItem *List; or something similar. If not, then I think the call to ht_walk may need to take the address of my_list.
Jonathan Leffler
@Leffler, Thanks for your comments. Forgetting void *user was a typo. Missing return types for the callbacks were copy-paste issues (OP didn't have them). Yes, I do believe List is a pointer, seeing how HashTree is passed by value, too.
strager
+1  A: 

I don't think what you want to do is pass an arbitrary number of arguments. I think what you have here is a case where you have multiple types of callback. In your example, you're passing a List, but you also mention sometimes you'll pass an Element. So it seems to me what you really want to do is declare your callback to take a void* and a flag to indicate what it is. Something like:

void callback(void* arg, int type) {
    switch (type) {
    case ARG_TYPE_LIST:
        List* list = (List*)arg;
        ...
        break;
    case ARG_TYPE_ELEMENT:
        Element* ele = (Element*)arg;
        ...
        break;
    ...
}

If you're passing things as a list, the list should know the count, so you don't have to worry about passing that.

Alternatively, you could define multiple types of callback and always callback the right one based on what sort of processing you need to do.

If you're not restrained to straight C (i.e. you can use C++) you may investigate using boost::bind, as it is very useful for this sort of thing.

jeffamaphone