2

I'm using Luabind to bind a C++ API to Lua. I have some objects that cannot be created directly, but rather must be created on another thread. I'm currently handling this by defining a "static" member called create that yields until the object is created:

luabind::class_<Foo>("Foo")
  .scope
  [
    luabind::def("create", &someCreateMethod, luabind::yield)
  ]

This works, but has the disadvantage of complicating the client API. For these classes, you cannot create them normally (e.g. local f = Foo()), but instead need to do local f = Foo.create().

Is it possible to define a Luabind constructor that doesn't actually call the C++ constructor, but instead another function that returns the constructed object (and can yield in the meantime)? I've tried defining bindings for __init and __call (the latter under a scope, to define it on the class, not its instances), but I didn't have success with either approach.

Xtapolapocetl
  • 613
  • 5
  • 21

2 Answers2

1

Constructors in Luabind must be actual C++ class constructors. So you'll just have to deal with the slight API weirdness.

If all you're interested in is the ability to use Foo as a constructor method, then you can do this. Register your C++ class Foo as FooLua to Lua. Then, register this someCreateMethod, not as a member of FooLua, but as just a Lua free function called Foo. Thus, as far as the user is concerned, Foo is a constructor for the Lua class Foo.

Now, this will inhibit your ability to give Foo other static properties, like members and so forth. But you could accomplish that by using some direct Lua API coding. You can create an empty table Foo and create a metatable for it that forwards __index and __newindex calls to FooLua. Similarly, you can override this metatable's __call to forward the construction to Foo.create.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • And there's no way, on either the C++ or the Lua side, to "fake" it with the `__call` operator either? – Xtapolapocetl Nov 14 '11 at 01:38
  • 1
    @Xtapolapocetl: Luabind stores C++ types as userdata, so Lua code cannot affect its metatables. You could poke a Luabind's metatables from C++, but really, this is such a minor issue that it's negligible. Indeed, if the syntax *really* bothers you, you could just call the object `FooInternal` with Luabind and just create a function `Foo` that calls the `FooInternal.create` function. The user can think that they're using a class called `Foo`. – Nicol Bolas Nov 14 '11 at 01:49
  • @Xtapolapocetl: In addition to Nicol's comment, if you only use `Foo` to create objects (i.e. no other scope methods) you could even just do `Foo = Foo.create`. – sbk Nov 14 '11 at 02:01
0

While luabind doesn't provide a straight-forward way of defining custom constructors, it is in fact possible with a bit of a hack:

template<typename T,auto TCnstrct,typename ...TArgs>
static void custom_constructor(luabind::argument const &self_, TArgs... args)
{
    using holder_type = luabind::detail::value_holder<T>;
    luabind::detail::object_rep* self = luabind::touserdata<luabind::detail::object_rep>(self_);

    void* storage = self->allocate(sizeof(holder_type));
    self->set_instance(new (storage) holder_type(nullptr,TCnstrct(std::forward<TArgs>(args)...)));
}

template<typename T,auto TCnstrct,typename ...TArgs>
    static void define_custom_constructor(lua_State *l)
{
    auto *registry = luabind::detail::class_registry::get_registry(l);
    auto *crep = registry->find_class(typeid(T));
    assert(crep);
    auto fn = luabind::make_function(l,&custom_constructor<T,TCnstrct,TArgs...>);
    crep->get_table(l);
    auto o = luabind::object{luabind::from_stack(l,-1)};
    luabind::detail::add_overload(o,"__init",fn);
    lua_pop(l,1);
}

This will allow you to use any free function as a constructor after the class definition:

static void define_vector_class(lua_State *l)
{
    auto modMath = luabind::module_(l,"math");
    struct Vector
    {
        Vector()=default;
        float x,y,z;
    };
    auto defVec = luabind::class_<Vector>("Vector");
    modMath[defVec];

    // Define custom constructor taking three float arguments
    define_custom_constructor<Vector,[](float x,float y,float z) -> Vector {
        Vector v;
        v.x = x;
        v.y = y;
        v.z = z;
        return v;
    },float,float,float>(l); // Constructor parameter types have to be specified in template parameter list as well
}

Tested with the deboostified version of luabind (https://github.com/decimad/luabind-deboostified), but it should work with the regular version as well.

Silverlan
  • 2,783
  • 3
  • 31
  • 66