Std :: bind emulation in C

I use std :: bind to provide a callback when abstracting some logic, first binding some parameters. i.e.

void start() { int secret_id = 43534; //Bind the secret_id to the callback function object std::function<void(std::string)> cb = std::bind(&callback, secret_id, std::placeholders::_1); do_action(cb); } void do_action(std::function<void(std::string)> cb) { std::string result = "hello world"; //Do some things... //Call the callback cb(result); } void callback(int secret_id, std::string result) { //Callback can now do something with the result and secret_id } 

So, in the above do_action example, you don't need to know about secret_id, and other functions can reuse it without having their own secret_id. This is especially useful when do_action is some kind of asynchronous operation.

My question is, is there a way to bind parameter values ​​to function pointers using only C?

If not by emulating std :: bind, is there another way to pass data from first () to callback () without complicating the neutral do_action ()?

+6
source share
4 answers

No. C does not allow you to do this directly.

In C, the standard method for handling callbacks is the context pointer:

 void register_callback(void (*cback)(void *context, int data), void *context); 

this means that you will pass a function that will accept void * in addition to the normal parameters that the callback should handle (in the above case, an integer), and you will also pass the void * that you want to go back.

This void * usually points to a struct that will contain all the additional parameters or data that you need in the callback, and using this approach, the library does not depend on what this context is. If the callback does not need any context, you simply pass the NULL pointer as context and ignore the first parameter when calling from the library.

Something like hacking and formally unsafe, but sometimes it is done if the context is simple data that matches the size of void * (for example, an integer), and if your environment does not have problems, you can trick the library by passing a fake void * , which is just an integer, and you convert it back to an integer when called from the library (this saves the caller from highlighting the context and managing its lifetime).

On how to trick the language to avoid this restriction (while still staying in the portable C country), I might think of some hacks:

First, we allocate a pool of two callbacks arguments and contextual data.

 void (*cbf[6])(int, int); int ctx[6]; 

then we write down (or macro-generating) the functions that we want to register, and which will invoke versions with two arguments.

 void call_with_0(int x) { cbf[0](ctx[0], x); } void call_with_1(int x) { cbf[1](ctx[1], x); } void call_with_2(int x) { cbf[2](ctx[2], x); } void call_with_3(int x) { cbf[3](ctx[3], x); } void call_with_4(int x) { cbf[4](ctx[4], x); } void call_with_5(int x) { cbf[5](ctx[5], x); } 

We also store them in the pool, where they are allocated and freed:

 int first_free_cback = 0; int next_free_cback[6] = {1, 2, 3, 4, 5, -1}; void (*cbacks[6])(int) = { call_with_0, call_with_1, call_with_2, call_with_3, call_with_4, call_with_5 }; 

Then, to bind the first parameter, we can do something like

 void (*bind(void (*g)(int, int), int v0))(int) { if (first_free_cback == -1) return NULL; int i = first_free_cback; first_free_cback = next_free_cback[i]; cbf[i] = g; ctx[i] = v0; return cbacks[i]; } 

but related functions must also be explicitly freed

 int deallocate_bound_cback(void (*f)(int)) { for (int i=0; i<6; i++) { if (f == cbacks[i]) { next_free_cback[i] = first_free_cback; first_free_cback = i; return 1; } } return 0; } 
+12
source

As explained in 6502, this cannot be done in portable C without any context argument passed to the callback, even if it does not call secret_id directly. However, there are libraries such as the Bruno Highl tram that allow you to create C-functions with additional information (closure) via non-portable means. These libraries do their magic by invoking compiler builds or extensions, but they carry over to many popular platforms; if they support the architecture you care about, they work great.

Taken from the Internet , here is an example of code that includes a trampoline - a higher order function that takes parameters a , b and c (similar to your secret_id ) and returns a function of exactly one parameter x , which calculates a*x^2 + b*x + c :

 #include <trampoline.h> static struct quadratic_saved_args { double a; double b; double c; } *quadratic_saved_args; static double quadratic_helper(double x) { double a, b, c; a = quadratic_saved_args->a; b = quadratic_saved_args->b; c = quadratic_saved_args->c; return a*x*x + b*x + c; } double (*quadratic(double a, double b, double c))(double) { struct quadratic_saved_args *args; args = malloc(sizeof(*args)); args->a = a; args->b = b; args->c = c; return alloc_trampoline(quadratic_helper, &quadratic_saved_args, args); } int main() { double (*f)(double); f = quadratic(1, -79, 1601); printf("%g\n", f(42)); free(trampoline_data(f)); free_trampoline(f); return 0; } 
+4
source

The short answer is no.

The only thing you can do is declare another function that has secret_id built secret_id . If you use C99 or newer, you can make it a built-in function to at least limit the overhead of the function call, although the new compiler can do this on its own.

To be honest, that’s all std :: bind does, since it returns a template structure, std :: bind just declares a new functor that has the secret_id function built in.

+3
source

An opaque type and secrecy in the source should do this:

 #include <stdio.h> // Secret.h typedef struct TagSecret Secret; typedef void (*SecretFunction)(Secret*, const char* visible); void secret_call(Secret*, const char* visible); // Public.c void public_action(Secret* secret, const char* visible) { printf("%s\n", visible); secret_call(secret, visible); } // Secret.c struct TagSecret { int id; }; void secret_call(Secret* secret, const char* visible) { printf("%i\n", secret->id); } void start() { Secret secret = { 43534 }; public_action(&secret, "Hello World"); } int main() { start(); return 0; } 

(The above does not apply to registering callback functions)

+3
source

Source: https://habr.com/ru/post/954758/


All Articles