I needed to screw Lua to the project in C ++. To write wrappers by hand is too lazy (to write too much), the finished ones didn’t fit for one reason or another. I decided to write my own. So I wondered how to simplify the interface as much as possible. From the very thought of this, the terrible designs of the templates came to mind. So it later turned out, but much easier than it seemed.
In C ++ 11, templates with a variable number of arguments appeared; this allows writing template functions / classes in a way that was completely impossible in C ++ 03. Such templates greatly simplify the task.
The first thing I needed to do was write a wrapper over the simplest actions with the interpreter (one could do with simple calls to Lua C C, but I don’t want to keep a bunch of indexes of different values on the stack in memory. So I wrapped them into several functions that besides from the need to pass into each function a pointer to the state of the interpreter, practically do not require indexes, since they have default values.
')
As a result, I wanted to see an interface close to the following:
lua.export_function(some_function);
You can also try. However, the interface will still be a bit more complicated. It is necessary to specify the name of the exported function to the interpreter. And we will pass the address to the function.
lua.export_function("some_function", &some_function);
We use the output of the template parameters. Parameters can be displayed automatically if they are:
- Return value of callback:
template <typename T> void some_function(T (*callback)()) {}
- Callback Parameter:
template <typename T> void some_function(void (*callback)(T)) {}
- The class to which the method belongs.
template <typename T> void some_function(void (T::*method)()) {}
All of these cases (and several others) can be combined. You can take advantage of this.
template <typename R, typename... Args> void export_function(const std::string& name, T (*function)(Args...)) { }
Now, you can take on the actual export function. For each function, create a lambda that will take arguments from the interpreter, pass them to the function, and then return the result to the interpreter. Lambda should be stored all the time that an instance of the interpreter works, so I keep a pointer to each lambda inside the class and delete it in the destructor.
template <typename R, typename... Args> void export_function(const std::string& name, T (*function)(Args...)) { auto function = new std::function<int(Lua&)>([function](Lua& vm) -> int { auto tuple = args<Args...>(); return apply_function<std::tuple_size<decltype(tuple)>::value> ::apply(function, tuple); }); lambda(function); }
Looks weird. Let's try to figure it out. First you need to get all the arguments from the interpreter.
template <typename T, typename T1, typename... Args> std::tuple<T, T1, Args...> args(const int i = 1) { T t = arg<T>(i); return std::tuple_cat(t, args<T1, Args...>(i+1)); }
We get the i-th argument and return it, and with the help of recursion we get the remaining arguments. But this is not enough.
This function needs to be overloaded in order for the other code to be executed at the last iteration.
template <typename T> std::tuple<T> args(const int i = 1) { return std::tuple<T>(arg<T>(i)); }
The arg function is obvious, I will not give it, all that is required is to write a few specializations.
Now that we have all the arguments in one tuple, we need to pass them all to a function.
template <int N> struct apply_function { template <typename R, typename... FunctionArgs, typename... TupleArgs, typename... Args> static R apply(R (*function)(Args...), std::tuple<TupleArgs...>, Args... args) { return apply_function<N-1>::apply(function, tuple, std::get<N-1>::value, args); } };
And you need to specialize this template for the last iteration.
template <> struct apply_function<0> { template <typename R, typename... FunctionArgs, typename... TupleArgs, typename... Args> static R apply(R (*function)(Args...), std::tuple<TupleArgs...>, Args... args) { return (*function)(args...); } };
In addition to all this, a few more specializations are required (problems in the void type).
results
It turned out quite a working wrapper for exporting C ++ functions and classes to lua. Of the obvious drawbacks, I see only a few:
- Lambdas are still slower than callbacks, if you wish, you can rewrite the code without them, but you will get more sample functions.
- With each function / method call we get two recursions, the depth of which is equal to the number of arguments of the functions. Perhaps the compiler will make the entire horde of template functions inline, I have not checked (and am not sure about this).
- Templates have a big effect on compile time. But even on my rather weak laptop, the assembly of this wrapper and the code that uses it takes much less time than the assembly of code that uses boost, so this is not critical.
- No support for multiple inheritance - too dreary to do it.
- There is no access to metatables, which means there is no operator redefinition.
- There is no support for overloading functions, but you can simply give overloaded functions different names.
The last three points may do a little later.
And now the pros:
- Simple interface.
- The solution in pure C ++ 11 does not require the generation of additional code with additional tools.
How to use
First of all, you need to create an object of the class util :: Lua, and the interpreter is initialized.
util::Lua vm;
After that, you can export functions / classes.
Functions
It's simple. We use only the function pointer and the name under which it will be available in lua.
some_function(); vm.export_function("some_function", &some_function);
Types of all parameters and the return value will be defined and processed correctly.
Classes
The exported class must be prepared. First you need to inherit it from util :: LuaClass so that when the object is returned to the interpreter, it is the object that is returned, not the userdata. After you need to define three static methods.
- The export_class method should export all class methods / functions.
- The export_me method should call the function Lua :: export_class <A, B> ()
- The class_name method must return the class name.
class A : public util::LuaClass { public: static void export_class(Lua& vm); static void export_me(Lua& vm); static const std::string class_name(); }; void A::export_me(Lua& vm) { vm.export_class<A>(); } class B: public A { public: static void export_class(Lua& vm); static void export_me(Lua& vm); static const std::string class_name(); }; void B::export_me(Lua& vm) { vm.export_class<B, A>(); }
The util :: Lua :: export_class functions are passed as template parameters — the class we want
export and its parent to export and its (if it is not already done).
The most interesting thing is going on in the export_class method. For example:
vm.export_constructor<A, int>(); vm.export_function("static_method", &A::static_method); vm.export_method("method", &A::method);
It's simple. We export static methods as functions, methods in a similar way, but through a separate function. The constructor is exported as a function named new, the types of its arguments must be explicitly specified as
template arguments, this is due to the fact that you cannot take a pointer to the constructor. The nice thing is that objects created by calling such a constructor from lua will process the Garbage Collector. When all object references are deleted, delete will be called for the C ++ object.
Code
All code is posted on
github.com/alex-ac/LuaCxx under the MIT license.
I would be glad to see comments, tips, ficrequest and bug reports.
UPD
I almost forgot. All code is built using g ++ 4.7.2, g ++> = 4.6.4 and clang> = 3.0 should also work.