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::mpl
to have some idea how to do it. You should definitely just use luabind
in 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:
namespace traits {
template <typename T>
struct push;
template <>
struct push<int> {
static void to_stack(lua_State * L, int x) { lua_pushinteger(L, x); }
};
template <>
struct push<double> {
static void to_stack(lua_State * L, double d) { lua_pushnumber(L, d); }
};
...
} // end namespace traits
Etc. You probably also want to specialize it for std::string
and things like that.
Then you can make a generic push
function like this:
template <typename T>
void push(lua_State * L, const T & t) {
traits::push<T>::to_stack(L, t);
}
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 between double
and int
etc., 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. Your read
technique 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 adapt
template that will take an arbitrary function pointer and try to adapt it into a lua_CFunction
that 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++14
you can use std::make_integer_sequence
, if not then you have to roll your own. Mine looks like this:
namespace detail {
/***
* Utility for manipulating lists of integers
*/
template <std::size_t... Ss>
struct SizeList {
static constexpr std::size_t size = sizeof...(Ss);
};
template <typename L, typename R>
struct Concat;
template <std::size_t... TL, std::size_t... TR>
struct Concat<SizeList<TL...>, SizeList<TR...>> {
typedef SizeList<TL..., TR...> type;
};
/***
* Count_t<n> produces a sizelist containing numbers 0 to n-1.
*/
template <std::size_t n>
struct Count {
typedef
typename Concat<typename Count<n - 1>::type, SizeList<n - 1>>::type type;
};
template <>
struct Count<0> {
typedef SizeList<> type;
};
template <std::size_t n>
using Count_t = typename Count<n>::type;
} // end namespace detail
Here's what your adapt
class might look like:
// Primary template
template <typename T, T>
class adapt;
// Specialization for C++ functions: int (lua_State *, ...)
template <typename... Args, int (*target_func)(lua_State * L, Args...)>
class adapt<int (*)(lua_State * L, Args...), target_func> {
template <typename T>
struct impl;
template <std::size_t... indices>
struct impl<detail::SizeList<indices...>> {
static int adapted(lua_State * L) {
try {
return target_func(L, read<Args>(L, 1 + indices)...);
} catch (std::exception & e) {
return luaL_error(L, "Caught an exception: %s", e.what());
}
}
};
public:
static int adapted(lua_State * L) {
using I = detail::Count_t<sizeof...(Args)>;
return impl<I>::adapted(L);
}
};
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 adapt
template produces a lua_CFunction
as a static class member, when you take a pointer to adapt<...>::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:
#define PRIMER_ADAPT(F) &::primer::adapt<decltype(F), (F)>::adapted
So, I can take a complicated C++ function f
, and then use PRIMER_ADAPT(&f)
as if it were simply a lua_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_checkstack
to be strictly correct.
You should definitely use one of the existing libraries unless you have a really compelling need that prevents it.