Search code examples
c++lualua-api

How to register C++ class constructor to Lua userdata and use it by default


Using the Lua C API, I registered a simple Object class to Lua, like this:

// My C++ Object class
class Object {
private:
    double x;
public:
    Object(double x) : x(x){}
};

// Create and return instance of Object class to Lua
int object_new(lua_State* L)
{
    double x = luaL_checknumber(L, 1);
    *reinterpret_cast<Object**>(lua_newuserdata(L, sizeof(Object*))) = new Object(x);
    luaL_setmetatable(L, "Object");
    return 1;
}

// Functions to register to Lua
const luaL_Reg functions[] =
{
    {"new", object_new},
    {nullptr, nullptr}
};

// Register the Object class to Lua
luaL_newmetatable(L, "Object");
luaL_setfuncs(L, functions, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");

In my Lua script, the following works just fine:

// Works!
my_object = Object.new(42)

But, I would like to be able to do this (i.e. omit the .new part):

// Fail :(
my_object = Object(42)

But when I execute the Lua script, I get this error:

...attempt to call a table value (global 'Object').

Is there a way to register a C++ class in a way that the constructor gets called if we don't provide a function name? What did I miss to make this work? That would be particularly useful for temporary objects.

Thanks!


Solution

  • You should check the return of luaL_newmetatable to register your metamethods just once.

    You can replace luaL_setmetatable by luaL_newmetatable, so your code is compatible to Lua 5.1, you can embed the metatable registration into the constructor and it works the same (except the additional lua_setmetatable).

    For a constructor, just register a function. The metatable should manage an instance, not his creation.

    Don't forget to add a deconstructor (__gc) to free your allocated C++ class instance.

    At the end you have just to register function Object which creates the metatable at his first call.

    #define LUA_META_OBJECT "Object"
    
    class Object {
    private:
        double x;
    public:
        Object(double x) : x(x){}
    };
    
    static int object_free(lua_State* L)
    {
        delete *static_cast<Object**>(luaL_checkudata(L, 1, LUA_META_OBJECT));
        return 0;
    }
    
    static int object_new(lua_State* L)
    {
        const lua_Number x = luaL_checknumber(L, 1);
        *static_cast<Object**>(lua_newuserdata(L, sizeof(Object*))) = new Object(x);
        if(luaL_newmetatable(L, LUA_META_OBJECT)){
            static const luaL_Reg functions[] =
            {
                {"__gc", object_free},
                {nullptr, nullptr}
            };
            luaL_setfuncs(L, functions, 0);
            lua_pushvalue(L, -1);
            lua_setfield(L, -2, "__index");
        }
        lua_setmetatable(L, -2);
        return 1;
    }
    
    ...
        lua_register(L, "Object", object_new);
    ...