so basically I'm trying to find a way to use C++ functions in Lua that are not lua_CFunctions (don't return and int and take a lua_State as a parameter). Basically your regular old C++ function. The catch, though, is I'm trying to find a way to do it without writing it's own dedicated lua_CFunction (so basically imagine I already have a program or a bunch of functions in C++ that I want to use in Lua, and I don't want to have to write a new function for each of them).
So, say I have a very simple C++ function:
static int doubleInt(int a) {
return a*2;
}
(with or without the static, it shouldn't(?) matter).
Say I want to use this function in Lua by calling doubleInt(10) in a lua script. Is there a way to do this without writing a separate
static int callFunc(lua_State *L) {
//do stuff to make the function work in lua
}
for every individual function? So something along the lines of what luaBind does with their def() function (and I know it sucks, but I can't really use a separate dedicated binding library; have to write my own).
I know I have to write a class with templates for this but I don't even have the slightest idea about how to go about getting the function in Lua. I don't think there is a way in C++ to automatically generate a custom function (presumably at compile time) - that would be amazing - so I don't even know where to start.
This is a very open-ended question.
I have been working on a lua binding library recently, so I can explain how I did this, but there are many ways you could do it.
You didn't tag this question C++11. I'm going to assume however that you are using C++11. If not, then it is extremely difficult and I would say not at all practical to roll your own especially if you don't already know enough about
boost::mplto have some idea how to do it. You should definitely just useluabindin that case.The first thing you need is, you need to create some basic infrastructure that tells you how to convert C++ types to corresponding lua types and back.
IMO the best way to do this is using a type trait, and not one massive overloaded function. So, you will define a primary template:
Etc. You probably also want to specialize it for
std::stringand things like that.Then you can make a generic
pushfunction like this:The advantage here is that implicit conversions are not considered when you call
push. Either the type you passed exactly matches something you defined the trait for, or it fails. And you can't get ambiguous overload problems betweendoubleandintetc., which can be a big pain in the butt.Then, you have to do the same thing for
read, so you have a trait that tells you how to read values of a given type off the stack. Yourreadtechnique needs to signal failures somehow, you can decide if that should be using exceptions or a different technique.Once you have this, you can try to make an
adapttemplate that will take an arbitrary function pointer and try to adapt it into alua_CFunctionthat does roughly the same thing.Basically, you want to use variadic templates so that you can specialize against all the parameters of the function pointer. You pass those types one by one to your read method, and use an index sequence to read from the correct stack positions. You try to read them all, and if you can do it without errors, then you can call the target function, and then you return its results.
If you want to also push generic C++ objects back as the return value, then you can call your push function at the end.
First, to help, you need an "index sequence" facility. If you are in
C++14you can usestd::make_integer_sequence, if not then you have to roll your own. Mine looks like this:Here's what your
adaptclass might look like:The real code from my implementation is here. I decided to do it without using exceptions.
This technique also works at compile-time -- since you are passing a function pointer to an arbitrary C++ function as a non-type template parameter, and the
adapttemplate produces alua_CFunctionas a static class member, when you take a pointer toadapt<...>::adapted, it has to all be resolved at compile-time. This means that all the different bits can be inlined by the compiler.To work around the inability to deduce the type of a non-type template parameter like a function pointer (prior to C++17), I use a macro which looks like this:
So, I can take a complicated C++ function
f, and then usePRIMER_ADAPT(&f)as if it were simply alua_CFunction.You should realize though that making all this stuff and testing it takes a really long time. I worked on this library more than a month, and it is refactored out from some code in another project where I had refined it longer. There are also a lot of pitfalls in lua related to "automating" stack operations like this, because it doesn't do any bounds checking for you and you need to call
lua_checkstackto be strictly correct.You should definitely use one of the existing libraries unless you have a really compelling need that prevents it.