Home > front end >  In C language, is there any way to use callback with arbitrary / variable arguments?
In C language, is there any way to use callback with arbitrary / variable arguments?

Time:10-21

I would like to send callbacks with different signatures for the same function. Somenthing like this:

#include <stdio.h>
#include <stdarg.h>

void a(int pa) {}
void b(int pb1, float pb2) {}

// exec implementation

int main() {
    exec(a, 1);
    exec(b, 1, 2.3);
}

I thought of using something like:

void exec(void (*func)(...), ...) {
    int arg1;
    float arg2;

    va_list valist;
    va_start(valist, size);

    arg1 = va_arg(valist, int);
    if (size == 1) {
        (*func)(arg1);
        va_end(valist);
        return;
    }

    arg2 = va_arg(valist, float);
    if (size == 2) {
        (*func)(arg1, arg2);
        va_end(valist);
        return;
    }
}

But obviously it doesn't work :(

CodePudding user response:

You could change the callbacks to take a single va_list argument:

void a(va_list args) 
{
    int pa = va_arg(args,int);
}

void b(va_list args)
{
    int pb1 = va_arg(args,int);
    double pb2 = va_arg(args,double);
}

And have your other function pass the va_list along.

void exec(void (*func)(va_list), ...)
{
    va_list valist;
    va_start(valist, func);
    func(valist);
    va_end(valist);
}

CodePudding user response:

The usual solution to making callback function interfaces flexible with respect to data provided to the function is to give the callback signature a void * parameter (possibly in addition to other parameters). Arbitrary data can be provided via such a parameter. Something like this:

void exec(void (*func)(void *), void *data) {
    func(data);
}

struct s2 {
    int i;
    float f;
};

void func1(void *data) {
    int i = *(int *)data;
    // ...
}

void func2(void *data) {
    struct s2 s = *(struct s2 *)data;
    // ...
}

int main(void) {
    int i = 42;
    struct s2 s = { .i = 17, .f = 3.14 };

    exec(func1, &i);
    exec(func2, &s);
}

HOWEVER, It is possible to do something more like you describe, where the callback functions genuinely have different signatures, by specifying the callback type without a prototype. In that case, there are still at least these caveats:

  • If the callback functions themselves are defined with prototypes (as they should be) then the parameter types cannot be any that are altered by the default argument promotions. So, pointers, ints, doubles, but not floats or short ints or chars (not an exhaustive list).

  • The callback functions cannot be variadic.

  • If the front-end is variadic, then it needs to be told at runtime, somehow, what the actual number and types of the arguments are.

  • Furthermore, there will need to be explicit calls to the callback functions, with correct arguments, so there can be only a fixed set of predetermined callback signatures supported.

For example, that might look something like this:

enum sig { INT, INT_DOUB };

void exec(void (*func)(/* no prototype */), enum sig cb_sig, ...);

void a(int pa) {}
void b(int pb1, double pb2) {}

int main(void) {
    exec(a, INT, 1);
    exec(b, INT_DOUB, 1, 2.3);
}

void exec(void (*func)(/* no prototype */), enum sig cb_sig, ...) {
    va_list valist;
    va_start(valist, cb_sig);

    switch (cb_sig) {
        case INT: {
            int i = va_arg(valist, int);
            func(i);
            break;
        }
        case INT_DOUB: {
            int i = va_arg(valist, int);
            double d = va_arg(valist, double);
            func(i, d);
            break;
        }
        default:
            assert(("Can't be reached", 0));
    }
}

It is possible that that would elicit a few warnings, such as about a function declaration that does not provide a prototype, and about calling a (declared, but) unprototyped function. Since you know the signatures by the time you execute the calls, however, you could get rid of the latter kind of warning via appropriate casting. For example,

        // ...
        case INT: {
            int i = va_arg(valist, int);
            ((void (*)(int))func)(i);
            break;
        }
        // ...
  • Related