Reputation: 13320
I have a Lua function registered which expects a metatable as parameter and I want to modify the contents of the table in C++ runtime:
int operate(lua_State *L)
{
std::vector<int> values{};
if (auto length = get_length_at_index(L, 1))
{
result.reserve(length);
lua_pushnil(L);
while (lua_next(L, 1))
{
result.push_back(lua_tointeger(L, -1));
lua_pop(L, 1);
}
}
for (auto &v : values)
{
v *= 2;
}
return 0;
}
int main()
{
auto L = luaL_newstate();
luaL_dofile(L, "script.lua");
lua_register(L, "operate", operate);
return 0;
}
The code above reads "lua.script"
which looks like this:
values = {1, 2, 3, 4, 5, 6}
operate(values)
for index = 1, #values do
print(values[index])
end
The expected output is the values from 1 to 6 multiplied by 2, but the output is the unmodified values. Is obvious that this happens because I'm copying the metatable into a std::vector
and modify the copy.
Is there a way to operate from C++ runtime over Lua objects in a way that they reflect the changes applied from C++?
PS: I know that I can return the table as the function return value, but I want to know if there's other options.
Upvotes: 1
Views: 1363
Reputation: 3000
As arguments passed to a function are copied to the callee stack, only passed tables (and userdata) can be mutated. As this is the case in your function, a table wrapper class could be created, which could overload [] to lua_gettable()/lua_settable(), but it's probably not worth it and more like C++ question.
See also https://github.com/ThePhD/sol2/ and more like that. As with most C++ template<T&&> proxy libraries, it is hard to quickly tell if sol2 can do t[i] *= 2
, so ymmw.
Upvotes: 1
Reputation: 1138
Edit: I didn't see that you already knew why. The explanation has been moved to the bottom for future readers.
After the first loop, your table remains on top of the stack, simplifying the following solution. Replace your second loop with:
int count = int(result.size());
for (int i = 0; i < count; ++i) {
lua_pushinteger(L, result[i] * 2);
lua_rawseti(L, -2, i);
}
-2 refers to the lua value below the top of the stack(similarly, -1 refers to the top). When rawseti
is reached your lua stack looks something like:
[-1]: copy of result[i]
[-2]: your values
table
rawseti
pops the value off the top of the stack and stores it in the specified table and index, making the table the top of the stack again.
Note that you can in fact do this within the first loop, but you should avoid making a habit of it because some lua operations cause undefined behavior while iterating, and it further complicates the code. See this post for clarification.
Why does this happen?
In summary, the code writes to C++ memory, but not the lua table.
The first loop iterates, reading each value and adding it to the result
container.
lua_pushnil(L);
while (lua_next(L, 1))
{
result.push_back(lua_tointeger(L, -1));
lua_pop(L, 1);
}
The second loop doubles the value of each element within the C++ container.
for (auto &v : values)
{
v *= 2;
}
Upvotes: 2
Reputation: 10939
You cannot get a reference to a value on the Lua stack. But you can modify the table in-place.
#include <cassert>
#include <lua.hpp>
int operate(lua_State *L)
{
assert(lua_istable(L,1));
int const N = lua_rawlen(L,1);
for (int i = 1; i <= N; ++i)
{
lua_pushinteger(L, i);
lua_gettable(L,1);
int value = lua_tonumber(L,-1);
value *= 2;
lua_pushinteger(L, i);
lua_pushinteger(L, value);
lua_settable(L,1);
}
return 0;
}
int main()
{
auto L = luaL_newstate();
luaL_openlibs(L);
lua_register(L, "operate", operate);
luaL_dofile(L, "script.lua");
}
Upvotes: 1