Кастинг между void * и указателем на функцию-член

В настоящее время я использую GCC 4.4, и у меня есть отличная головная боль между void * и указателем на функцию-член. Я пытаюсь написать простую в использовании библиотеку для связывания объектов С++ с интерпретатором Lua, например:

LuaObject<Foo> lobj = registerObject(L, "foo", fooObject);
lobj.addField(L, "bar", &Foo::bar);

У меня есть большая часть этого, за исключением следующей функции (которая специфична для определенной сигнатуры функции, пока у меня не будет возможности ее обобщить):

template <class T>
int call_int_function(lua_State *L) 
{
    // this next line is problematic
    void (T::*method)(int, int) = reinterpret_cast<void (T::*)(int, int)>(lua_touserdata(L, lua_upvalueindex(1)));
    T *obj = reinterpret_cast<T *>(lua_touserdata(L, 1));

    (obj->*method)(lua_tointeger(L, 2), lua_tointeger(L, 3));
    return 0;
}

Для тех из вас, кто не знаком с Lua, lua_touserdata(L, lua_upvalueindex(1)) получает первое значение, связанное с замыканием (в этом случае оно является указателем на функцию-член) и возвращает его как void *. GCC жалуется, что void * → void (T:: *) (int, int) является недопустимым. Любые идеи о том, как обойти это?

Ответ 1

Вы не можете отсылать указатель-член к void * или к любому другому "регулярному" типу указателя. Указатели-члены не являются адресами, как обычные указатели. Скорее всего, вам нужно будет обернуть вашу функцию-член в регулярную функцию. С++ FAQ Lite подробно объясняет это. Основная проблема заключается в том, что данные, необходимые для реализации указателя на член, являются не просто адресом, а фактически сильно варьируется на основе компилятора реализация.

Я предполагаю, что у вас есть контроль над возвращаемыми данными пользователя lua_touserdata. Это не может быть указателем на член, поскольку нет законного способа вернуть эту информацию. Но у вас есть другие варианты:

  • Самый простой выбор - это, вероятно, обернуть вашу функцию-член в бесплатную функцию и вернуть ее. Эта свободная функция должна принять объект в качестве первого аргумента. См. Пример кода ниже.

  • Используйте метод, аналогичный методу Boost.Bind mem_fun, чтобы вернуть объект функции, который вы можете соответствующим образом настроить. Я не вижу, что это проще, но это позволит вам связать большее состояние с возвратом функции, если вам нужно.

Здесь вы переписываете свою функцию, используя первый способ:

template <class T>
int call_int_function(lua_State *L) 
{
    void (*method)(T*, int, int) = reinterpret_cast<void (*)(T*, int, int)>(lua_touserdata(L, lua_upvalueindex(1)));
    T *obj = reinterpret_cast<T *>(lua_touserdata(L, 1));

   method(obj, lua_tointeger(L, 2), lua_tointeger(L, 3));
   return 0;
}

Ответ 2

Можно преобразовать указатель в функции-члены и атрибуты с помощью объединений:

// helper union to cast pointer to member
template<typename classT, typename memberT>
union u_ptm_cast {
    memberT classT::*pmember;
    void *pvoid;
};

Чтобы преобразовать, поместите исходное значение в один элемент и вытащите целевое значение из другого.

Хотя этот метод практичен, я понятия не имею, будет ли он работать в каждом случае.

Ответ 3

В качестве обходного решения с ограничениями приведения функции-указателя к элементу void* вы можете обернуть указатель функции в маленькую выделенную кучу структуру и поместить указатель на эту структуру в ваши пользовательские данные Lua:

template <typename T>
struct LuaUserData {
    typename void (T::*MemberProc)(int, int);

    explicit LuaUserData(MemberProc proc) :
        mProc(proc)
    { }

    MemberProc mProc;
};

LuaObject<Foo> lobj = registerObject(L, "foo", fooObject);
LuaUserData<Foo>* lobj_data = new LuaUserData<Foo>(&Foo::bar);

lobj.addField(L, "bar", lobj_data);

// ...

template <class T>
int call_int_function(lua_State *L) 
{
    typedef LuaUserData<T>                       LuaUserDataType;
    typedef typename LuaUserDataType::MemberProc ProcType;

    // this next line is problematic
    LuaUserDataType* data =
        reinterpret_cast<LuaUserDataType*>(lua_touserdata(L, lua_upvalueindex(1)));
    T *obj = reinterpret_cast<T *>(lua_touserdata(L, 1));

    (obj->*(data.mMemberProc))(lua_tointeger(L, 2), lua_tointeger(L, 3));
    return 0;
}

Я не разбираюсь в Lua, поэтому я, вероятно, не заметил что-то в приведенном выше примере. Имейте в виду, что если вы пройдете этот маршрут, вам придется управлять распределением LuaUserData.

Ответ 4

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

Если вам нужно связать нестационарную функцию С++ с C-подобным механизмом обратного вызова на основе void *, то вы можете попытаться сделать это вместо статической оболочки.

Обертка может принимать указатель на экземпляр в качестве аргумента и передавать управление нестатической функции-члену:

void myclass::static_fun(myclass *instance, int arg)
{
   instance->nonstatic_fun(arg);
}

Ответ 5

Здесь просто измените параметры функции void_cast, чтобы они соответствовали вашим потребностям:

template<typename T, typename R>
void* void_cast(R(T::*f)())
{
    union
    {
        R(T::*pf)();
        void* p;
    };
    pf = f;
    return p;
}

пример использования:

auto pvoid = void_cast(&Foo::foo);