of arguments and return values but in essence is always the same. In other words, it begs to be automated.
Programming in Lua, in listing 25.4 presents a solution a la printf/scanf. While it works well for numbers and strings it has the same drawbacks. It has the same potential for memory access disasters and is not easily extensible for new types.
Unfortunately this was the only way to write a generic call funtion in C++ without writing overloads for 0 to N arguments. However with the variadic templates feature of the new C++11 standard we can write a new generic call function that is both type-safe and user-extensible.
function add(a, b)
return a+b
end
For example, given the Lua function above, we would like to call it like this:int result = callFunc<int>(L, "add", 1, 2);Our first attempt of implementation looks like this:
// type-specific helper
template<typename T>
struct LuaValue;
// recursive function template to push a variable number of arguments
template<class H, class... T>
int pushArgs(lua_State* L, const H& h, const T&... t) {
int size = LuaValue<H>::size();
LuaValue<H>::pushValue(L, h);
return size + pushArgs(L, t...);
}
// base case
int pushArgs(lua_State* L) {
return 0;
}
// the main function
template<class R, class... Args>
R callFunc(lua_State* L, const std::string& name, const Args... args) {
lua_getglobal(L, name.c_str());
luaL_checktype(L, -1, LUA_TFUNCTION);
const int size = pushArgs(L, args...);
if (lua_pcall(L, size, LuaValue<R>::size(), 0) != 0) {
lua_pushfstring(L, "Error running function %s:\n", name.c_str());
lua_insert(L, -2);
lua_concat(L, 2);
lua_error(L);
}
R ret = LuaValue<R>::getStackValue(L, -1);
lua_pop(L, LuaValue<R>::size());
return ret;
}
The procedure is simple: first we load the function on the stack and verify that it is really a function. Then we use recursion on the pack of variable argument to pushall arguments on the stack. The pushArgs function returns the number of values that where pushed and we pass this value to lua_pcall. At the end we must restore the stack to its previous state and return the the value returned by Lua. For each type we must define a specialization of the helper struct LuaValue. To call the function "add" as above we must add a specialization for int:
template<> struct LuaValue<int> {
static int getStackValue(lua_State* L, int pos) {
return luaL_checkinteger(L, pos);
}
static void pushValue(lua_State* L, int value) {
lua_pushinteger(L, value);
}
static int size() { return 1; }
};
A specialization of LuaValue must define three functions: a function to push thevalue on the stack, a function to retrieve a value on the stack and a function that returns the number of stack positions that this value takes.
So far so good, but let's say that we want to call a function with no return values:
// Lua
function log(severity, message)
--do something with the arguments
return --return nothing
end
// C++
callFunc<void>(L, "log", WARNING, "corrupting your filesytem...");
What happens is that this code fails to compile because we allocated a temporary value of type R on the stack which, in this case is void. The reason we had to create this temporary was that we had to pop the returned values from the stack. This must be done after we retrieve the return values but before actually returning.The solution that comes to mind is to use RAII to clear the stack:
struct popper {
popper(lua_State* L, int size = 1)
: m_L(L)
, m_size(size) {}
~popper() {
if (m_size > 0) lua_pop(m_L, m_size);
}
private:
lua_State* m_L;
int m_size;
};
// ... end of callFunc:
popper p(L, LuaValue<R>::size());
return LuaValue<R>::getStackValue(L, -1);
}
With this little trick we will be able to return a "void value". As you have surely guessed, one piece is still missing: the LuaValue for void:template<> struct LuaValue<void> {
static void getStackValue(lua_State* L, int pos) {}
static int size() { return 0; }
};
The implementation for pushValue was intentionally left out, so if you try to use void as argument type your code won't compile.Before we finish, lets consider one more case:
function div(a , b)
return a/b, a%b
end
In Lua, a function can return more than one value so how can our generic C++ call function handle it? We return the values as a std::tuple! With the new variadic templates C++11 introduced a this new container in the standard library. The change required is only to add an appropriate overload for std::tuple:
template<class... T> struct LuaValue<std::tuple<T...> > {
template<class Tuple, int I, int N>
struct helper {
static void pushValue(lua_State* L, const Tuple& tuple) {
LuaValue< typename std::tuple_element< I, Tuple >::type >
::pushValue(L, std::get<I>(tuple));
helper<Tuple, I+1, N>::pushValue(L, tuple);
}
static void getValue(lua_State* L, Tuple& tuple, int pos) {
std::get<I>(tuple) =
LuaValue< typename std::tuple_element<I, Tuple>::type>
::getStackValue(L, pos);
helper<Tuple, I+1, N>::getValue(L, tuple, ++pos);
}
};
template<class Tuple, int N>
struct helper<Tuple, N, N> {
static void pushValue(lua_State*, const Tuple&) {}
static void getValue(lua_State*, Tuple&, int) {}
};
typedef std::tuple<T...> TType;
static TType getStackValue(lua_State* L, int pos) {
TType ret;
helper<TType, 0, std::tuple_size<TType>::value>
::getValue(L, ret, pos-size()+1);
return ret;
}
static void pushValue(lua_State* L, const TType& tuple) {
helper<TType, 0, std::tuple_size<TType>::value>
::pushValue(L, tuple);
}
static int size() { return std::tuple_size<TType>::value; }
};
Ok, I admit, this was quite a bit of template trickery but in the end it all boils down to this: the pushValue function pushes every value in the tuple to the stack and the getStackValue function retrieves a value from the stack for each position in the tuple. So now we can call the div funtion like this:int result, remainder; std::tie(result, remainder) = callFunc(L, "div", 42, 3);With this scaffolding in place it is easy to add new types to pass as parameters or return values. The source can be found here. The are specializations of LuaValue for collections like std::vector and std::map.
Nenhum comentário:
Postar um comentário