3

在 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 类型的用户数据

更新:元表注册:

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
4

2 回答 2

4

编辑:另请参阅对在 C API中实现有特殊警告的任何人的答案。__eq


__eq元方法属于您的元表,而不是表__index

在 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 的元表中,它在一个完全独立的表上,您只是碰巧在不同的、不相关的元方法 ( __index) 中使用它。Lua 在尝试解析相等运算符时不会查看a.

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 作为伪代码):

-- 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)

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

于 2015-09-17T19:17:26.203 回答
2

对于所有将面临同样问题的其他人:
这是我让 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
于 2015-09-17T21:59:16.260 回答