I myself have experienced this experience.
Basically there are two good solutions that I know. One of them is good if a member function for a class for which there will be only one element for each lua state. The other is more flexible, but more complex and slow. (I would like to learn other methods / improvements to these methods!)
I think lua_bind uses some templates very similar to method 1, but uses tricks to make the implementation flexible, like method 2. I think that any of them is more transparent than lua_bind does.
Method 1
(1) For each my_class member my_class that you want to pass to lua, it should take lua_State * L and return an int .
(2) During lua initialization, save the pointer to the corresponding my_class in lua_extraspace .
*static_cast<my_class**>(lua_getextraspace(L_)) = &instance;
(3) When you want to pass member functions to lua, use a template like this:
typedef int (my_class::*mem_func)(lua_State * L);
Then you register things like this:
const luaL_Reg regs[] = { { "callback_1", &dispatch<&my_class::callback_1> }, { "callback_2", &dispatch<&my_class::callback_2> }, { "callback_3", &dispatch<&my_class::callback_3> }, { NULL, NULL } }; luaL_register(L, regs);
Method 1 has the advantage that it is quite simple and very fast, I think it will be faster than lua binding. Because get_extraspace does nothing but a little pointer arithmetic. Most likely, a good compiler can optimize the dispatch pattern so that the function it executes is built-in and there is no overhead.
You might want to modify the dispatch pattern so that a null pointer is specified in the extrasphere, depending on how the lifetimes of your lua state and your my_class .
Potentially, you can also store more complex things in the extrasphere, for example, pointers to several different objects or even as a vector or something like that (you can read about how to configure lavas extraspace in your documents). Or you can store things in the lua registry, and the send function can retrieve them from there, but the extra space is faster - it is up to you.
Method 2
In method 2, you mainly use the usual lua methods to push the C ++ object to lua that belongs to lua, but you do it where the object is C ++ std::function and you overload metafunction _call so that it calls function. (If you are not in C ++ 11, you can use boost::function .)
Then, when you want to push a C ++ member function to lua, you use std::bind to make it a function object.
This method has the disadvantage that inside lua the type of the “function” will actually be userdata , but since you can call it just fine and use it as a function, it doesn't really matter. If this is bad for you, one thing you can do is use the same trick, but also then do a closure that has the userdata object of the object as an upvalue, and when the call is called, it just redirects the arguments to and returns results. Then the closure type will be function in lua, but it will do basically the same thing.
typedef std::function<int(lua_State *)> lua_function; char const * cpp_function = "CPP_Function"; static int intf_dispatcher ( lua_State* L ) { //make a temporary copy, in case lua_remove(L,1) might cause lua to garbage collect and destroy it lua_function f = * static_cast<lua_function *> (luaL_checkudata(L, 1, cpp_function)); // remove from the stack before executing, so that like all other callbacks, f finds only its intended arguments on the stack. lua_remove(L,1); int result = (f)(L); return result; } static int intf_cleanup ( lua_State* L ) { lua_function * d = static_cast< lua_function *> (luaL_testudata(L, 1, cpp_function)); if (d == NULL) { std::cerr << "ERROR: intf_cleanup called on data of type: " << lua_typename( L, lua_type( L, 1 ) ) << std::endl; lua_pushstring(L, "C++ function object garbage collection failure"); lua_error(L); } else { d->~lua_function(); } return 0; } static int intf_tostring( lua_State* L ) { lua_function * d = static_cast< lua_function *> (luaL_checkudata(L, 1, cpp_function)); // d is not null, if it was null then checkudata raised a lua error and a longjump was executed. std::stringstream result; result << "c++ function: " << std::hex << d; lua_pushstring(L, result.str().c_str()); return 1; } void register_metatable ( lua_State* L ) { luaL_newmetatable(L, cpp_function); lua_pushcfunction(L, intf_dispatcher); lua_setfield(L, -2, "__call"); lua_pushcfunction(L, intf_cleanup); lua_setfield(L, -2, "__gc"); lua_pushcfunction(L, intf_tostring); lua_setfield(L, -2, "__tostring"); lua_pushvalue(L, -1); //make a copy of this table, set it to be its own __index table lua_setfield(L, -2, "__index"); lua_pop(L, 1); } void push_function( lua_State* L, const lua_function & f ) { void * p = lua_newuserdata(L, sizeof(lua_function)); luaL_setmetatable(L, cpp_function); new (p) lua_function(f); }