Lua中混合类型的相等运算符

在《Lua 编程(第二版)》的第 13.2 章中指出:

与算术元方法不同,关系元方法不支持混合类型。

与此同时,

Lua 只有在被比较的两个对象共享此元方法时才调用相等元方法。

因此,我在 C 中实现我的库,并希望能够支持以下行为:

a = A()
b = B()
a == b

提供以下内容:

static const struct luaL_Reg mylib_A[] =
{
  { "__eq", my_equal }
  , <more stuff>
  , { NULL, NULL }
};

static const struct luaL_Reg mylib_B[] =
{
  { "__eq", my_equal }
  , <more stuff>
  , { NULL, NULL }
};

但是这似乎不起作用,有没有解决办法?

注:my_equal 可以处理任何参数中类型为 A 和类型为 B 的 userdata。

更新: 注册元表:

luaL_newmetatable(lua, "B");
lua_pushvalue(lua, -1);
lua_setfield(lua, -2, "__index");
luaL_register(lua, NULL, mylib_B);

luaL_newmetatable(lua, "A");
lua_pushvalue(lua, -1);
lua_setfield(lua, -2, "__index");
luaL_register(lua, NULL, mylib_A);

luaL_register(lua, "mylib", mylib); -- 其中 mylib 是一堆静态函数

应用程序代码:

require 'mylib'
a = mylib.new_A()
b = mylib.new_B()
a == b -- __eq 没有被调用
点赞
用户501459
用户501459

__eq元方法应该放在你的元表中,而不是放在__index表中。

在Lua中:

function my_equal(x,y)
    return x.value == y.value
end

A = {}
A.__eq = my_equal

function new_A(value)
    local a = { value = value }
    return setmetatable(a, A)
end

B = {}
B.__eq = my_equal

function new_B(value)
    local b = { value = value }
    return setmetatable(b, B)
end

a = new_A()
b = new_B()
print(a == b) -- __eq is called, result is true

a.value = 5
print(a == b) -- __eq is called, result is false

你所做的是:

myLib_A = {}
myLib_A.__eq = my_equal

A = {}
A.__index = myLib_A

请注意,__eq不在A的元表中,它在一个完全不同的表中,你恰好在不同的、不相关的元方法(__index)中使用它。Lua在尝试解决运算符“==”的相等性时将不会在那里查找。

Lua手册对此进行了详细解释:

“eq”:== 操作。函数 getcomphandler 定义了Lua如何为比较运算符选择元方法。当被比较的两个对象具有相同类型和所选操作的相同元方法时,才会选择元方法。

 function getcomphandler (op1, op2, event)
   if type(op1) ~= type(op2) then
     return nil
   end
   local mm1 = metatable(op1)[event]
   local mm2 = metatable(op2)[event]
   if mm1 == mm2 then
     return mm1
   else
     return nil
   end
 end

"eq" 事件的定义如下:

 function eq_event (op1, op2)
   if type(op1) ~= type(op2) then  -- different types?
     return false   -- different objects
   end
   if op1 == op2 then   -- primitive equal?
     return true   -- objects are equal
   end
   -- try metamethod
   local h = getcomphandler(op1, op2, "__eq")
   if h then
     return (h(op1, op2))
   else
     return false
   end
 end

因此,当 Lua 遇到 result = a == b 时,它将执行以下操作(此操作是在 C 中完成的,在此处使用 Lua 作为伪代码):

-- 操作数的类型相同吗?在我们的情况下,它们都是表:
if type(a) ~= type(b) then
 return false
end

-- 操作数是同一个对象吗?这个比较是在C代码中完成的,所以它不会重新调用相等操作符。
if a ~= b then
 return false
end

-- 操作数是否有相同的“__eq”元方法?
local mm1 = getmetatable(a).__eq
local mm2 = getmetatable(b).__eq
if mm1 ~= mm2 then
 return false
end

-- 为左操作数调用“__eq”元方法(与右相同,没有什么区别)
return mm1(a,b)

您可以看到,这里没有任何路径可以解决 a.__eq,这会通过您的__index元方法解析为 myLib_A

2015-09-17 19:17:26
用户1212010
用户1212010

对于其他遇到相同问题的人:

这是我让 Lua 意识到 my_equal 在两种情况下都是相同函数的唯一方式,从而正确地从 getcomphandler 中返回操作符的。以任何其他方式注册它(包括单独的 luaL_Reg)都不起作用,因为 my_equalluaL_register 时保存在不同的闭包下,我通过仅创建一次闭包来避免这种情况。

// 我们将它复制到下面以确保 Lua 知道它是同一个函数
lua_pushcfunction(lua, my_equal);

luaL_newmetatable(lua, "B");
// 为了清晰起见去掉了 __index
luaL_register(lua, NULL, mylib_B);

// 现在我们单独注册 __eq
lua_pushstring(lua, "__eq");
lua_pushvalue(lua, -3); // 将 my_equal 复制到上面
lua_settable(lua, -3); // 将其注册到 B 的元表下
lua_pop(lua, 1);

luaL_newmetatable(lua, "A");
// 为了清晰起见去掉了 __index
luaL_register(lua, NULL, mylib_A);

lua_pushstring(lua, "__eq");
lua_pushvalue(lua, -3); // 将 my_equal 复制到上面
lua_settable(lua, -3); // 将其注册到 A 的元表下

luaL_register(lua, "mylib", mylib); // 其中 mylib 是一堆静态函数
2015-09-17 21:59:16