voltnor
voltnor

Reputation: 111

Set a function argument with function pointers

In C is there a way to set a function argument using function pointers such that all calls to that function pointer just use the set parameter? As an example, is it possible to do something like the code below?

void do_something(int *a, int *b, int *c){
    *a = *c - 5;
    *b = *c - 10;
}

typedef void (*funcptr)(int *a, int *b, int *c);

int main(){
  int num = 5;
  funcptr f = do_something(c=&num); // c = 5 for all calls to f(a, b)
  int *a; int *b;
  f(a, b); //a = 0, b = -5

  num = 10;
  f = do_something(c=&num); // c = 10 for all calls to f(a, b)
  f(a, b); //a = 5, b = 0
}

This seems possible with external C libraries like Blocks and FFCALL, but it appears I'd need to set a, b, and c instead of setting a single argument.

Upvotes: 0

Views: 603

Answers (5)

Daniel Jour
Daniel Jour

Reputation: 16156

[..] is there a way [..] ?

There always is a way. The question is if it's worth the effort and possible downsides.

The following is based on another answer I gave about deferred (that is, called e.g. at the end of scope) function execution.

Start off with a unified type for a "partially applied function":

struct partially_applied {
  void * data; // parameters
  void (*function)(void *); // function unpacking parameters and calling actual function
  void (*store)(void *, char const *, void *); // storing parameters
};
typedef struct partially_applied * FUN;

To make a function partially apply-able we need

  • a struct holding the (eventually) already applied parameters (I added an init function and an allocation function)
  • a function to unpack these and call the actual function
  • a function to store ("partially apply") parameters

Here we go:

