问题
I have the following code:
lib.hxx:
template <typename C, typename R, typename ... Args>
R Lib::extract_call(lua_State* L, R(C::*method)(Args...))
{
return static_cast<C*>(this)->*method(extract_data<Args>(L)...);
}
lib.cc:
template <>
std::string Lib::extract_data(lua_State* L)
{
if (! lua_isstring(L, -1))
{
return "";
}
return lua_tostring(L, -1);
}
[...] // Other specializations following
I am embedding lua in a project, and I'm currently looking for a way to call methods from lua, and from a dispatcher extract arguments automatically from the lua stack. From those "simple" templates it is possible to easily generate the calls needed to extract data from the lua stack given in parameter without any typing error.
BUT, my problem is, when extract_data<Args>(L)...
is unpacked, the order of evaluation for all extract_data
calls are unspecified (as stated in standard, for optimization purposes), while it really matters in which order you extract data from the lua stack.
On the other hand, I can't regroup all of these calls in an initializer list since they are of different type.
Therefore, how can I ensure extract_data
calls to be in a specific order, or at least keep an automated way to pass arguments to my member pointer function ?
EDIT: I had forgotten that the calls need to be in revert order, which I don't think is achievable by any language mechanism. Therefore, here is my solution, back to regular, non variadic templates:
template <typename C, typename R, typename A1>
R Lib::extract_call(lua_State* L, R(C::*method)(A1))
{
return (static_cast<C*>(this)->*method)(extract_data<A1>(L));
}
template <typename C, typename R, typename A1, typename A2>
R Lib::extract_call(lua_State* L, R(C::*method)(A1, A2))
{
A2 b = extract_data<A2>(L);
A1 a = extract_data<A1>(L);
return (static_cast<C*>(this))->*method(a,b);
}
template <typename C, typename R,
typename A1, typename A2, typename A3>
R Lib::extract_call(lua_State* L, R(C::*method)(A1, A2, A3))
{
A3 c = extract_data<A3>(L);
A2 b = extract_data<A2>(L);
A1 a = extract_data<A1>(L);
return (static_cast<C*>(this))->*method(a,b,c);
}
// And so on up to 8 arguments
回答1:
Now we will get more complicated templates. The following code I wrote without compiler, there might be some mistakes:
template <unsigned int n>
class tuple_extractor{
template <typename T, typename ...ArgsOut, typename ...ArgsIn, typename ...ArgsPart>
static void extractTuple(
T* obj,
void (T::*func)(ArgsOut...),
const std::tuple<ArgsIn...>& inParams,
ArgsPart... partParams){
tuple_extractor<n-1>::extractTuple(obj, func, inParams, std::get<n-1>(inParams));
}
};
template <>
class tuple_extractor<0>{
template <typename T, typename ...ArgsOut, typename ...ArgsIn>
static void extractTuple(
T* obj,
void (T::*func)(ArgsOut...),
const std::tuple<ArgsIn...>& inParams,
ArgsIn... partParams){
obj->func(partParams...);
}
};
template <typename C, typename R, typename ... Args>
R Lib::extract_call(lua_State* L, R(C::*method)(Args...))
{
std::tuple<Args...> tmp{extract_data<Args>(L)...};
tuple_extractor<sizeof...(Args)>::extractTuple(static_cast<C*>(this), method, tmp);
}
It seems that GCC has a bug that influences ordering of the brace initialization. If you use a an affected version, just use
// For first-to-last order use:
template <typename T, typename ...Args>
inline std::tuple<T, Args...> getTuple(lua_State* L){
return std::tuple_cat(std::make_tuple<T>(extract_data<T>(L)), getTuple<Args...>(L));
}
template <typename T>
inline std::tuple<T> getTuple(lua_State* L){
return std::make_tuple<T>(extract_data<T>(L));
}
template <typename C, typename R, typename ... Args>
R Lib::extract_call(lua_State* L, R(C::*method)(Args...))
{
std::tuple<Args...> tmp = getTuple<Args...>(L);
tuple_extractor<sizeof...(Args)>::extractTuple(static_cast<C*>(this), method, tmp);
}
Firstly we create a tuple containing all the parameters, but in an ordered fashion, then we extract obtained tuple into parameters to a method call.
This is answer to the question posted. However I agree with @Mike - if you can change prototype of methods called by the pointer, you should add a overload for a tuple and just pass it as one parameter. Above code could in principle be almost fully inlined and cause very little performance overhead, but I am not sure what today's compilers will actually do with it.
EDIT:
Compilable version here:
回答2:
If you can change the method to take a single std::tuple<Args...>
, rather than multiple Args...
, then you can place extract_data
inside a brace-initialiser:
return static_cast<C*>(this)->*method({extract_data<Args>(L)...});
Unlike function arguments, evaluation of the clauses of the initialiser is sequenced from left to right.
来源:https://stackoverflow.com/questions/19942877/c-variadic-templates-and-evaluation-order