📜 ⬆️ ⬇️

Patterns with a variable number of arguments using the example of a wrapper for Lua

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:

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:

The last three points may do a little later.
And now the pros:

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.

 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.

Source: https://habr.com/ru/post/165765/


All Articles