如何提高 Lua 包装器函数的简洁性

How can I improve the conciseness of my Lua wrapper functions?

本文关键字:函数 简洁性 包装 何提高 Lua      更新时间:2023-10-16

我有以下模板专用化,将C++函数包装到 Lua:

  template<class ...Args>
  struct Wrapper<void (*)(Args...)> {
    using F = void (*)(Args...);
    static int f (lua_State *L)
    {
      Lua lua(L);
      // Grab the function pointer.
      F f = (F) lua_touserdata(L, lua_upvalueindex(1));
      // Build a tuple of arguments.
      auto args = lua.CheckArgs<1, Args...>();
      // Apply the function to the tuple.
      FunctionPointer<F> fp(f);
      fp.Apply(args);
      return 0;
    }
  };
  template<class R, class ...Args>
  struct Wrapper<R (*)(Args...)> {
    using F = R (*)(Args...);
    static int f (lua_State *L)
    {
      Lua lua(L);
      // Grab the function pointer.
      F f = (F) lua_touserdata(L, lua_upvalueindex(1));
      // Build a tuple of arguments.
      auto args = lua.CheckArgs<1, Args...>();
      // Apply the function to the tuple.
      FunctionPointer<F> fp(f);
      lua.Push( fp.Apply(args) );
      return 1;
    }
  };

请注意它们之间的差异很小。在第一个专用化中,FunctionPointer<F>::Apply返回 void。在第二个中,它的结果被推送到 Lua 堆栈上。

我可以将这两个专业合并为一个吗?

我意识到这可能看起来很迂腐,但我不得不在我的代码中的其他地方编写很多这样的包装器,因为包装器的函数类型(自由函数,或 PMF,常量与否)有所不同。我总共有14个这样的专业。

以下是另外两个,它们仅在 PMF 是否常量方面有所不同:

  template <typename Self, typename ...Args>
  struct MethodWrapper<void (Self::*)(Args...) >
  {
    using F = void (Self::*)(Args...);
    static int f (lua_State *L)
    {
      Lua lua(L);
      F f = *(F *)lua_touserdata(L, lua_upvalueindex(1));
      Self* self = lua.CheckPtr<Self>(1);
      auto args = lua.CheckArgs<2, Args...>();
      FunctionPointer<F> fp(f);
      try {
        fp.Apply(self, args);
      } catch(std::exception& e) {
        luaL_error(L, e.what());
      }
      return 0;
    }
  };
  template <typename R, typename Self, typename ...Args>
  struct MethodWrapper<R (Self::*)(Args...) const >
  {
    // exactly the same as above
  };

我可以避免这种剪切和粘贴吗? (虽然不使用宏)

相关,但遭受相同数量的必需专业化:如何使用可变参数模板制作通用 Lua 函数包装器?

你应该能够创建一个泛型函子,它接受fpargslua,并调用lua.Push(),在Rvoid时具有部分特化,它只是调用函数并忽略(void)结果。 然后,您将像这样调用它:

ApplyAndPushIfNotVoid<R>()(lua, fp, args);

绝对可以消除所有重复的模板专用化。事实上,对于一次性分支的情况,比如在你的自由函数struct Wrapper中,你甚至不需要编写一个专门的来隐藏它——只需使用 type_traits 中的std::is_void

template<typename R, typename ...Args>
struct Wrapper
{
  using F = R (*)(Args...);
  static int f (lua_State *L, F f)
  {
    // ...
    FunctionPointer<F> fp {f};
    if (std::is_void<R>::value)
    {
      fp.Apply(args);
      return 0;
    }
    else
    {
      lua.Push( fp.Apply(args) );
      return 1;
    }
  }
};

编译器将根据其实例化方式优化其中一个分支。

但是,当

返回类型R = void时,假分支在实例化过程中仍然会进行类型检查,从而导致正文格式不正确。

像另一个答案一样使用模板专业化是一个明显的解决方案。还有另一种解决方法:FunctionPointer<F>::Apply R = void时返回虚拟void_type。例如使用 std::conditional ,可以修改FunctionPointer,使其工作如下:

template <typename F>
class FunctionPointer
{
  template <typename R, typename ...Args>
  static R func_return( R(*)(Args...) )
  { return {}; }
  using R_ = decltype( func_return( (F)nullptr ) );
  struct void_type {};
public:
  F f;
  using R = typename std::conditional<std::is_void<R_>::value,
                                      void_type, R_>::type;
  template <typename ...Args>
  R Apply(std::tuple<Args...> &args)
  { 
    // ...
    return {};
  }
};

IDEone 演示,其中外部依赖类型已存根。

对于MethodWrapper,我会从成员指针中识别不同的"特征"和它需要的方面,并提取所有这些并将其隐藏在某个特征类后面。我们称之为PMF_traits

template <typename T, typename ...Args>
struct PMF_traits
{
private:
  using T_traits = decltype( PMF_trait_helper( (T)nullptr ) );
public:
  using class_type  = typename T_traits::class_type;
  using return_type = typename T_traits::return_type;
  static const bool const_member = T_traits::const_member;
  using type = T;
};

PMF_trait_helper本身只是一个空函数,用于帮助从PMF中推断和提取类型信息。这是处理constnon-const PMF的地方。该信息是使用PMF_trait_detail捕获的,并传回PMF_traits

template <typename R, typename Class, bool Is_Const>
struct PMF_trait_detail
{
  using class_type = Class;
  using return_type = R;
  static const bool const_member = Is_Const;
};
template <typename R, typename Class, typename ...Args>
PMF_trait_detail<R, Class, false> PMF_trait_helper( R (Class::*)(Args...) )
{ return PMF_trait_detail<R, Class, false> (); }
template <typename R, typename Class, typename ...Args>
PMF_trait_detail<R, Class, false> PMF_trait_helper( R (Class::*)(Args...) const)
{ return PMF_trait_detail<R, Class, true> (); }

通过这种设置MethodWrapper不再需要单独处理const non-const案例

template <typename PMF, typename ...Args>
struct MethodWrapper
{
  typedef typename PMF_traits<PMF>::class_type Self;
  int f (lua_State *L)
  {
    // ...
    FunctionPointer<PMF> fp { (PMF) lua_touserdata(L, lua_upvalueindex(1)) };
    Self *self = lua.CheckPtr<Self>(1);
    // ...
    try
    {
      // Almost like 'Wrapper' above
      // handle void and non-void case etc.
      if (std::is_void< typename PMF_traits<PMF>::return_type >::value)
      {
        fp.Apply(self, args);
        return 0;
      }
      else { // ... }
    }
    catch(std::exception& e)
    {
      return luaL_error(L, e.what());
    }
  }
};

请注意,我没有捕获PMF_traits中的可变参数,只是为了降低模板的复杂性和语法措辞,但如果需要,也应该可以使用std::tuple编码和保存此信息。

使用此技术,您应该能够重构并显著减少所需的专业化数量。