#define MAKE_PARTIAL(fn, N, ...) \
struct partially_applied_ ## fn ## _data { \
    DATA_DEF(N, __VA_ARGS__) \
}; \
\
static void init_partially_applied_ ## fn ## _data (void * p) { \
    struct partially_applied_ ## fn ## _data * data = p; \
    DATA_INIT(N, __VA_ARGS__); \
} \
\
static void * allocate_partially_applied_ ## fn ## _data (void) { \
    void * data = malloc(sizeof(struct partially_applied_ ## fn ## _data)); \
    if (data == NULL) { \
        fprintf(stderr, "Allocation failure for " #fn " data\n"); \
        exit(1); \
    } \
    init_partially_applied_ ## fn ## _data(data); \
    return data; \
} \
\
static void partially_applied_ ## fn (void * p) { \
    struct partially_applied_ ## fn ## _data * data = p; \
    if (DATA_CHECK(N, __VA_ARGS__)) { \
        fn(DATA_ACCESS(N, __VA_ARGS__)); \
    } else { \
        fprintf(stderr, "Not all parameters for " #fn " are vaild\n"); \
    } \
} \
\
static void partially_applied_ ## fn ## _store ( \
    void * p, char const * id, void * src) { \
    struct partially_applied_ ## fn ## _data * data = p; \
    DATA_STORE_CODE(N, __VA_ARGS__) \
    fprintf(stderr, "Cannot store %s in " #fn "!\n", id); \
}

The above contains some macros. These depend on the number of macro arguments (which could be counted by the preprocessor, but I want to keep it simpler). To expand the correct macro (depending on the number of arguments) we need a little helper:

#define SPLICE_2(l,r) l##r
#define SPLICE_1(l,r) SPLICE_2(l,r)
#define SPLICE(l,r) SPLICE_1(l,r)

Now on to the macros. DATA_DEF defines the structure contents:

#define DATA_DEF_0(...)
#define DATA_DEF_1(type, name)      type name; bool name ## _valid;
#define DATA_DEF_2(type, name, ...) type name; bool name ## _valid; DATA_DEF_1(__VA_ARGS__)
#define DATA_DEF_3(type, name, ...) type name; bool name ## _valid; DATA_DEF_2(__VA_ARGS__)
#define DATA_DEF_4(type, name, ...) type name; bool name ## _valid; DATA_DEF_3(__VA_ARGS__)
// add more to support more parameters
#define DATA_DEF(N, ...) SPLICE(DATA_DEF_,N)(__VA_ARGS__)

DATA_INIT expands to code to initialize such a structure:

#define DATA_INIT_0(...)
#define DATA_INIT_1(t, name)      data->name ## _valid = false;
#define DATA_INIT_2(t, name, ...) data->name ## _valid = false; DATA_INIT_1(__VA_ARGS__)
#define DATA_INIT_3(t, name, ...) data->name ## _valid = false; DATA_INIT_2(__VA_ARGS__)
#define DATA_INIT_4(t, name, ...) data->name ## _valid = false; DATA_INIT_3(__VA_ARGS__)
// add more to support more parameters
#define DATA_INIT(N, ...) SPLICE(DATA_INIT_,N)(__VA_ARGS__)

DATA_CHECK expands to a condition testing if all arguments have been applied:

#define DATA_CHECK_0(...) true
#define DATA_CHECK_1(t, name)      data->name ## _valid
#define DATA_CHECK_2(t, name, ...) data->name ## _valid && DATA_CHECK_1(__VA_ARGS__)
#define DATA_CHECK_3(t, name, ...) data->name ## _valid && DATA_CHECK_2(__VA_ARGS__)
#define DATA_CHECK_4(t, name, ...) data->name ## _valid && DATA_CHECK_3(__VA_ARGS__)
// add more to support more parameters
#define DATA_CHECK(N, ...) SPLICE(DATA_CHECK_,N)(__VA_ARGS__)

DATA_ACCESS expands into code for passing the parameters to the actual function (actually it's only the comma separated arguments list):

#define DATA_ACCESS_0(...)
#define DATA_ACCESS_1(t, name)      data->name
#define DATA_ACCESS_2(t, name, ...) data->name, DATA_ACCESS_1(__VA_ARGS__)
#define DATA_ACCESS_3(t, name, ...) data->name, DATA_ACCESS_2(__VA_ARGS__)
#define DATA_ACCESS_4(t, name, ...) data->name, DATA_ACCESS_3(__VA_ARGS__)
// add more to support more parameters
#define DATA_ACCESS(N, ...) SPLICE(DATA_ACCESS_,N)(__VA_ARGS__)

And finally DATA_STORE_CODE expands into code to store the parameters:

#define DATA_STORE_CODE_OP(type, name) \
if (strcmp(id, #name) == 0) { data->name = *((type *) src); data->name ## _valid = true; return; }

#define DATA_STORE_CODE_0(...)
#define DATA_STORE_CODE_1(type, name)      DATA_STORE_CODE_OP(type, name)
#define DATA_STORE_CODE_2(type, name, ...) DATA_STORE_CODE_OP(type, name) DATA_STORE_CODE_1(__VA_ARGS__)
#define DATA_STORE_CODE_3(type, name, ...) DATA_STORE_CODE_OP(type, name) DATA_STORE_CODE_2(__VA_ARGS__)
#define DATA_STORE_CODE_4(type, name, ...) DATA_STORE_CODE_OP(type, name) DATA_STORE_CODE_3(__VA_ARGS__)
// more
#define DATA_STORE_CODE(N, ...) SPLICE(DATA_STORE_CODE_,N)(__VA_ARGS__)

Adding little helpers to allocate and free partially applied function structures (data is expected to be allocated by malloc here) ...

FUN make_fun(void (*function)(void *), void (*store)(void *, char const *, void *), void * data) {
    FUN f = malloc(sizeof(*f));
    if (f == NULL) {
        fprintf(stderr, "Allocation of FUN failed\n");
        exit(1);
    }
    f->function = function;
    f->store = store;
    f->data = data;
    return f;
}
void free_fun(FUN f) {
    free(f->data);
    free(f);
}

... we can go on to define a macro that actually makes an instance of a partially applied function:

#define PARTIAL(fn) make_fun(&(partially_applied_ ## fn), \
                             &(partially_applied_ ## fn ## _store), \
                             allocate_partially_applied_ ## fn ## _data())

Of course we want to be able to apply some arguments:

#define APPLY(PFN, N, ...) \
do { \
    struct partially_applied * pfn = (PFN); \
    DATA_STORE(N, __VA_ARGS__) \
} while(0)

The macro DATA_STORE expands into code to call the store functions multiple times, so that we can apply multiple arguments at once:

#define DATA_STORE_OP(name, value) pfn->store(pfn->data, #name, &(value));
#define DATA_STORE_0(...)
#define DATA_STORE_1(name, value) DATA_STORE_OP(name, value)
#define DATA_STORE_2(name, value, ...) DATA_STORE_OP(name, value) DATA_STORE_1(__VA_ARGS__)
#define DATA_STORE_3(name, value, ...) DATA_STORE_OP(name, value) DATA_STORE_2(__VA_ARGS__)
#define DATA_STORE_4(name, value, ...) DATA_STORE_OP(name, value) DATA_STORE_3(__VA_ARGS__)
#define DATA_STORE(N, ...) SPLICE(DATA_STORE_,N)(__VA_ARGS__)

Last but not least we want to be able to call such a function (this could also be a function, but well):

#define CALL(fn) (fn)->function((fn)->data)

Finally, an example:

void foo(char * str, int i) {
    printf("FOO| str = %s, i = %d\n", str, i);
}
void bar(float f, int i, size_t s) {
    printf("BAR| f = %f, i = %d, s = %zu\n", f, i, s);
}
MAKE_PARTIAL(foo, 2, char *, string, int, integer)
MAKE_PARTIAL(bar, 3, float, floating, int, INT, size_t, SOME_SIZE)


int main() {
    FUN f = PARTIAL(foo);
    char * c = "Crazy";
    APPLY(f, 1, string, c);
    printf("doing other stuff\n");
    FUN g = PARTIAL(bar);
    size_t size = 99;
    APPLY(g, 1, SOME_SIZE, size);
    int answer = 42;
    APPLY(f, 1, integer, answer);
    answer = 21;
    float pi = 3.14;
    APPLY(g, 2, INT, answer, floating, pi);
    CALL(f);
    printf("done\n");
    CALL(g);
    printf("now completely done\n");
    return 0;
}

Some downsides:

  • macros. macros everywhere.
  • Losing some type safety (in APPLY)
  • Need for lvalues (APPLY(f, 1, integer, 42) does not work)

Upvotes: 0

dbush
dbush

Reputation: 224362

Rather than using function pointers, you would need a wrapper function that uses a global for state.

void do_something(int *a, int *b, int c){
    *a = c - 5;
    *b = c - 10;
}

static int c_val = NULL;

void do_something_wrap(int *a, int *b)
{
    do_something(a, b, c);
}

int main(){
  int num = 5;
  c_val = num;
  int a; int b;
  do_something_wrap(&a, &b); //a = 0, b = -5

  num = 10;
  c_val = num;
  do_something_wrap(&a, &b); //a = 5, b = 0
}

Upvotes: 0

Marcus Müller
Marcus Müller

Reputation: 36402

no, this is not a language feature of C or of the C calling convention.

You'll need to construct function that calls your function pointer with that argument yourself; external libraries that provide such functionality are available, as you've noticed.

Another way, though I'm personally not overly fond of that is using <stdarg> and varargs, letting your function do one thing or another, depending on the number of arguments passed.

In C frameworks, you'll often find a lot of functions that work in some kind of context. That is often even used in implementation of object-oriented programming. I don't know the larger picture of what you want to implement, but often questions like these are rised in a context where someone wants to do something that resembles function overloading in C++; if that's the case, you might really just make functions that take a "state/context" argument, and further arguments, which might or might not be relevant, depending on the state.

Upvotes: 1

a3f
a3f

Reputation: 8657

Standard C has no such facility. Compilers offer extensions that allow this though. Nested functions in GCC, Blocks in clang or via a library like CALLBACK in clisp or assembling your call via libffi.

Upvotes: 0

Vlad from Moscow
Vlad from Moscow

Reputation: 311048

C has no such features like default arguments or function overloading.

Moreover in this declaration

funcptr f = do_something;

the initializer shall be a function pointer.

Upvotes: 0

Related Questions