diff --git a/docs/source/api/usertype.rst b/docs/source/api/usertype.rst index db746c49..5847100e 100644 --- a/docs/source/api/usertype.rst +++ b/docs/source/api/usertype.rst @@ -187,6 +187,16 @@ If you don't specify anything at all and the type is `destructible`_, then a des usertype regular function options +++++++++++++++++++++++++++++++++ +If you don't specify anything at all and the type ``T`` supports ``operator <``, ``operator <=``, or ``operator==`` (``const`` or non-``const`` qualified): + +* for ``operator <`` and ``operator <=`` + - These two ``sol::meta_function::less_than(_or_equal_to)`` are generated for you and overriden in Lua. +* for ``operator==`` + - An equality operator will always be generated, doing pointer comparison if ``operator==`` on the two value types is not supported or doing a reference comparison and a value comparison if ``operator==`` is supported +* heterogenous operators cannot be supported for equality, as Lua specifically checks if they use the same function to do the comparison: if they do not, then the equality method is not invoked; one way around this would be to write one ``int super_equality_function(lua_State* L) { ... }``, pull out arguments 1 and 2 from the stack for your type, and check all the types and then invoke ``operator==`` yourself after getting the types out of Lua (possibly using :ref:`sol::stack::get<stack-get>` and :ref:`sol::stack::check_get<stack-check-get>`) + +Otherwise, the following is used to specify functions to bind on the specific usertype for ``T``. + * ``"{name}", &free_function`` - Binds a free function / static class function / function object (lambda) to ``"{name}"``. If the first argument is ``T*`` or ``T&``, then it will bind it as a member function. If it is not, it will be bound as a "static" function on the lua table * ``"{name}", &type::function_name`` or ``"{name}", &type::member_variable`` @@ -209,7 +219,7 @@ usertype arguments - simple usertype * ``sol::simple`` - Only allowed as the first argument to the usertype constructor, must be accompanied by a ``lua_State*`` - This tag triggers the :doc:`simple usertype<simple_usertype>` changes / optimizations - - Only supported when directly invoking the constructor (e.g. not when calling ``sol::table::new_usertype`` or ``sol::table::new_simple_usertype``) + - Only supported when directly invoking the constructor (e.g. not when calling ``sol::table::new_usertype`` or ``sol::table::new_simple_usertype``) diff --git a/docs/source/conf.py b/docs/source/conf.py index 098d3639..b8d77038 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -61,7 +61,7 @@ author = 'ThePhD' # The short X.Y version. version = '2.12' # The full version, including alpha/beta/rc tags. -release = '2.12.0' +release = '2.12.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/sol/container_usertype_metatable.hpp b/sol/container_usertype_metatable.hpp index 8ac22d9f..9a958d5a 100644 --- a/sol/container_usertype_metatable.hpp +++ b/sol/container_usertype_metatable.hpp @@ -67,17 +67,17 @@ namespace sol { } - template <typename T, typename C = void> + template <typename Raw, typename C = void> struct container_usertype_metatable { - typedef meta::unqualified_t<T> U; + typedef meta::unqualified_t<Raw> T; typedef std::size_t K; - typedef typename U::value_type V; - typedef typename U::iterator I; + typedef typename T::value_type V; + typedef typename T::iterator I; struct iter { - U& source; + T& source; I it; - iter(U& source, I it) : source(source), it(std::move(it)) {} + iter(T& source, I it) : source(source), it(std::move(it)) {} }; static auto& get_src(lua_State* L) { @@ -224,18 +224,18 @@ namespace sol { } }; - template <typename T> - struct container_usertype_metatable<T, std::enable_if_t<meta::has_key_value_pair<T>::value>> { - typedef meta::unqualified_t<T> U; - typedef typename U::value_type KV; + template <typename Raw> + struct container_usertype_metatable<Raw, std::enable_if_t<meta::has_key_value_pair<meta::unqualified_t<Raw>>::value>> { + typedef meta::unqualified_t<Raw> T; + typedef typename T::value_type KV; typedef typename KV::first_type K; typedef typename KV::second_type V; - typedef typename U::iterator I; + typedef typename T::iterator I; struct iter { - U& source; + T& source; I it; - iter(U& source, I it) : source(source), it(std::move(it)) {} + iter(T& source, I it) : source(source), it(std::move(it)) {} }; static auto& get_src(lua_State* L) { diff --git a/sol/simple_usertype_metatable.hpp b/sol/simple_usertype_metatable.hpp index eaa2f942..127be267 100644 --- a/sol/simple_usertype_metatable.hpp +++ b/sol/simple_usertype_metatable.hpp @@ -110,6 +110,9 @@ namespace sol { typedef simple_usertype_metatable<T> umt_t; static int push(lua_State* L, umt_t&& umx) { + bool hasequals = false; + bool hasless = false; + bool haslessequals = false; for (std::size_t i = 0; i < 3; ++i) { // Pointer types, AKA "references" from C++ const char* metakey = nullptr; @@ -128,25 +131,53 @@ namespace sol { luaL_newmetatable(L, metakey); stack_reference t(L, -1); for (auto& kvp : umx.registrations) { - switch (i) { - case 0: - if (kvp.first.template is<std::string>() && kvp.first.template as<std::string>() == "__gc") { - continue; + if (kvp.first.template is<std::string>()) { + std::string regname = kvp.first.template as<std::string>(); + if (regname == name_of(meta_function::equal_to)) { + hasequals = true; } - break; - case 1: - if (kvp.first.template is<std::string>() && kvp.first.template as<std::string>() == "__gc") { - stack::set_field(L, kvp.first, detail::unique_destruct<T>, t.stack_index()); - continue; + else if (regname == name_of(meta_function::less_than)) { + hasless = true; + } + else if (regname == name_of(meta_function::less_than_or_equal_to)) { + haslessequals = true; + } + switch (i) { + case 0: + if (regname == name_of(meta_function::garbage_collect)) { + continue; + } + break; + case 1: + if (regname == name_of(meta_function::garbage_collect)) { + stack::set_field(L, kvp.first, detail::unique_destruct<T>, t.stack_index()); + continue; + } + break; + case 2: + default: + break; } - break; - case 2: - default: - break; } stack::set_field(L, kvp.first, kvp.second, t.stack_index()); } - + luaL_Reg opregs[4]{}; + int opregsindex = 0; + if (!hasless) { + const char* name = name_of(meta_function::less_than).c_str(); + usertype_detail::make_reg_op<T, std::less<>, meta::supports_op_less<T>>(opregs, opregsindex, name); + } + if (!haslessequals) { + const char* name = name_of(meta_function::less_than_or_equal_to).c_str(); + usertype_detail::make_reg_op<T, std::less_equal<>, meta::supports_op_less_equal<T>>(opregs, opregsindex, name); + } + if (!hasequals) { + const char* name = name_of(meta_function::equal_to).c_str(); + usertype_detail::make_reg_op<T, std::conditional_t<meta::supports_op_equal<T>::value, std::equal_to<>, usertype_detail::no_comp>, std::true_type>(opregs, opregsindex, name); + } + t.push(); + luaL_setfuncs(L, opregs, 0); + t.pop(); // Metatable indexes itself stack::set_field(L, meta_function::index, t, t.stack_index()); diff --git a/sol/traits.hpp b/sol/traits.hpp index e2b44fa1..8d7195fd 100644 --- a/sol/traits.hpp +++ b/sol/traits.hpp @@ -262,37 +262,54 @@ namespace sol { static const bool value = sizeof(test<Derived>(0)) == sizeof(yes); }; + struct has_begin_end_impl { + template<typename T, typename U = unqualified_t<T>, + typename B = decltype(std::declval<U&>().begin()), + typename E = decltype(std::declval<U&>().end())> + static std::true_type test(int); + + template<typename...> + static std::false_type test(...); + }; + + struct has_key_value_pair_impl { + template<typename T, typename U = unqualified_t<T>, + typename V = typename U::value_type, + typename F = decltype(std::declval<V&>().first), + typename S = decltype(std::declval<V&>().second)> + static std::true_type test(int); + + template<typename...> + static std::false_type test(...); + }; + + template <typename T, typename U = T, typename = decltype(std::declval<T&>() < std::declval<U&>())> + std::true_type supports_op_less_test(const T&); + std::false_type supports_op_less_test(...); + template <typename T, typename U = T, typename = decltype(std::declval<T&>() == std::declval<U&>())> + std::true_type supports_op_equal_test(const T&); + std::false_type supports_op_equal_test(...); + template <typename T, typename U = T, typename = decltype(std::declval<T&>() <= std::declval<U&>())> + std::true_type supports_op_less_equal_test(const T&); + std::false_type supports_op_less_equal_test(...); + } // meta_detail + template <typename T> + using supports_op_less = decltype(meta_detail::supports_op_less_test(std::declval<T&>())); + template <typename T> + using supports_op_equal = decltype(meta_detail::supports_op_equal_test(std::declval<T&>())); + template <typename T> + using supports_op_less_equal = decltype(meta_detail::supports_op_less_equal_test(std::declval<T&>())); + template<typename T> struct is_callable : boolean<meta_detail::is_callable<T>::value> {}; - struct has_begin_end_impl { - template<typename T, typename U = unqualified_t<T>, - typename B = decltype(std::declval<U&>().begin()), - typename E = decltype(std::declval<U&>().end())> - static std::true_type test(int); - - template<typename...> - static std::false_type test(...); - }; + template<typename T> + struct has_begin_end : decltype(meta_detail::has_begin_end_impl::test<T>(0)) {}; template<typename T> - struct has_begin_end : decltype(has_begin_end_impl::test<T>(0)) {}; - - struct has_key_value_pair_impl { - template<typename T, typename U = unqualified_t<T>, - typename V = typename U::value_type, - typename F = decltype(std::declval<V&>().first), - typename S = decltype(std::declval<V&>().second)> - static std::true_type test(int); - - template<typename...> - static std::false_type test(...); - }; - - template<typename T> - struct has_key_value_pair : decltype(has_key_value_pair_impl::test<T>(0)) {}; + struct has_key_value_pair : decltype(meta_detail::has_key_value_pair_impl::test<T>(0)) {}; template <typename T> using is_string_constructible = any<std::is_same<unqualified_t<T>, const char*>, std::is_same<unqualified_t<T>, char>, std::is_same<unqualified_t<T>, std::string>, std::is_same<unqualified_t<T>, std::initializer_list<char>>>; diff --git a/sol/usertype_metatable.hpp b/sol/usertype_metatable.hpp index bec8dcb8..dbfea5d3 100644 --- a/sol/usertype_metatable.hpp +++ b/sol/usertype_metatable.hpp @@ -33,8 +33,14 @@ #include "deprecate.hpp" namespace sol { - namespace usertype_detail { + struct no_comp { + template <typename A, typename B> + bool operator()(A&&, B&&) { + return false; + } + }; + inline bool is_indexer(string_detail::string_shim s) { return s == name_of(meta_function::index) || s == name_of(meta_function::new_index); } @@ -88,6 +94,37 @@ namespace sol { return luaL_error(L, "sol: attempt to index (set) nil value \"%s\" on userdata (bad (misspelled?) key name or does not exist)", accessor.data()); } + template <typename T, typename Op> + inline int operator_wrap(lua_State* L) { + auto maybel = stack::check_get<T>(L, 1); + if (maybel) { + auto mayber = stack::check_get<T>(L, 2); + if (mayber) { + auto& l = *maybel; + auto& r = *mayber; + if (std::is_same<no_comp, Op>::value) { + return stack::push(L, detail::ptr(l) == detail::ptr(r)); + } + else { + Op op; + return stack::push(L, (detail::ptr(l) == detail::ptr(r)) || op(detail::deref(l), detail::deref(r))); + } + } + } + return stack::push(L, false); + } + + template <typename T, typename Op, typename Supports, typename Regs, meta::enable<Supports> = meta::enabler> + inline void make_reg_op(Regs& l, int& index, const char* name) { + l[index] = { name, &operator_wrap<T, Op> }; + ++index; + } + + template <typename T, typename Op, typename Supports, typename Regs, meta::disable<Supports> = meta::enabler> + inline void make_reg_op(Regs&, int&, const char*) { + // Do nothing if there's no support + } + struct add_destructor_tag {}; struct check_destructor_tag {}; struct verified_tag {} const verified{}; @@ -136,7 +173,7 @@ namespace sol { struct usertype_metatable<T, std::index_sequence<I...>, Tn...> : usertype_detail::registrar { typedef std::make_index_sequence<sizeof...(I) * 2> indices; typedef std::index_sequence<I...> half_indices; - typedef std::array<luaL_Reg, sizeof...(Tn) / 2 + 1> regs_t; + typedef std::array<luaL_Reg, sizeof...(Tn) / 2 + 1 + 3> regs_t; typedef std::tuple<Tn...> RawTuple; typedef std::tuple<clean_type_t<Tn> ...> Tuple; template <std::size_t Idx> @@ -155,6 +192,9 @@ namespace sol { void* baseclasscast; bool mustindex; bool secondarymeta; + bool hasequals; + bool hasless; + bool haslessequals; template <std::size_t Idx, meta::enable<std::is_same<lua_CFunction, meta::unqualified_tuple_element<Idx + 1, RawTuple>>> = meta::enabler> inline lua_CFunction make_func() { @@ -178,6 +218,18 @@ namespace sol { } int finish_regs(regs_t& l, int& index) { + if (!hasless) { + const char* name = name_of(meta_function::less_than).c_str(); + usertype_detail::make_reg_op<T, std::less<>, meta::supports_op_less<T>>(l, index, name); + } + if (!haslessequals) { + const char* name = name_of(meta_function::less_than_or_equal_to).c_str(); + usertype_detail::make_reg_op<T, std::less_equal<>, meta::supports_op_less_equal<T>>(l, index, name); + } + if (!hasequals) { + const char* name = name_of(meta_function::equal_to).c_str(); + usertype_detail::make_reg_op<T, std::conditional_t<meta::supports_op_equal<T>::value, std::equal_to<>, usertype_detail::no_comp>, std::true_type>(l, index, name); + } if (destructfunc != nullptr) { l[index] = { name_of(meta_function::garbage_collect).c_str(), destructfunc }; ++index; @@ -216,6 +268,15 @@ namespace sol { // Returnable scope // That would be a neat keyword for C++ // returnable { ... }; + if (reg.name == name_of(meta_function::equal_to)) { + hasequals = true; + } + if (reg.name == name_of(meta_function::less_than)) { + hasless = true; + } + if (reg.name == name_of(meta_function::less_than_or_equal_to)) { + haslessequals = true; + } if (reg.name == name_of(meta_function::garbage_collect)) { destructfunc = reg.func; return; @@ -241,7 +302,8 @@ namespace sol { indexbase(&core_indexing_call<true>), newindexbase(&core_indexing_call<false>), indexbaseclasspropogation(walk_all_bases<true>), newindexbaseclasspropogation(walk_all_bases<false>), baseclasscheck(nullptr), baseclasscast(nullptr), - mustindex(contains_variable() || contains_index()), secondarymeta(contains_variable()) { + mustindex(contains_variable() || contains_index()), secondarymeta(contains_variable()), + hasequals(false), hasless(false), haslessequals(false) { } template <std::size_t I0, std::size_t I1, bool is_index> @@ -414,9 +476,9 @@ namespace sol { (void)detail::swallow{ 0, (um.template make_regs<(I * 2)>(value_table, lastreg, std::get<(I * 2)>(um.functions), std::get<(I * 2 + 1)>(um.functions)), 0)... }; um.finish_regs(value_table, lastreg); value_table[lastreg] = { nullptr, nullptr }; - bool hasdestructor = !value_table.empty() && name_of(meta_function::garbage_collect) == value_table[lastreg - 1].name; regs_t ref_table = value_table; regs_t unique_table = value_table; + bool hasdestructor = !value_table.empty() && name_of(meta_function::garbage_collect) == value_table[lastreg - 1].name; if (hasdestructor) { ref_table[lastreg - 1] = { nullptr, nullptr }; unique_table[lastreg - 1] = { value_table[lastreg - 1].name, detail::unique_destruct<T> }; diff --git a/test_operators.cpp b/test_operators.cpp new file mode 100644 index 00000000..b8e48e78 --- /dev/null +++ b/test_operators.cpp @@ -0,0 +1,83 @@ +#define SOL_CHECK_ARGUMENTS + +#include <sol.hpp> +#include <catch.hpp> + +TEST_CASE("operators/default", "test that generic equality operators and all sorts of equality tests can be used") { + sol::state lua; + lua.open_libraries(sol::lib::base); + + struct T {}; + struct U { + int a; + U(int x = 20) : a(x) {} + bool operator==(const U& r) { + return a == r.a; + } + }; + struct V { + int a; + V(int x = 20) : a(x) {} + bool operator==(const V& r) const { + return a == r.a; + } + }; + lua.new_usertype<T>("T"); + lua.new_usertype<U>("U"); + lua.new_usertype<V>("V"); + + T t1; + T& t2 = t1; + T t3; + U u1; + U u2{ 30 }; + U u3; + U v1; + U v2{ 30 }; + U v3; + lua["t1"] = &t1; + lua["t2"] = &t2; + lua["t3"] = &t3; + lua["u1"] = &u1; + lua["u2"] = &u2; + lua["u3"] = &u3; + lua["v1"] = &v1; + lua["v2"] = &v2; + lua["v3"] = &v3; + + // Can only compare identity here + REQUIRE_NOTHROW({ + lua.script("assert(t1 == t1)"); + lua.script("assert(t2 == t2)"); + lua.script("assert(t3 == t3)"); + }); + REQUIRE_NOTHROW({ + lua.script("assert(t1 == t2)"); + lua.script("assert(not (t1 == t3))"); + lua.script("assert(not (t2 == t3))"); + }); + // Object should compare equal to themselves + // (and not invoke operator==; pointer test should be sufficient) + REQUIRE_NOTHROW({ + lua.script("assert(u1 == u1)"); + lua.script("assert(u2 == u2)"); + lua.script("assert(u3 == u3)"); + }); + REQUIRE_NOTHROW({ + lua.script("assert(not (u1 == u2))"); + lua.script("assert(u1 == u3)"); + lua.script("assert(not (u2 == u3))"); + }); + // Object should compare equal to themselves + // (and not invoke operator==; pointer test should be sufficient) + REQUIRE_NOTHROW({ + lua.script("assert(v1 == v1)"); + lua.script("assert(v2 == v2)"); + lua.script("assert(v3 == v3)"); + }); + REQUIRE_NOTHROW({ + lua.script("assert(not (v1 == v2))"); + lua.script("assert(v1 == v3)"); + lua.script("assert(not (v2 == v3))"); + }); +} \ No newline at end of file