#define SOL_CHECK_ARGUMENTS 1 #include #include struct thing { int member_variable = 5; double member_function() const { return member_variable / 2.0; } }; #define TEMPLATE_AUTO(x) decltype(x), x // cheap storage: in reality, you'd need to find a // better way of handling this. // or not! It's up to you. static std::unordered_map thing_function_associations; static std::unordered_map thing_variable_associations; void register_thing_type(sol::state& lua) { thing_variable_associations.emplace_hint(thing_variable_associations.cend(), "member_variable", sol::object(lua.lua_state(), sol::in_place, &sol::c_call) ); thing_function_associations.emplace_hint(thing_function_associations.cend(), "member_function", sol::object(lua.lua_state(), sol::in_place, &sol::c_call) ); struct call_handler { static int lookup_function(lua_State* L) { sol::stack_object source(L, 1); sol::stack_object key(L, 2); if (!source.is()) { return luaL_error(L, "given an incorrect object for this call"); } sol::optional maybe_svkey = key.as>(); if (maybe_svkey) { { // functions are different from variables // functions, when obtain with the syntax // obj.f, obj.f(), and obj:f() // must return the function itself // so we just push it realy into our target auto it = thing_function_associations.find(*maybe_svkey); if (it != thing_function_associations.cend()) { return it->second.push(L); } } { // variables are different than funtions // when someone does `obj.a`, they expect // this __index call (this lookup function) to // return to them the value itself they're seeing // so we call out lua_CFunction that we serialized earlier auto it = thing_variable_associations.find(*maybe_svkey); if (it != thing_variable_associations.cend()) { // note that calls generated by sol2 // for member variables expect the stack ordering to be // 2(, 3, ..., n) -- value(s) // 1 -- source // so we destroy the key on the stack sol::stack::remove(L, 2, 1); lua_CFunction cf = it->second.as(); return cf(L); } } } return sol::stack::push(L, sol::lua_nil); } static int insertion_function(lua_State* L) { sol::stack_object source(L, 1); sol::stack_object key(L, 2); sol::stack_object value(L, 3); if (!source.is()) { return luaL_error(L, "given an incorrect object for this call"); } // write to member variables, etc. etc... sol::optional maybe_svkey = key.as>(); if (maybe_svkey) { { // variables are different than funtions // when someone does `obj.a`, they expect // this __index call (this lookup function) to // return to them the value itself they're seeing // so we call out lua_CFunction that we serialized earlier auto it = thing_variable_associations.find(*maybe_svkey); if (it != thing_variable_associations.cend()) { // note that calls generated by sol2 // for member variables expect the stack ordering to be // 2(, 3, ..., n) -- value(s) // 1 -- source // so we remove the key value sol::stack::remove(L, 2, 1); lua_CFunction cf = it->second.as(); return cf(L); } else { // write to member variable, maybe override function // if your class allows for it? (void)value; } } // exercise for reader: // how do you override functions on the metatable with // proper syntax, but error when the type is // an "instance" object? } return 0; } }; lua.new_usertype("thing"); sol::table metatable = lua["thing"]; metatable[sol::meta_method::index] = &call_handler::lookup_function; metatable[sol::meta_method::new_index] = &call_handler::insertion_function; } void unregister_thing_type(sol::state&) { thing_function_associations.clear(); thing_variable_associations.clear(); } int main() { std::cout << "=== metatable with custom-built (static) handling ===" << std::endl; sol::state lua; lua.open_libraries(sol::lib::base); // register custom type + storage register_thing_type(lua); lua.script(R"(t = thing.new() print(t.member_variable) print(t:member_function()) t.member_variable = 24 print(t.member_variable) print(t:member_function()) )"); // clear storage unregister_thing_type(lua); std::cout << std::endl; return 0; }