读取 Lua 对象(元表?)作为 std::tuple。

huangapple go评论112阅读模式
英文:

Read lua object (metatable?) as std::tuple

问题

我有一些函数可以从 Lua(5.4)堆栈中读取元组,假设堆栈包含一个 Lua 表:

template <typename type_t>
type_t stack_value_as(lua_State *const L, int index)
{
	// 在这里进行模板魔法...
	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 = [&](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{};
}

当我使用下面的 Lua 脚本测试这段代码时,它失败了:

test = {e = 42, n = 3.1415, id = "test, test, 1, 2, 3..."};

-- 调用 1:成功
tup({1, 2.3456, "789"})
-- 调用 2 和 3:lua_istable 返回 true,lua_rawlen 返回 0
tup(test)
tup({e = 42, n = 3.1415, id = "test, test, 1, 2, 3..."})

函数 tup 绑定到一个接收 tuple<int, double, string> 参数的 C++ 函数。当堆栈包含一个 Lua 对象时,lua_istable 函数返回真,但 lua_rawlen 返回 0。我猜测在这种情况下,内容是一个 Lua 元表。我该如何将这些 Lua 对象(元表?)读取为 std::tuple

英文:

I have some functions that can read tuples from Lua (5.4) stack, assuming that the stack contains a Lua table:

template &lt;typename type_t&gt;
type_t stack_value_as(lua_State *const L, int index)
{
	// template magic happens here…
	return {};
}

template &lt;typename type_t&gt;
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&lt;type_t&gt;(L, -1);
	lua_pop(L, 1);
	return result;
}

template &lt;typename ... type_pack&gt;
auto read_tuple(lua_State *L, int index)
{
	using tuple_t = std::tuple&lt;type_pack ...&gt;;

	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 = [&amp;]&lt;int ... index_pack&gt;(const std::integer_sequence&lt;int, index_pack ...&gt; &amp;)
			{
				return tuple_t{ table_value_at&lt;type_pack&gt;(L, index, index_pack - index) ... };
			};
			return helper(std::make_integer_sequence&lt;int, size_cpp&gt;{});
		}
	}

	return tuple_t{};
}

When I test this code with the Lua script below it fails:

test = {e = 42, n = 3.1415, id = &quot;test, test, 1, 2, 3...&quot;};

-- call 1: ok
tup({1, 2.3456, &quot;789&quot;})
-- call 2 &amp; 3: lua_istable is true, lua_rawlen is 0
tup(test)
tup({e = 42, n = 3.1415, id = &quot;test, test, 1, 2, 3...&quot;})

The function tup is tied to a C++ function that receives tuple&lt;int, double, string&gt; 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?

答案1

得分: 1

关于 lua_rawlen == 0

在这段代码中,根据Lua 5.4参考手册lua_rawlen == 0是正确的。

根据lua_rawlen的说明:
> 返回给定索引处值的原始“长度”:[...];对于表,这是没有元方法的长度运算符('#')的结果;

根据长度运算符的说明:

> 应用于表的长度运算符返回表中的边界。表t中的边界是满足以下条件的任何非负整数:
>
> (border == 0 or t[border] ~= nil) and
> (t[border + 1] == nil or border == math.maxinteger)
>
> 简单来说,边界是表中存在的正整数索引,其后紧跟一个不存在的索引,还有两个特殊情况:当索引1不存在时,边界为0;当索引为整数的最大值时,边界为最大整数值。请注意,非正整数的键不会影响边界。

在我们的例子中,对于任何整数itest[i] == nil,因此0是该表的唯一边界。结论:#test == 0

简而言之,一般情况下,长度运算符不会计算Lua表的键值对数量。它在{1, 2.3456, "789"}这种情况下可以计算,因为它是一个数组:所有的键都是从1开始的连续整数。在其他情况下,长度运算符没有太多实际价值。

tup({1, 2.3456, "789"}) vs tup(test)

根据test的声明方式,所有的键值对都具有字符串键。一个等价的声明方式是:

# 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..."

这与具有整数键的表不同:

# {1, 2.3456, "789"}
equivalent = {}
equivalent[1] = 1
equivalent[2] = 2.3456
equivalent[3] = "789"

在所有情况下获取正确的键值对数量是一个简单的修复。然而,对于元组转换来说存在一个更大的问题。从根本上讲,Lua表类似于C++的std::unordered_map。在{e = 42, n = 3.14, id = "test"}中,键值对没有固定的顺序:("e",42)不是第一个键值对,("n",3.14)也不是第二个。如果在C++函数中迭代Lua表的所有键值对,可能会以不同的顺序获取它们。你需要对字符串键进行一些逻辑处理,以获得元组元素的一致顺序。

英文:

About lua_rawlen == 0

test = {e = 42, n = 3.1415, id = &quot;test, test, 1, 2, 3...&quot;};
-- call 2 &amp; 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, &quot;789&quot;} 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.

tup({1, 2.3456, "789"}) vs tup(test)

The way test was declared, all (key/value) pairs have string keys. An equivalent declaration would be:

# test = {e = 42, n = 3.1415, id = &quot;test, test, 1, 2, 3...&quot;}
test = {}
test[&#39;e&#39;] = 42
test[&#39;n&#39;] = 3.1415
test[&#39;id&#39;] = &quot;test, test, 1, 2, 3...&quot;

Which is different from a table with integer keys:

# {1, 2.3456, &quot;789&quot;}
equivalent = {}
equivalent[1] = 1
equivalent[2] = 2.3456
equivalent[3] = &quot;789&quot;

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 = &quot;test&quot;} the pairs have no inherent order: (&quot;e&quot;,42) isn't the 1st pair, nor is (&quot;n&quot;,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.

huangapple
  • 本文由 发表于 2023年8月9日 17:43:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76866479.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定