diff --git a/headers/meta.hpp/meta_value.hpp b/headers/meta.hpp/meta_value.hpp index bfef1d3..a6e6bc8 100644 --- a/headers/meta.hpp/meta_value.hpp +++ b/headers/meta.hpp/meta_value.hpp @@ -8,6 +8,34 @@ #include "meta_fwd.hpp" +namespace meta_hpp +{ + class value; +} + +namespace meta_hpp::value_detail +{ + struct traits { + bool(*equal)(const value&, const value&){}; + bool(*not_equal)(const value&, const value&){}; + }; + + template < typename T > + bool value_equal(const value& l, const value& r); + + template < typename T > + bool value_not_equal(const value& l, const value& r); + + template < typename T > + const traits* get_traits() noexcept { + static traits traits{ + &value_equal, + &value_not_equal, + }; + return &traits; + } +} + namespace meta_hpp { class value { @@ -25,7 +53,8 @@ namespace meta_hpp , typename = std::enable_if_t> > value(T&& val) : raw_{std::forward(val)} - , fid_{get_type_family_id()} {} + , fid_{get_type_family_id()} + , traits_{value_detail::get_traits()} {} template < typename T , typename U = std::decay_t @@ -38,12 +67,14 @@ namespace meta_hpp template < typename T, typename... Args > explicit value(std::in_place_type_t, Args&&... args) : raw_{std::in_place_type, std::forward(args)...} - , fid_{get_type_family_id()} {} + , fid_{get_type_family_id()} + , traits_{value_detail::get_traits()} {} template < typename T, typename U, typename... Args > explicit value(std::in_place_type_t, std::initializer_list ilist, Args&&... args) : raw_{std::in_place_type, ilist, std::forward(args)...} - , fid_{get_type_family_id()} {} + , fid_{get_type_family_id()} + , traits_{value_detail::get_traits()} {} template < typename T > T cast() && { @@ -80,6 +111,51 @@ namespace meta_hpp using std::swap; swap(raw_, other.raw_); swap(fid_, other.fid_); + swap(traits_, other.traits_); + } + + template < typename T + , typename U = std::decay_t + , typename = std::enable_if_t> + , typename = std::enable_if_t, U, U>> > + friend bool operator==(const value& l, T&& r) { + return l.fid() == get_type_family_id() + && std::equal_to<>{}(*l.try_cast(), std::forward(r)); + } + + template < typename T + , typename U = std::decay_t + , typename = std::enable_if_t> + , typename = std::enable_if_t, U, U>> > + friend bool operator==(T&& l, const value& r) { + return get_type_family_id() == r.fid() + && std::equal_to<>{}(std::forward(l), *r.try_cast()); + } + + template < typename T + , typename U = std::decay_t + , typename = std::enable_if_t> + , typename = std::enable_if_t, U, U>> > + friend bool operator!=(const value& l, T&& r) { + return l.fid() != get_type_family_id() + || std::not_equal_to<>{}(*l.try_cast(), std::forward(r)); + } + + template < typename T + , typename U = std::decay_t + , typename = std::enable_if_t> + , typename = std::enable_if_t, U, U>> > + friend bool operator!=(T&& l, const value& r) { + return get_type_family_id() != r.fid() + || std::not_equal_to<>{}(std::forward(l), *r.try_cast()); + } + + friend bool operator==(const value& l, const value& r) { + return l.traits_->equal(l, r); + } + + friend bool operator!=(const value& l, const value& r) { + return l.traits_->not_equal(l, r); } public: bool to_bool() const { return cast(); } @@ -105,9 +181,37 @@ namespace meta_hpp private: std::any raw_; family_id fid_; + const value_detail::traits* traits_{}; }; inline void swap(value& l, value& r) noexcept { l.swap(r); } } + +namespace meta_hpp::value_detail +{ + template < typename T > + bool value_equal(const value& l, const value& r) { + if ( l.fid() != r.fid() ) { + return false; + } + if constexpr ( std::is_invocable_v, T, T> ) { + return std::equal_to<>{}(*l.try_cast(), *(r.try_cast())); + } else { + return std::addressof(l) == std::addressof(r); + } + } + + template < typename T > + bool value_not_equal(const value& l, const value& r) { + if ( l.fid() != r.fid() ) { + return true; + } + if constexpr ( std::is_invocable_v, T, T> ) { + return std::not_equal_to<>{}(*l.try_cast(), *(r.try_cast())); + } else { + return std::addressof(l) != std::addressof(r); + } + } +} diff --git a/untests/meta_value_tests.cpp b/untests/meta_value_tests.cpp index cd7363e..ffaa7b6 100644 --- a/untests/meta_value_tests.cpp +++ b/untests/meta_value_tests.cpp @@ -14,6 +14,8 @@ namespace { class clazz {}; class clazz2 {}; + + void func() {} } TEST_CASE("meta/value") { @@ -61,6 +63,110 @@ TEST_CASE("meta/value/fid") { CHECK(meta::value{std::in_place_type>, {1,2,3,4}}.fid() == meta::get_type_family_id>()); } +TEST_CASE("meta/value/equal") { + namespace meta = meta_hpp; + using namespace std::string_literals; + + { + CHECK(meta::value{1} == 1); + CHECK(meta::value{1} != 0); + CHECK(meta::value{1} != "hello"s); + + CHECK_FALSE(meta::value{1} != 1); + CHECK_FALSE(meta::value{1} == 0); + CHECK_FALSE(meta::value{1} == "hello"s); + } + + { + CHECK(1 == meta::value{1}); + CHECK(0 != meta::value{1}); + CHECK("hello"s != meta::value{1}); + + CHECK_FALSE(1 != meta::value{1}); + CHECK_FALSE(0 == meta::value{1}); + CHECK_FALSE("hello"s == meta::value{1}); + } + + { + int i = 1; + + CHECK(meta::value{1} == i); + CHECK(meta::value{1} == std::as_const(i)); + CHECK_FALSE(meta::value{1} != i); + CHECK_FALSE(meta::value{1} != std::as_const(i)); + + CHECK(i == meta::value{1}); + CHECK(std::as_const(i) == meta::value{1}); + CHECK_FALSE(i != meta::value{1}); + CHECK_FALSE(std::as_const(i) != meta::value{1}); + } + + { + CHECK(meta::value{1} == meta::value{1}); + CHECK(meta::value{1} != meta::value{0}); + CHECK(meta::value{1} != meta::value{"hello"s}); + CHECK_FALSE(meta::value{1} != meta::value{1}); + CHECK_FALSE(meta::value{1} == meta::value{0}); + CHECK_FALSE(meta::value{1} == meta::value{"hello"s}); + + CHECK(meta::value{1} == meta::value{1}); + CHECK(meta::value{0} != meta::value{1}); + CHECK(meta::value{"hello"s} != meta::value{1}); + CHECK_FALSE(meta::value{1} != meta::value{1}); + CHECK_FALSE(meta::value{0} == meta::value{1}); + CHECK_FALSE(meta::value{"hello"s} == meta::value{1}); + } + + { + CHECK(meta::value{std::in_place_type, 1} == meta::value{1}); + CHECK(meta::value{1} == meta::value{std::in_place_type, 1}); + + using vi = std::vector; + CHECK(meta::value{std::in_place_type, {1,2,3}} == meta::value{vi{1,2,3}}); + CHECK(meta::value{vi{1,2,3}} == meta::value{std::in_place_type, {1,2,3}}); + CHECK_FALSE(meta::value{std::in_place_type, {1,2,3}} == meta::value{vi{1,2,4}}); + CHECK_FALSE(meta::value{vi{1,2,4}} == meta::value{std::in_place_type, {1,2,3}}); + } + + { + meta::value v1{&func}; + meta::value v2{&func}; + + CHECK(v1 == v1); + CHECK_FALSE(v1 != v1); + + CHECK(v1 == v2); + CHECK_FALSE(v1 != v2); + } + + { + meta::value v1{clazz{}}; + meta::value v2{clazz{}}; + + CHECK(v1 == v1); + CHECK_FALSE(v1 != v1); + + CHECK(v1 != v2); + CHECK_FALSE(v1 == v2); + } + + { + const int rv = 1; + + meta::value v1{&rv}; + meta::value v2{&rv}; + + CHECK(v1 == v1); + CHECK_FALSE(v1 != v1); + } + + { + meta::value v1{"hello"s}; + meta::value{1}.swap(v1); + CHECK(v1 == meta::value{1}); + } +} + TEST_CASE("meta/value/swap") { namespace meta = meta_hpp; using namespace std::string_literals;