Reputation: 6410
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
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
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