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

时间:2015-09-17 18:36:41

标签: lua lua-api

In chapter 13.2 of Programming in Lua它声明

  

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

并且同时

  只有当被比较的两个对象共享这个元方法

时,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类

更新: Metatables注册:

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

2 个答案:

答案 0 :(得分:4)

编辑:另请参阅whoever's answer,其中有关于在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

请注意,在{A>的元表中,__eq 不是,它位于一个完全独立的表中,您恰好正在使用另一个不相关的元方法(__index) 。在尝试解析a的等式运算符时,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在此处用作伪代码):

-- 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的路径,该路径将通过您的myLib_A元方法解析为__index

答案 1 :(得分:2)

对于所有其他将面临同样问题的人:
这是我在这两种情况下让Lua意识到my_equal与Lua完全相同的函数,从而从getcomphandler返回正确的运算符的唯一方法。由于luaL_Regmy_equal下的不同闭包下保存,因此以任何其他方式注册它(包括单独的luaL_register)都不起作用,我在此避免仅创建一次闭包。 / p>

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