Home > Software design >  How to cast function into a struct in C?
How to cast function into a struct in C?

Time:05-11

This is my first post on StackOverflow, so I hope the format will be okay.

I want to pass functions as parameter to another function. To that end, I declare a struct to describe functions. But then, I get an invalid analyser error on compilation.

In functions.h, I have this bit:

struct double_fun{
    double (*function)(double x);
};
// Test functions.
extern struct double_fun square;
// Other test functions

// Exponential function.
extern double exponential_temp(double x,double temperature);
extern struct double_fun exponential(double temperature);

Then in functions.c:

// Test functions for somme.
double square_fun(double x){
    return x*x;
}
struct double_fun square = square_fun();
// Other test functions

// Exponential function.
double exponential_temp(double x, double temperature){
    return exp(x/temperature); // From math.h
}

struct double_fun exponential(double temperature){
    double aux_fun(double x){return exponential_temp(x, temperature);};
    struct double_fun aux = aux_fun;
    return aux;
}


double somme(double* liste, int length, struct double_fun fun){
    double poids = 0;
    for(int i=0;i<length;  i){
        poids = poids   (fun.function)(liste[i]);
    }
    return poids;
}

My ultimate goal is to use somme(list, length, function) to apply a function to a list then return the sum of the elements (in particular with the exponential function). But in another file where I call somme, I call it several times for different values of temperature. This is why I have exponential(temperature) which is supposed to return a function depending on the value of temperature.

This is the error I get:

make learning
gcc -Iinclude/ -o source/functions.o -c source/functions.c -Wall -Werror -lm
source/functions.c:83:28: error: invalid initializer
   83 | struct double_fun square = square_fun;
      |                            ^~~~~~~~~~
[[Same error for other test functions]]
source/functions.c: In function ‘exponential’:
source/functions.c:104:29: error: invalid initializer
  104 |     struct double_fun aux = aux_fun;
      |                             ^~~~~~~
make: *** [Makefile:21 : source/functions.o] Erreur 1

I tried to use no struct and return pointers towards functions instead, and it worked for the test functions, but then there seemed to be no valid syntax for exponential(temperature) that would return a function depending on temperature. On StackOverflow, I found several explanations of how to pass functions as parameters, but no example allows me to have temperature as a parameter without being an argument.

If you could help me either get around this error or find another way to pass a function as parameter to somme, I would be very grateful!

CodePudding user response:

You don't convert the function to a struct. The function is a member of the struct, so you use an ordinary initializer list to fill it in:

struct double_fun square = {square_fun};

Also, you don't put () after the function name. That calls the function, it doesn't evaluate to the function pointer itself.

There's little point in using a struct for this. You might use a struct if it held multiple functions that you want to pass together. For example, a device is represented in the kernel as a struct containing function pointers for opening, reading, writing, closing, etc. the device.

CodePudding user response:

You actually try to implement a closure.

The idiomatic way of doing it in is C is passing a function pointer and a void pointer to the context.

However, some time ago I came up with a different approach. Surprisingly, there is a family of builtin types in C that carries both a data and the code itself. Those are pointers to a function pointer.

The trick is use this single object to pass both the code by dereferencing a function pointer. And next passing the very same double function pointer as the context as a first argument. It looks a bit convoluted by actually it results in very flexible and readable machanism for closures.

See the code:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

// typedefing functions makes usually makes code more readable
typedef double double_fun_t(void*, double);

struct exponential {
   // closure must be placed as the first member to allow safe casting
   // between a pointer to `closure` and `struct exponential`
  double_fun_t *closure;
  double temperature;
};

double exponential(void *ctx_, double x) {
  struct exponential *ctx = ctx_;
  return exp(x / ctx->temperature);
}

// the "constructor" of the closure for exponential
double_fun_t **make_exponential(double temperature) {
  struct exponential *e = malloc(sizeof *e);
  e->closure = exponential;
  e->temperature = temperature;
  return &e->closure;
}

// now simple closure with no context, a pure x -> x*x mapping
double square(void *_unused, double x){
    (void)_unused;
    return x*x;
}

// use compound literal to transform a function to a closure
double_fun_t **square_closure = & (double_fun_t*) { square };

// the worker that process closures, note that `double_fun_t` is not used
// because `double(**)(void*,double)` is builtin type
double somme(double* liste, int length, double (**fun)(void*,double)){
    double poids = 0;
    for(int i=0;i<length;  i)
        // calling a closure, note that `fun` is used for both obtaing
        // the function pointer and for passing the context
        poids = poids   (*fun)(fun, liste[i]);
    return poids;
}

int main(void) {
    double list[3] = { 1, 2, 3 };
    
    printf("%g\n", somme(list, 3, square_closure));

    // a dynamic closure
    double_fun_t **exponential = make_exponential(42);
    printf("%g\n", somme(list, 3, exponential));
    free(exponential);

    return 0;
}

The advantage of this approach is that the closure exports a pure interface for calling double->double functions. There is no need to introduce any boxing structures used by all clients of the closure. The only requirement is the "calling convention" which is very natural and does not require sharing any code.

  • Related