Привязка функции-члена класса к c-функции - PullRequest
5 голосов
/ 31 января 2011

Ниже у меня есть концепция связывания функции-члена класса с глобальной функцией.Основной целью этого является использование C ++ для реализации функции обратного вызова в стиле C.Можно ли сделать это лучше (например, без финального макроса или typeof или с использованием функций C ++ 0x)?

#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;
}

1 Ответ

1 голос
/ 02 февраля 2011

Я думаю, что единственная техника, которая будет работать идеально, - это написать функцию-обертку C для каждой функции-члена, которую вы хотите вызвать в обратном вызове C; i.e.:

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

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

Вы также можете использовать лямбда-выражения C ++ 0x, которые неявно преобразуются в указатель на функцию, имеющую тот же параметр и возвращаемый тип, что и оператор вызова функции закрытого типа. Но имейте в виду, что языковой связкой типа функции является «C ++», а не «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;
}

Обратите внимание, что в разделе 5.2.2 «Вызов функции» стандарта C ++ говорится:

Вызов функции через выражение, тип функции которого имеет языковую связь, которая отличается от языковой связи типа функции определения вызываемой функции, не определена.

Таким образом, следующее технически вызывает неопределенное поведение:

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.

//...

РЕДАКТИРОВАТЬ: После нескольких экспериментов, я придумал следующие шаблонные функции, которые возвращают лямбда-выражения, которые вызывают данную функцию-член:

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...);
    };
}

Если Foo определяется как:

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; }
};

Затем вы используете шаблон make_lambda_to_call_member_function как:

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);

Обратите внимание, однако, что возвращаемые лямбда-объекты не будут неявно преобразованы в указатель функции, потому что лямбда-выражение использует лямбда-захват.

В последней версии C ++ 0x, n3225 говорится:

Тип закрытия для лямбда-выражения без лямбда-захвата имеет общедоступную не виртуальную неявную функцию преобразования констант в указатель на функцию с таким же параметром и возвращает типы как оператор вызова функции типа замыкания. Значение, возвращаемое этой функцией преобразования, должно быть адресом функции, которая при вызове имеет тот же эффект, что и вызов оператора вызова функции типа замыкания.

Недопустимо следующее:

void (*fn5)(Foo*) = make_lambda_to_call_member_function(&Foo::x);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...