Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding class member function to c-function

Tags:

c++

templates

Below I have a concept of binding class member function to a global function. The main purpose of this is using C++ for C-style callback function implementation. Can this be done in a better way (for example, without final macro or typeof, or using C++0x features)?

#include <iostream>

using namespace std;

template<typename MF> struct mf_wrapper_helper;

template<typename R, typename T>
struct mf_wrapper_helper<R (T::*)()>
{
    template <R (T::*F)()>
    static R wrapper(T *foo) { return (foo->*F)(); }
};

template<typename R, typename T, typename T1>
struct mf_wrapper_helper<R (T::*)(T1)>
{
    template <R (T::*F)(T1)>
    static R wrapper(T *foo, T1 arg1) { return (foo->*F)(arg1); }
};

#define BIND_MF(mf) \
    mf_wrapper_helper<typeof(mf)>::wrapper<mf>


struct Foo
{
    void x() { cout << "Foo::x()" << endl; }
    void x1(int i) { cout << "Foo::x1(" << i << ")" << endl; }
};

int
main()
{
    typedef void (*f_t)(Foo *);
    typedef void (*f1_t)(Foo *, int i);

    Foo foo;

    f_t f_p = BIND_MF(&Foo::x);
    (*f_p)(&foo);

    f1_t f1_p = BIND_MF(&Foo::x1);
    (*f1_p)(&foo, 314);

    return 0;
}
like image 246
klimkin Avatar asked Nov 14 '22 03:11

klimkin


1 Answers

I think that the only technique which will work perfectly is to write a C wrapper function for each member function that you wish to invoke within a C callback; i.e.:

extern "C" void Foo_x(Foo *foo)
{
    foo->x();
}

extern "C" void Foo_x1(Foo *foo, int i)
{
    foo->x1(i);
}

You could also use lambda expressions of C++0x, which implicitly convert to a pointer to function having the same parameter and return types as the closure type’s function call operator. But, keep in mind that the language linkage of the function type is "C++", not "C".

#include <cstdlib>
#include <iostream>

using namespace std;

struct Foo
{
    void x() { cout << "Foo::x()" << endl; }
    void x1(int i) { cout << "Foo::x1(" << i << ")" << endl; }
};

int main()
{
    typedef void (*f_t)(Foo*); // default ("C++") language linkage
    typedef void (*f1_t)(Foo*, int);

    Foo foo;

    Foo_x(&foo);
    Foo_x1(&foo, -10);

    f_t fn = [] (Foo *foo) {
        foo->x();
    };
    fn(&foo);

    f1_t fn1 = [] (Foo *foo, int i) {
        foo->x1(i);
    };
    fn1(&foo, 314);

    return EXIT_SUCCESS;
}

Note that Section 5.2.2, Function call, of the C++ Standard states:

Calling a function through an expression whose function type has a language linkage that is different from the language linkage of the function type of the called function’s definition is undefined.

So the following technically invokes undefined behavior:

extern "C" typedef void (*f_t)(Foo*);

int main()
{
    Foo foo;

    f_t fn = [] (Foo *foo) {
        foo->x();
    };
    fn(&foo); // `fn` is a pointer to a function that uses "C++" language linkage,
            // but it is erroneously called through "C" language linkage.

//...

EDIT: After a bit of experimentation, I came up with the following template functions which return lambdas that call the given member function:

template <typename return_t, class base, typename... arg_types>
std::function<return_t (base*, arg_types...)> make_lambda_to_call_member_function(return_t (base::*mem_fn)(arg_types...)) 
{
    return [mem_fn] (base *o, arg_types... args) -> return_t {
        (o->*mem_fn)(args...);
    };
}

template <typename return_t, class base, typename... arg_types>
std::function<return_t (const base*, arg_types...)> make_lambda_to_call_member_function(return_t (base::*cmem_fn)(arg_types...) const) 
{
    return [cmem_fn] (const base *o, arg_types... args) -> return_t {
        (o->*cmem_fn)(args...);
    };
}

If Foo is defined as:

struct Foo
{
    void x() { cout << "Foo::x()" << endl; }
    void x1(int i) { cout << "Foo::x1(" << i << ")" << endl; }
    void cx1(float f) const { cout << "Foo::cx1(" << f << ")" << endl; }
};

Then you use the template make_lambda_to_call_member_function like:

auto fn = make_lambda_to_call_member_function(&Foo::x);
fn(&foo);

auto fn1 = make_lambda_to_call_member_function(&Foo::x1);
fn1(&foo, 314);

auto fn2 = make_lambda_to_call_member_function(&Foo::cx1);
fn2(&foo, 44.832f);

Note, however, that the returned lambda objects will not implicitly convert to a function pointer because the lambda expression uses a lambda-capture.

The latest draft of C++0x, n3225, states:

The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type's function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type's function call operator.

The following is illegal:

void (*fn5)(Foo*) = make_lambda_to_call_member_function(&Foo::x);
like image 191
Daniel Trebbien Avatar answered Dec 17 '22 23:12

Daniel Trebbien