问题
I am writing in C a userdata type for use in Lua. It has some array-type properties and various methods aswell. Right now if u is of this type, I use u:set(k,v)
resp. u:get(k)
to access data and e.g. u:sort()
as method. For this I set __index
to a table containing these methods. Now if I want to access the data using u[k] = v
or u[k]
, I need to set __newindex
and __index
to set
resp get
. But then the other methods are no longer accessible...
What's the best way to deal with this in C? I am guessing I need to write a function in C to register as __index
and somehow deal with it there. Maybe check if key belongs to a Lua table of methods and if so call it.
Any help/hints would be appreciated. I did not find examples like this, although it seems a very natural thing to do (to me.)
edit: Added my C version of the solution in Lua posted in the answer below. This is more or less a direct translation, so all credit goes to @gilles-gregoire .
The following C function is registered as __index metamethod.
static int permL_index(lua_State *L) {
struct perm **pp = luaL_checkudata(L, 1, PERM_MT);
int i;
luaL_getmetatable(L, PERM_MT);
lua_pushvalue(L, 2);
lua_rawget(L, -2);
if ( lua_isnil(L, -1) ) {
/* found no method, so get value from userdata. */
i = luaL_checkint(L, 2);
luaL_argcheck(L, 1 <= i && i <= (*pp)->n, 2, "index out of range");
lua_pushinteger(L, (*pp)->v[i-1]);
};
return 1;
};
This is the code that does that,
int luaopen_perm(lua_State *L) {
luaL_newmetatable(L, PERM_MT);
luaL_setfuncs(L, permL_methods, 0);
luaL_setfuncs(L, permL_functions, 0);
lua_pop(L, 1);
luaL_newlib(L, permL_functions);
return 1;
};
where permL_methods
is
static const struct luaL_Reg permL_methods[] = {
{ "__index", permL_index },
{ "__eq", permL_equal },
{ "__tostring", permL_tostring },
{ "__gc", permL_destroy },
[...]
{ NULL, NULL }
};
and permL_functions
is
static const struct luaL_Reg permL_functions[] = {
{ "inverse", permL_new_inverse },
{ "product", permL_new_product },
{ "composition", permL_new_composition },
[...]
{ NULL, NULL }
};
回答1:
This looks like a problem which can be solved with nested metatables. You need one metatable for the methods (like your sort() method), and a second one for index operations. That second metatable is actually the metatable of the methods metatable.
Let me write this as lua code. You need 3 tables:
-- the userdata object. I'm using a table here,
-- but it will work the same with a C userdata
u = {}
-- the "methods" metatable:
mt = {sort = function() print('sorting...') end}
-- the "operators" metatable:
op_mt = {__index = function() print('get') end}
Now, the tricky part is here: lua will first lookup u
when you will call a method.
If it does not find it, it will lookup in the table pointed by the __index field of u
's metatable... And Lua will repeat the process for that table!
-- first level metatable
mt.__index = mt
setmetatable(u, mt)
-- second level metatable
setmetatable(mt, op_mt)
You can now use your u
like this:
> u:sort()
sorting...
> = u[1]
get
nil
EDIT: a better solution by using a function for the __index metamethod
Using a function for the __index metamethod is probably the right way to this:
u = {}
mt = {sort = function() print('sorting...') end}
setmetatable(u, mt)
mt.__index = function(t, key)
-- use rawget to avoid recursion
local mt_val = rawget(mt, key)
if mt_val ~=nil then
return mt_val
else
print('this is a get on object', t)
end
end
Usage:
> print(u)
table: 0x7fb1eb601c30
> u:sort()
sorting...
> = u[1]
this is a get on object table: 0x7fb1eb601c30
nil
>
来源:https://stackoverflow.com/questions/26970316/lua-userdata-array-access-and-methods