Taylor
Taylor

Reputation: 6410

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

I have the following template specializations which wrap C++ functions to 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;

    }
  };

Notice how they differ rather minimally. In the first specialization, FunctionPointer<F>::Apply returns void. In the second, it's result is pushed onto the Lua stack.

Can I combine these two specializations into one?

I realize this may seem pedantic, but I have had to write a lot of these wrappers elsewhere in my code, because of variations in the type of the function being wrapper (free function, or PMF, const or not). I have a total of 14 such specializations.

Here are two more which differ only by whether the PMF is const or not:

  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
  };

Can I avoid this cut and paste? (Without using macros though)

Related, but suffers from the same number of required specializations: How to use variadic templates to make a generic Lua function wrapper?

Upvotes: 2

Views: 356

Answers (2)

greatwolf
greatwolf

Reputation: 20838

It's definitely possible to eliminate all that repetitive template specialization. In fact, for a one-off branching case, like in your free-function struct Wrapper, you don't even need to write a specialization to hide it -- just use std::is_void from type_traits:

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;
    }
  }
};

The compiler will optimize out one of the branches depending on how it gets instantiated.

There is a slight wrinkle though, when the return type is R = void the falsey branch still gets type-checked during instantiation which results in the body being ill-formed.

Using template specialization like in the other answer is one obvious solution. There is an alternative workaround: have FunctionPointer<F>::Apply return a dummy void_type when R = void. For example using std::conditional, FunctionPointer can be modified to work like:

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 Demo with external dependent types stubbed out.

For the MethodWrapper, I would identify the different 'traits' and aspects it needs from the member pointer and extract all those and hide it behind some trait class. Let's call it 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;
};

The PMF_trait_helper itself is just an empty function to help deduce and extract type information out of PMF. Here is where the const and non-const PMF is handled. That information is captured using PMF_trait_detail and passed back up to 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> (); }

With that setup MethodWrapper no longer needs to handle const non-const cases separately

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());
    }
  }
};

Note I didn't capture the variadic arguments in the PMF_traits just to keep the template complexity and syntactic verbiage down but it should be possible to encode and save this info too using std::tuple if you need that.

Using this technique you should be able to refactor and significantly reduce the number of specializations you need.

Upvotes: 1

John Zwinck
John Zwinck

Reputation: 249133

You should be able to make a generic functor which takes fp, args, and lua, and calls lua.Push(), with a partial specialization for when R is void which just invokes the function and ignores the (void) result. You would then invoke it like this:

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

Upvotes: 2

Related Questions