Lua bindings, C++ Functions, Loose Ends


There are a few loose ends that are left over from the exposing of C++ functions to be used in Lua.

First, the current implementation leaks memory pretty badly. Every time a function is exposed to Lua, it constructs a new LuaCallable_CppFunction on the heap, which is never deleted. To solve this, we take advantage of Lua’s garbage collection. Just as a metatable can have a __call method to call userdata, it can also have a __gc method to state what should happen when userdata is deleted. Here, I will make a second function for the purpose of deleting the LuaCallable once it is done.

int garbage_collect_lua_callable(lua_State* L){
  void* storage = lua_touserdata(L, 1);
  LuaCallable* callable = *static_cast<LuaCallable**>(storage);
  delete callable;
  return 0;

Now, modifying the Push() function

void Push(lua_State* L, LuaCallable* callable){
  void* userdata = lua_newuserdata(L, sizeof(callable));
  *static_cast<LuaCallable**>(userdata) = callable;

  int metatable_uninitialized = luaL_newmetatable(L, "lua-bindings_cpp-function");
    Push(L, call_lua_callable);
    lua_setfield(L, -2, "__call");

    Push(L, garbage_collect_lua_callable);
    lua_setfield(L, -2, "__gc");

    Push(L, "Access restricted");
    lua_setfield(L, -2, "__metatable");
  lua_setmetatable(L, -2);

Now, the garbage_collect_lua_callable() function will be called whenever Lua’s garbage collection decides that the function is no longer needed. In addition, by assigning a value to the __metatable field, we make the metatable itself be inaccessible from within Lua. This is a safety feature, as otherwise, someone might manually call the garbage collection causing multiple deletes to be called.

Second, as I alluded to earlier, there is some template magic in indices and build_indices.

template<int... Is>
struct indices {};

template<int N, int... Is>
struct build_indices
  : build_indices<N-1, N-1, Is...> {}

template<int... Is>
struct build_indices<0, Is...> : indices<Is...> {};

This trick is taken from It recursively counts down, generating each index. A function accepts an argument of type indices<Indices...>, and is passed an argument of type build_indices<N>. This then generates all indices from 0 to N-1.

This is needed so that, at compile time, many arguments can be read from the Lua stack. Consider the following code.

void function(){
  func(Read<double>(L, 1), Read<int>(L, 2), Read<std::string>(L, 3) );

With the indices trick, we can make this into a variadic function as follows.

template<int... Indices, typename... Params>
void function_helper(indices<Indices...>){
  func(Read<Params>(L, Indices+1)...);

template<typename... Params>
void function(){

By passing build_indices into the helper function, we fix the value of Indices. This then allows us to make the appropriate function call each time to Read().