Interfacing Lua With Templates in C++11 Conclusion

The code for this project is hosted here

This is a final part of a 3 part series: – part 1part 2part 3

This is the final writeup on my personal attempt to abstract the interface between Lua and modern C++11. The first part discussed the implementation of calling Lua functions from C++. The second part tackled the inverse problem, binding and calling C++ functions from Lua. To conclude the series, I will discuss the approach I take to binding C++ classes to Lua. Because many of the template metaprogramming methods were already discussed in the other two parts, this will be the shortest one of the series.

Lua as a language does not have objects built-in. Rather, it provides a powerful abstraction via tables and metatables. While it is possible to come up with elaborate schemes for mapping objects to Lua tables to support polymorphism, inheritance, and other OO concepts, this would likely require the Selene library to be bundled with a Lua package which is not the goal of the project (currently). As such, the implementation discussed here and provided by Selene opts for the simplest mapping possible. The hope is that this functionality will be sufficient for any user of Selene to implement whatever abstraction they may need on top of it with relative ease.

Following the pattern of the previous posts, we will first consider what the interface will look like as a guide.

Lua-C++-Object Interface

To keep things simple, we’ll allow the user to register specific instances of C++ objects. If the user wishes to instantiate C++ objects from Lua, it is simple to expose a C++ function to do so. Thus, we will not handle construction/destruction and C++ object lifetime which may unecessarily complicate usage of the library. The class we will try to bind to Lua is the following:

1
2
3
4
5
6
7
struct Foo {
    int x;
    Foo(int x_) : x(x_) {}

    int DoubleAdd(int y) { return 2 * (x + y); }
    void SetX(int x_) { x = x_; }
};

C++ does not support any sort of class reflection so we have no choice but to have the user specify which member functions are to be exposed. This isn’t too problematic as the user may want to name the functions differently in the Lua context anyways.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Foo foo(0); // Create an instance of Foo with x initialized to 0

// Create a context and register the instance
sel::State state;
state.Register("foo", foo,
               "double_add", &Foo::DoubleAdd,
               "set_x", &Foo::SetX);

// Change foo.x from 0 to 4 from Lua
state.CallField("foo", "set_x", 4);
assert(foo.x == 4);

// Call Foo::DoubleAdd on instance foo
int result = state.CallField<int>("foo", "double_add", 3);
assert(result == 14);

The Register function is overloaded to accept an name to bind an instance to in Lua, a reference to the instance, and a variadic list of alternating function names and pointers.

Implementation

Tackling member functions

The first thing we need is a sel::ClassFun object which allows us to call member functions from Lua. The sel::ClassFun class is actually very similar to the sel::Fun from part 2, the only distinction being that instead of being bound to a global, the sel::ClassFun is bound to a field of our instance table. The syntax for doing this is lua_setfield(l, index, name) where index is the position on the stack where the table resides and name is the name of the field we wish to bind the function to. Recall that the way sel::Fun worked was by binding a closure with a pointer to the sel::Fun instance as an upvalue. The same approach works here.

Next, we’ll need a sel::Class object to contain the instances of sel::ClassFun. The skeleton of the class looks like:

Class.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace sel {
struct BaseClass {
    virtual ~BaseClass() {}
};

template <typename T, typename... Funs>
class Class : public BaseClass {
private:
    std::vector<std::unique_ptr<BaseFun>> _funs;

public:
    // TODO: constructor
};
}

To implement the member function registration, let’s start from the bottom by providing a function which registers only a single function to sel::Class.

1
2
3
4
5
6
7
8
9
10
11
12
13
template <typename Ret, typename... Args>
void _register_fun(lua_State *state,
                   T *t,
                   const char *fun_name,
                   Ret(T::*fun)(Args...)) {
    std::function<Ret(Args...)> lambda = [t, fun](Args... args) {
        return (t->*fun)(args...);
    };
    constexpr int arity = detail::_arity<Ret>::value;
    _funs.emplace_back(
        new ClassFun<arity, Ret, Args...>
        {state, std::string{fun_name}, lambda});
}

The usage of the lambda allows us to convert a member function to a non-member function by binding it to the pointer to the instance, t. This is what allows us to leverage existing code used to describe sel::Fun. The detail::_arity struct is simply a type trait struct which looks at the return type to deduce how many values the function is expected to return (0 in case of void, 1 in case of a single value, or N in case of a size-N tuple). Its definition is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace sel {
namespace detail {

template <typename T>
struct _arity {
    static contexpr int value = 1;
};

template <typename... Vs>
struct _arity<std::tuple<Vs...>> {
    static contexpr int value = sizeof...(Vs);
};

template <>
struct _arity<void> {
    static contexpr int value = 0;
};

}
}

Since the user may opt to bind multiple member functions, we’ll need to provide a function that recusively unpacks the arguments while making call to sel::Class::_register_fun. Call it sel::Class::_register_funs for simplicity.

1
2
3
4
5
6
7
8
9
10
// base case
void _register_funs(lua_State *state, T *t) {}

template <typename F, typename... Fs>
void _register_funs(lua_State *state, T *t,
                    const char *name, F fun,
                    Fs... funs) {
    _register_fun(state, t, name, fun);
    _register_funs(state, t, name, fun);
}

This is a slightly different pattern from what we’ve seen before. Notice that the function will recursively peel of two arguments at a time from the parameter pack, expecting the pack to be of the form const char * and F alternating. Code that attempts to call this function with an odd number of arguments will fail to compile.

With the _register_funs function, implementing the constructor of sel::Class is fairly trivial, requiring a call to lua_createtable and a call to _register_funs.

Class registration

The only thing left is to actually register the class with our sel::State object. Because the function requires a reference to the object instance, we can overload our existing sel::State::Register method.

1
2
3
4
5
6
7
8
template <typename T, typename... Funs>
void Register(const std::string &name, T &t,
              Funs... funs) {
    Unregister(name); // ensures the name is not used elsewhere
    auto tmp = std::unique_ptr<BaseClass>(
        new Class<T, Funs...>{_l, &t, name, funs...});
    _objs.insert(std::make_pair(name, std::move(tmp)));
}

An interesting point to note is that in declaring a new instance of sel::Class, the type arguments T and Funs... needed to be passed explicitly because template arguments cannot be deduced from arguments passed to the constructor.

Conclusion

And that’s a wrap for this series! If you’ve been following along, you now have a reasonably complete understanding of how Selene is implemented, sans a few more abstractions that have made the implementation easier in some cases. The library is still a work in progress, but it is slim enough that you should feel comfortable using it, experimenting with it, and perhaps adding your own functionality. The main takeaways are that clean interfaces can take a lot of work but they can be are absolute time-savers in the long wrong by abstracting away much unecessary boilerplate. If you come across similar boilerplate in the wild, hopefully, some of the tools and patterns presented in this series will help you. As always, feel free to leave a comment or question.

Comments