0

I am very interested in the way how can I translate a C++ class into Lua class. And I found a great helper class - LunaWrapper (Description here)

But it seems, that in Lua, the only thing that persist of the class itself (not talking about resulting objects here!) is only the constructor. That is, I can't call any static functions if any. (for example, let's add to the example which is described on LunaWrapper page a function:

static bool Foo::bar(const char* text)
{
    printf("in bar\n");
}
)

So, for example, what I want to do in Lua:


local foo = Foo()
foo:foo()
Foo.bar() -- this should output "in bar", but this wont work! And this is what I want.
foo.bar() -- this should also output "in bar", but this is probably going to work.

How do I do that?

4
  • LunaWrapper doesn't do that. So you'd have to do it yourself. Also, Lua is a proper name, not an acronym. So you shouldn't use ALL CAPS. Commented Aug 31, 2012 at 10:02
  • 1
    Indeed, Lua wrappers tend to be a bit picky about what they expose and people often just write their own to make sure they match their needs, or at least that's what I've done in the past. If you want to add the functionality to LunaWrapper, remember that Lua types are glorified tables and that objects are just assigned such a metatable that is then used to look up methods. So in your case Foo is a metatable. It's created in the static Luna::Register function. You can anything you want to it there, or even add a metatable to the metatable to be able to do Foo.bar, not just foo.bar Commented Aug 31, 2012 at 10:32
  • Ok, it seems that I get that. And is there a possibility to preserve the Foo() behaviour as a constructor? Is it some metamethod which should be used? Is it __call metamethod? Commented Aug 31, 2012 at 13:17
  • 1
    To be precise - the Register function does two things: it creates a metatable named T::className, as well as a function named T::className that creates the object and sets its metatable to the one it generated. So yes - you can preserve the constructor behavior and just modify the metatable. Commented Aug 31, 2012 at 13:58

1 Answer 1

1

After a bit of playing around, this is what I came up with:

First, add another struct to Luna and define the static functions in another table within your class (just to match the style of the LunaWrapper code, you can use whatever way you like to get that information:

struct StaticRegType {
  const char *name;
  int(*mfunc)(lua_State*); // pointers to static members are C-style func pointers
                           // and are incompatible with int(T::*mfunc)() ones
};
...
static const Luna<Foo>::StaticRegType StaticRegister[];
...
const Luna<Foo>::StaticRegType Foo::StaticRegister[] = {
   { "bar", &Foo::bar },
   { 0 }
};

Now for the interesting part. Unfortunately, it turns out you have to change quite a bit of LunaWrapper to get it to do what you want. Instead of making a function called Foo that calls the constructor, we're going to make a table named Foo and attach a metatable with a __call method, so that we maintain the Foo() constructor syntax (this goes in Luna::Register):

lua_newtable(L);
... // here we'll insert our manipulations of the table
lua_setglobal(L, T::className); // setglobal will pop the table off the stack
                                // so we'll do whatever we want to it and then give it
                                // a name to save ourselves the extra lookup

Creating the metatable and adding the constructor and garbage collection functions:

luaL_newmetatable(L, T::className);
lua_pushstring(L, "__gc");
lua_pushcfunction(L, &Luna<T>::gc_obj);
lua_settable(L, -3);
lua_pushstring(L, "__call");
lua_pushcfunction(L, &Luna<T>::constructor);
lua_settable(L, -3);

Now we need to add all of our methods. We're going to use the __index metamethod - two options: 1. set __index to a cfunction that takes the name we tried to call from lua and runs the function, or 2. set __index to a table that contains all of our functions (see this for more information on both options). I prefer the latter since it saves us from looping through all our functions, doing pesky string comparisons and we can do some copy-pasta and reuse the closures from Luna. It does however require us to make a new staticThunk function that will handle our new methods:

static int staticThunk(lua_State *L) {
  int i = (int)lua_tonumber(L, lua_upvalueindex(1));
  return (*(T::StaticRegister[i].mfunc))(L);
}

Note it's quite a bit simpler since we don't need to fetch the object on which we're calling the function (I'm also rather fond of the template abstraction that let's the compiler handle the specifics of triggering the right function for our object, but that was just a good decision on the Luna author's part ;) ).

Now we need to make a table for __index and add the methods to it.

lua_pushstring(L,"__index"));
lua_newtable(L);
// adding the normal methods is the same as in Luna (the for-loop over T::Register)
// add the static methods by going over our new StaticRegister array (remember to use
// staticThunk for them and thunk for the members
lua_settable(L, -3); // push the __index table to the metatable

Almost there... Here's the metatable trickery - we have built the Foo table (even if it's not really named Foo just yet) and now we are going to set the metatable we build for our objects as it's metatable too. This way we can do both Foo.bar() and local foo = Foo(); foo.bar():

lua_setmetatable(L, -2); // at this point the stack contains our metatable right under
                         // our table
lua_setglobal(L, T::className); // name the darn thing

The last thing you need to do is get rid of anything in Luna::constructor that isn't related to actually constructing the object (added benefit - Register actually registers the object type and constructor actually just allocates it, the way it should be if you ask me). We moved the loop that was originally in here to the Register function. And we're done!

Note: One drawback of this solution is that while for convenience it allows you to call both Foo.bar() and foo.bar() it also allows you to call Foo.foo(), which doesn't make sense and would be caught as an illegal attempt to call a member function during compilation of an equivalent C/C++ program, but will fail with a runtime error in Lua. If you want to get rid of this behavior you would have to create one metatable for the Foo table that does not include the member functions and another for the objects you create that includes them.

Sign up to request clarification or add additional context in comments.

1 Comment

Well, I have created something different in plain Lua exactly what I wanted, will post it a bit later, but thanks anyway for your investigation! Yes, I figured out that the thing which was lost was the "__call" methamethod and a "basic" class table, so I was successful in creating a class with static variables, static functions (even private ones) and public and private variables. Will post the solution a bit later.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.