Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C - passing function with arguments into wrapper executor

Tags:

c

Is there a way to build a wrapper function in C as follows:

void wrapper(void *func) {
  // Set up
  func([same arguments as func was passed in with]);
  // Clean up
}

void foo(int x, int y);
void bar(char *z);

wrapper(foo(1, 2));
wrapper(bar("Hello"));

It seems as though you have to either pass in the arugments within wrapper or only support one type of method header for func. I've been writing a lot of Javascript... and of course this is possible in JS.

like image 329
sir_thursday Avatar asked Mar 14 '23 02:03

sir_thursday


2 Answers

That's the best I can think of with variadic function wwrappers:

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

void wrapper(void (*func)(va_list), ...) {
    va_list args;
    va_start(args, func);

    func(args);

    va_end(args);
}

void foo(int x, int y)
{
    printf("foo(%d,%d)\n", x, y);
}
void vfoo(va_list args)
{
    foo(va_arg(args, int), va_arg(args, int));
}
void bar(char *z)
{
    printf("bar(%s)\n", z);
}
void vbar(va_list args)
{
    bar(va_arg(args, char*));
}

int main()
{
    wrapper(vfoo, 1, 2);
    wrapper(vbar, "Hello, World!");
    return 0;
}

Live example on Coliru.

like image 185
YSC Avatar answered Apr 02 '23 04:04

YSC


Have you considered (ab)using the preprocessor?

#include <stdio.h>

#define wrapper(f) /* Set up   */\
                   f;            \
                   /* Clean up */

void foo(int x, int y) {
    printf("x: %d; y: %d\n", x, y);
}

void bar(char *str) {
    printf("str: %s\n", str);
}

int main(void) {
    wrapper(foo(42, 11));
    wrapper(bar("hello world"));
}

To elaborate upon why I added the possibility of an ab- prefix to (ab)use, I wouldn't hesitate to use whatever is the most expressive solution to a problem, regardless of the attitude held by the general populus. However, I recognise that sometimes we have no control over the restrictions we are bound by, and policies are often put in place to heavily restrict the use of macros. Unfortunately, those bound by such silly policies won't find this post too helpful.

In a comment above I suggested that "if you want a feature from Javascript you should probably write your code in Javascript...".

Upon pondering about this overnight, I came to the conclusion that features similar to those you ask are actually doable and would be quite nice in C... so I admit, I was wrong.

The following is an example of mimicking how Javascript treats functions.

#include <stdio.h>

struct argument;
typedef struct argument argument;
typedef void function(argument *);

struct argument {
    function *function;

    /* for foo() */
    int x;
    int y;

    /* for bar() */
    char *str;
};

void wrapper(argument *a) {
    // set up
    a->function(a);
    // clean up
}

void foo(argument *a) {
    printf("x: %d; y: %d\n", a->x, a->y);
}

void bar(argument *a) {
    printf("str: %s\n", a->str);
}

#define foo(...) wrapper(&(argument){ .function = foo, __VA_ARGS__ })
#define bar(...) wrapper(&(argument){ .function = bar, .str = "", __VA_ARGS__ })

int main(void) {
    foo(.x = 42, .y = 11);
    bar(.str = "hello world");
}

There are actually some really nice features coming from this style of wrapper:

  • It becomes very difficult for a down-stream programmerr to mung the stack, where-as it is very easy for programmers using typical variadic functions such as printf and scanf to cause subtle yet devastating bugs by passing the wrong types and/or values to those functions. This leads on to my next point:
  • Default argument values are possible by simply modifying the foo and bar macros. As an example in the code above, the default value for the argument named str is set to "" (an empty string) for bar.
  • By introducing extra logic into wrapper, you can mimic partial function application (or binding properties to functions, much like you'd see in Javascript). Additionally, with a little bit of effort, mimicking continuation passing style might be possible by turning wrapper into a trampoline-style function and modifying the return value? I'll ponder more on this and provide an update tomorrow.
  • Finally, down-stream users are encouraged to pair argument identifiers with argument values, which improves maintainability drastically.

There are some minor draw-backs, the most significant being that neither foo nor bar can be passed, unapplied, to another function as an argument. This would be rare for these functions specifically, as they differ greatly in signature, but for functions that are identical or even similar in signature I can understand one might want to pass them as callbacks or what-not.

The solution to that problem is to rename the macros to default_foo and default_bar or something like that...

Finally, thanks for asking such a thought-provoking question... I hope reading my answer has been as interesting for you as thinking about (and writing) it was for me.

like image 25
autistic Avatar answered Apr 02 '23 04:04

autistic