3

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
whoever
  • 575
  • 4
  • 18

2 Answers2

4

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.

Community
  • 1
  • 1
Mud
  • 28,277
  • 11
  • 59
  • 92
  • Yeah, thanks for clarifying it out, I really did expect it to check the __index'ed table. But now there seems to be something more with it: I restructured the code to self-assign __index of A to A itself and register all functions in A (so it goes like luaL_newmetatable, luaL_register, lua_setfield(L, -1, "__index") now), this works between instance_of_A_1 == instance_of_A_2, but still doesn't get called on instance_of_A == instance_of_B, any ideas? – whoever Sep 17 '15 at 20:27
  • new_A and new_B both create new userdata and assign the appropriate metatables to them via luaL_getmetatable, lua_setmetatable – whoever Sep 17 '15 at 20:28
  • Additionaly, getmetatable(a)["__eq"](a, b) calls the appropriate method, getmetatable(b)["__eq"](a, b) calls the appropriate method, but getmetatable(a)["__eq"] == getmetatable(window)["__eq"] evaluates to false! (which I suppose to be a reason why it doesn't get called) I'm somewhat in a loss here, any thoughts? – whoever Sep 17 '15 at 20:50
  • Don't know why you're mentioning `__index` still. It's not relevant to the `__eq` problem so it only distracts. Make sure you're not trying to resolve the metamethod through a metamethod. `__eq` should be a field directly on your metatable. `getmetatable(window)` has no meaning to me. It's the first time you've mentioned `window`. – Mud Sep 17 '15 at 21:53
  • `window` states for `b` there, only noticed it after 5 minute deadline on comment editing. I actually managed to track down the original problem thanks to your explanation of `getcomphandler` implementation. I'm not even sure whether there's a need to switch the accepted answer than, could you just perhaps link to mine's in the begining so that others will find it? – whoever Sep 17 '15 at 22:24
  • I don't mind if you switch answers. I don't care about StackOverflow points. But, sure, I'll link to yours. – Mud Sep 18 '15 at 06:53
2

For all others who would be facing the same problem:
This was the only way how I made Lua aware of my_equal being the exact same function from the point of Lua in both cases and consequently returning correct operator from getcomphandler. Registering it in any other way (including separate luaL_Reg) doesn't work due to my_equal being saved under different closures upon luaL_register, which I here avoid by creating the closure only once.

// 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
whoever
  • 575
  • 4
  • 18