I have some functions that can read tuples from Lua (5.4) stack, assuming that the stack contains a Lua table:
template <typename type_t>
type_t stack_value_as(lua_State *const L, int index)
{
// template magic happens here…
return {};
}
template <typename type_t>
type_t table_value_at(lua_State *const L, int table_stack_position, int index)
{
lua_rawgeti(L, table_stack_position, index);
auto result = stack_value_as<type_t>(L, -1);
lua_pop(L, 1);
return result;
}
template <typename ... type_pack>
auto read_tuple(lua_State *L, int index)
{
using tuple_t = std::tuple<type_pack ...>;
if (lua_istable(L, index))
{
const auto size_lua = lua_rawlen(L, index);
constexpr auto size_cpp = sizeof...(type_pack);
if (size_lua == size_cpp)
{
auto helper = [&]<int ... index_pack>(const std::integer_sequence<int, index_pack ...> &)
{
return tuple_t{ table_value_at<type_pack>(L, index, index_pack - index) ... };
};
return helper(std::make_integer_sequence<int, size_cpp>{});
}
}
return tuple_t{};
}
When I test this code with the Lua script below it fails:
test = {e = 42, n = 3.1415, id = "test, test, 1, 2, 3..."};
-- call 1: ok
tup({1, 2.3456, "789"})
-- call 2 & 3: lua_istable is true, lua_rawlen is 0
tup(test)
tup({e = 42, n = 3.1415, id = "test, test, 1, 2, 3..."})
The function tup
is tied to a C++ function that receives tuple<int, double, string>
as parameter. When the stack contains a Lua object the lua_istable
function returns truthy but the lua_rawlen
returns 0
. My guess is that in this case the content is a LUA metatable. How can I read those Lua objects (metatables?) as std::tuple
?
test = {e = 42, n = 3.1415, id = "test, test, 1, 2, 3..."};
-- call 2 & 3: lua_istable is true, lua_rawlen is 0
tup(test)
In this snippet, lua_rawlen == 0
is correct according to Lua 5.4 reference manual.
From lua_rawlen:
Returns the raw "length" of the value at the given index: [...]; for tables, this is the result of the length operator ('#') with no metamethods;
From the length operator:
The length operator applied on a table returns a border in that table. A border in a table t is any non-negative integer that satisfies the following condition:
(border == 0 or t[border] ~= nil) and (t[border + 1] == nil or border == math.maxinteger)
In words, a border is any positive integer index present in the table that is followed by an absent index, plus two limit cases: zero, when index 1 is absent; and the maximum value for an integer, when that index is present. Note that keys that are not positive integers do not interfere with borders.
In our case, test[i] == nil
for any integer i
, so 0 is the only border of this table. Conclusion: #test == 0
.
The TL,DR: in general, the length operator doesn't compute the number of (key/value) pairs of a Lua table. It does so with {1, 2.3456, "789"}
because it is an array: all its keys are consecutive integers starting from 1. In any other case, the length operator doesn't have much practical value.
The way test
was declared, all (key/value) pairs have string keys. An equivalent declaration would be:
# test = {e = 42, n = 3.1415, id = "test, test, 1, 2, 3..."}
test = {}
test['e'] = 42
test['n'] = 3.1415
test['id'] = "test, test, 1, 2, 3..."
Which is different from a table with integer keys:
# {1, 2.3456, "789"}
equivalent = {}
equivalent[1] = 1
equivalent[2] = 2.3456
equivalent[3] = "789"
Getting the correct number of (key/value) pairs in all cases is an easy fix. However for the tuple conversion there is a bigger problem. Fundamentally, Lua tables are similar to c++'s std::unordered_map
. In {e = 42, n = 3.14, id = "test"}
the pairs have no inherent order: ("e",42)
isn't the 1st pair, nor is ("n",3.14)
the 2nd. If you iterate over all (key/value) pairs of a Lua table in your c++ function, you can get them in a different order. You'll have to do some logic with the string keys to get a consistent order on the tuple's elements.