这是一个非常开放的问题。
我最近一直在研究一个 lua 绑定库,所以我可以解释一下我是如何做到的,但是有很多方法可以做到。
你没有标记这个问题 C++11。但是,我将假设您使用的是 C++11。如果不是,那么这将非常困难,我会说自己推出完全不切实际,特别是如果你对boost::mpl 还不够了解,无法知道如何去做。在这种情况下,你绝对应该使用luabind。
您需要做的第一件事是,您需要创建一些基本的基础设施,告诉您如何将 C++ 类型转换为相应的 lua 类型并返回。
IMO 最好的方法是使用类型特征,而 不是 一个大型重载函数。因此,您将定义一个主模板:
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
等等。您可能还想专门针对 std::string 之类的东西。
然后你可以像这样创建一个通用的push 函数:
template <typename T>
void push(lua_State * L, const T & t) {
traits::push<T>::to_stack(L, t);
}
这里的优点是调用push 时不考虑隐式转换。您传递的类型与您为其定义特征的内容完全匹配,或者它失败。并且您不能在 double 和 int 等之间遇到模棱两可的过载问题,这可能会让人头疼。
然后,你必须对read 做同样的事情,所以你有一个特征告诉你如何从堆栈中读取给定类型的值。您的read 技术需要以某种方式发出失败信号,您可以决定是否应该使用异常或其他技术。
一旦你有了这个,你就可以尝试制作一个adapt模板,它可以接受一个任意的函数指针,并尝试将它改编成一个大致相同的lua_CFunction。
基本上,您希望使用可变参数模板,以便您可以专门针对函数指针的所有参数。您将这些类型一一传递给您的读取方法,并使用索引序列从正确的堆栈位置读取。你尝试阅读它们,如果你能做到没有错误,那么你可以调用目标函数,然后返回它的结果。
如果您还想将通用 C++ 对象作为返回值推回,那么您可以在最后调用您的 push 函数。
首先,为了提供帮助,您需要一个“索引序列”工具。如果你在C++14,你可以使用std::make_integer_sequence,如果不是,那么你必须自己动手。我的看起来像这样:
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
您的 adapt 类可能如下所示:
// 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);
}
};
我实现的真正代码是here。我决定不使用异常来做。
此技术在编译时也有效——因为您将函数指针作为非类型模板参数传递给任意 C++ 函数,而 adapt 模板生成 lua_CFunction 作为静态类成员,当您将指针指向adapt<...>::adapted 时,它必须在编译时全部解析。这意味着编译器可以内联所有不同的位。
为了解决无法推断函数指针等非类型模板参数的类型(C++17 之前)的问题,我使用了如下所示的宏:
#define PRIMER_ADAPT(F) &::primer::adapt<decltype(F), (F)>::adapted
所以,我可以取一个复杂的 C++ 函数 f,然后像使用 lua_CFunction 一样使用 PRIMER_ADAPT(&f)。
您应该意识到,制作所有这些东西并对其进行测试需要很长时间。我在这个库上工作了一个多月,它是从另一个项目中的一些代码中重构出来的,在那里我对其进行了更长时间的改进。 lua 中也有很多与此类“自动化”堆栈操作相关的陷阱,因为它不会为您进行任何边界检查,您需要调用 lua_checkstack 才能严格正确。
您绝对应该使用现有的库之一,除非您有非常迫切的需要阻止它。