2

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.

Yu Hao
  • 119,891
  • 44
  • 235
  • 294
kageB
  • 21
  • 3
  • You should explain exactly why you can't use a binding library. Is it because, you need it to be header only, and you can't use something you have to link with? There are plenty of header only binding libraries. Is it because, your professor told you you can't? I have a hard time believing that. – Chris Beck Jul 18 '16 at 07:29

2 Answers2

1

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.

Chris Beck
  • 15,614
  • 4
  • 51
  • 87
0

If you're not limited to standard Lua lib, you can try LuaJIT. It has ffi support. Calling external function is as simple as:

local ffi = require("ffi")
ffi.cdef[[
int printf(const char *fmt, ...);
]]
ffi.C.printf("Hello %s!", "world")
Vlad
  • 5,450
  • 1
  • 12
  • 19