Кастинг между 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);