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

Equality operator on mixed types in Lua

In chapter 13.2 of Programming in Lua据说

Unlike arithmetic metamethods, relational metamethods do not support mixed types.

同时

Lua calls the equality metamethod only when the two objects being compared share this metamethod

所以我正在用 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 的用户数据

更新: 元表注册:

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); -- where mylib is a bunch of static functions

申请代码:

require 'mylib'
a = mylib.new_A()
b = mylib.new_B()
a == b -- __eq is not called

编辑:另请参阅 ,其中有关于在 C API.

中实现 __eq 的特别警告

__eq 元方法属于您的元table,不属于 __index table.

在lua中:

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



A = {} -- luaL_newmetatable(lua, "A");
A.__eq = my_equal

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


B = {} -- luaL_newmetatable(lua, "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 = {} -- luaL_newmetatable(lua, "A");
A.__index = myLib_A

请注意 __eq 在 A 的 metatable 中 不是 ,它在一个完全独立的 table 上,你恰好是使用不同的、不相关的元方法 (__index)。 Lua 在尝试解析 a.

的相等运算符时不会查看那里

Lua 手册对此有详细解释:

"eq": the == operation. The function getcomphandler defines how Lua chooses a metamethod for comparison operators. A metamethod only is selected when both objects being compared have the same type and the same metamethod for the selected operation.

 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

The "eq" event is defined as follows:

 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这里用作伪代码):

-- Are the operands are the same type? In our case they are both tables:
if type(a) ~= type(b) then
 return false
end

-- Are the operands the same object? This comparison is done in C code, so
-- it's not going to reinvoke the equality operator.
if a ~= b then
 return false
end

-- Do the operands have the same `__eq` metamethod?
local mm1 = getmetatable(a).__eq
local mm2 = getmetatable(b).__eq
if mm1 ~= mm2 then
 return false
end

-- Call the `__eq` metamethod for the left operand (same as the right, doesn't really matter)
return mm1(a,b)

您可以看到这里没有导致解析 a.__eq 的路径,它将通过您的 __index 元方法解析为 myLib_A

对于将面临同样问题的所有其他人:
这是我让 Lua 意识到 my_equal 在这两种情况下从 Lua 的角度来看是完全相同的函数并因此从 getcomphandler 返回正确的运算符的唯一方法。以任何其他方式(包括单独的 luaL_Reg)注册它都不起作用,因为 my_equalluaL_register 时被保存在不同的闭包下,我在这里通过只创建一次闭包来避免这种情况。

// we'll copy it further to ensure lua knows that it's the same function
lua_pushcfunction(lua, my_equal);

luaL_newmetatable(lua, "B");
// removed __index for clarity
luaL_register(lua, NULL, mylib_B);

// Now we register __eq separately
lua_pushstring(lua, "__eq");
lua_pushvalue(lua, -3); // Copy my_equal on top
lua_settable(lua, -3); // Register it under B metatable
lua_pop(lua, 1);


luaL_newmetatable(lua, "A");
// removed __index for clarity
luaL_register(lua, NULL, mylib_A);

lua_pushstring(lua, "__eq");
lua_pushvalue(lua, -3); // Copy my_equal on top
lua_settable(lua, -3); // Register it under A metatable

luaL_register(lua, "mylib", mylib); // where mylib is a bunch of static functions