Emulating std::bind in C

2020-03-19 02:26发布

I'm using std::bind to provide a callback while abstracting some logic by binding some parameters first. 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 example, the do_action does not need to know about the secret_id and other functions can reuse it without having a secret_id of their own. 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 then is there another way to pass data from first() to callback() without complicating the neutral do_action()?

4条回答
地球回转人心会变
2楼-- · 2020-03-19 02:29

An opaque type and keeping secret in a source should do it:

#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 address registering callback functions)

查看更多
迷人小祖宗
3楼-- · 2020-03-19 02:39

No. C doesn't allow you to do that directly.

In C the standard way to handle callbacks is using context pointers:

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

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

This void * normally points to a struct that will contain all the extra parameters or data you need in the callback and using this approach the library doesn't depend on what this context is. If the callback doesn't need any context you just pass a NULL pointer as context and ignore the first parameter when being called from the library.

Something that is kind of hackish and formally unsafe but it's sometimes done is that if the context is a simple data that fits the size of a void * (e.g. an integer) and if your environment is not going to have problems with it you can trick the library by passing a fake void * that is just an integer and you convert it back to an integer when being called from the library (this saves the caller from allocating the context and managing its lifetime).

On how to how to trick the language to avoid this limitation (still remaining in the land of portable C) I can think some hack:

First we allocate a pool of two-arguments callbacks and context data

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

then we write (or macro-generate) functions that we wish to register and that will call the two-arguments versions.

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 a pool where they're allocated and deallocated:

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 bound functions must also be explicitly deallocated

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;
}
查看更多
家丑人穷心不美
4楼-- · 2020-03-19 02:39

The short answer is no.

The only thing you can do is declare another function that has the secret_id built into it. If you're using C99 or newer you can make it an inline function to at least limit the function call overhead, although a newer compiler may do that by itself anyway.

To be frank though, that is all std::bind is doing, as it is returning a templated struct, std::bind simply declares a new functor that has secret_id built into it.

查看更多
小情绪 Triste *
5楼-- · 2020-03-19 02:45

As 6502 explained, it is not possible to do this in portable C without some kind of context argument being passed to the callback, even if it doesn't name secret_id directly. However, there are libraries such as Bruno Haible's trampoline that enable creation of C functions with additional information (closures) through non-portable means. These libraries do their magic by invoking assembly or compiler extensions, but they are ported to many popular platforms; if they support architectures you care about, they work fine.

Taken from the web, here is an example of code that trampoline enables is this higher-order function that takes parameters a, b, and c (analogous to your secret_id, and returns a function of exactly one parameter x that 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;
}
查看更多
登录 后发表回答