In chapter 13.2 of Programming in Lua it's stated that
Unlike arithmetic metamethods, relational metamethods do not support mixed types.
and at the same time
Lua calls the equality metamethod only when the two objects being compared share this metamethod
So I'm implementing my library in C and want to be able to support behavior like
a = A()
b = B()
a == b
by providing
static const struct luaL_Reg mylib_A[] =
{
{ "__eq", my_equal }
, <more stuff>
, { NULL, NULL }
};
and
static const struct luaL_Reg mylib_B[] =
{
{ "__eq", my_equal }
, <more stuff>
, { NULL, NULL }
};
Which doesn't seem to work, is there a workaround for this?
Note: my_equal is able to handle both userdata of type A and type B in any of it's arguments
UPDATE: Metatables registration:
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
Application code:
require 'mylib'
a = mylib.new_A()
b = mylib.new_B()
a == b -- __eq is not called
EDIT: Also see whoever's answer which has a particular caveat with regards to implementing __eq
in the C API.
The __eq
metamethod belongs in your metatable, not in the __index
table.
In 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
What you have done is this:
myLib_A = {}
myLib_A.__eq = my_equal
A = {} -- luaL_newmetatable(lua, "A");
A.__index = myLib_A
Note that __eq
is not in A's metatable, it's on a totally separate table that you just be happen to be using in a different, unrelated metamethod (__index
). Lua is not going to look there when trying to resolve the equality operator for a
.
The Lua manual explains this in detail:
"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
So when Lua encounters result = a == b
, it's going to do the following (this is done in C, Lua used as pseudocode here):
-- 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)
You can see there's no path here that results in resolve a.__eq
, which would resolve to myLib_A
through your __index
metamethod.