diff --git a/headers/meta.hpp/meta_infos/member_info.hpp b/headers/meta.hpp/meta_infos/member_info.hpp index a3d1a7d..a1413e9 100644 --- a/headers/meta.hpp/meta_infos/member_info.hpp +++ b/headers/meta.hpp/meta_infos/member_info.hpp @@ -21,6 +21,12 @@ namespace meta_hpp const std::string& name() const noexcept; const member_type& type() const noexcept; + + template < typename Instance > + value get(Instance&& instance) const; + + template < typename Instance, typename Value > + void set(Instance&& instance, Value&& value) const; public: template < typename F > void visit(F&& f) const; @@ -40,12 +46,71 @@ namespace meta_hpp }; } +namespace meta_hpp::detail +{ + using member_getter = std::function; + using member_setter = std::function; + + template < typename Member > + value raw_member_getter(Member member, const inst& inst) { + using mt = member_pointer_traits; + using class_type = typename mt::class_type; + using value_type = typename mt::value_type; + + using qualified_type = const class_type; + + if ( !inst.can_cast_to() ) { + throw std::logic_error("an attempt to get a member with an incorrect instance type"); + } + + value_type return_value{std::invoke(member, inst.cast())}; + return value{std::forward(return_value)}; + } + + template < typename Member > + void raw_member_setter(Member member, const inst& inst, const arg& arg) { + using mt = member_pointer_traits; + using class_type = typename mt::class_type; + using value_type = typename mt::value_type; + + using qualified_type = class_type; + + if constexpr ( !std::is_const_v ) { + if ( !inst.can_cast_to() ) { + throw std::logic_error("an attempt to set a member with an incorrect instance type"); + } + + if ( !arg.can_cast_to() ) { + throw std::logic_error("an attempt to set a member with an incorrect argument type"); + } + + std::invoke(member, inst.cast()) = arg.cast(); + } else { + throw std::logic_error("an attempt to set a constant member"); + } + } + + template < typename Member > + member_getter make_member_getter(Member member) { + using namespace std::placeholders; + return std::bind(&raw_member_getter, member, _1); + } + + template < typename Member > + member_setter make_member_setter(Member member) { + using namespace std::placeholders; + return std::bind(&raw_member_setter, member, _1, _2); + } +} + namespace meta_hpp { struct member_info::state final { std::string name; member_type type; data_info_map datas; + detail::member_getter getter; + detail::member_setter setter; }; } @@ -67,6 +132,16 @@ namespace meta_hpp inline const member_type& member_info::type() const noexcept { return state_->type; } + + template < typename Instance > + value member_info::get(Instance&& instance) const { + return state_->getter(inst{std::forward(instance)}); + } + + template < typename Instance, typename Value > + void member_info::set(Instance&& instance, Value&& value) const { + state_->setter(inst{std::forward(instance)}, arg{std::forward(value)}); + } } namespace meta_hpp @@ -91,12 +166,12 @@ namespace meta_hpp namespace meta_hpp { template < typename Member > - member_info::member_info(std::string name, Member instance) + member_info::member_info(std::string name, Member member) : state_{std::make_shared(state{ std::move(name), type_db::get().template as(), - {} - })} { - (void)instance; - } + {}, + detail::make_member_getter(member), + detail::make_member_setter(member), + })} {} } diff --git a/untests/features/infos/member_info_tests.cpp b/untests/features/infos/member_info_tests.cpp index 31db3d2..c43b170 100644 --- a/untests/features/infos/member_info_tests.cpp +++ b/untests/features/infos/member_info_tests.cpp @@ -8,7 +8,156 @@ namespace { + using namespace meta_hpp; + using namespace std::string_literals; +} + +namespace +{ + struct ivec2 { + int x{}; + int y{}; + + [[maybe_unused]] ivec2() = default; + [[maybe_unused]] explicit ivec2(int v): x{v}, y{v} {} + [[maybe_unused]] ivec2(int x, int y): x{x}, y{y} {} + }; + + struct ipnt2 { + const int x{}; + const int y{}; + + [[maybe_unused]] ipnt2() = default; + [[maybe_unused]] explicit ipnt2(int v): x{v}, y{v} {} + [[maybe_unused]] ipnt2(int x, int y): x{x}, y{y} {} + }; + + bool operator==(const ivec2& l, const ivec2& r) noexcept { + return l.x == r.x && l.y == r.y; + } + + bool operator==(const ipnt2& l, const ipnt2& r) noexcept { + return l.x == r.x && l.y == r.y; + } } TEST_CASE("features/infos/member") { + registry db; + + db( + namespace_{"vmath"}( + class_{"ivec2"}( + member_{"x", &ivec2::x}, + member_{"y", &ivec2::y} + ), + class_{"ipnt2"}( + member_{"x", &ipnt2::x}, + member_{"y", &ipnt2::y} + ) + ) + ); + + SUBCASE("non_const") { + const member_info x_mi = db.get_member_by_path("vmath::ivec2::x"); + const member_info y_mi = db.get_member_by_path("vmath::ivec2::y"); + REQUIRE(x_mi); + REQUIRE(y_mi); + + { + CHECK(x_mi.name() == "x"); + CHECK(x_mi.type().class_type() == type_db::get()); + CHECK(x_mi.type().value_type() == type_db::get()); + + CHECK(y_mi.name() == "y"); + CHECK(y_mi.type().class_type() == type_db::get()); + CHECK(y_mi.type().value_type() == type_db::get()); + } + + { + ivec2 v{1,2}; + + CHECK(x_mi.get(v).equals(1)); + CHECK(x_mi.get(std::as_const(v)).equals(1)); + CHECK(x_mi.get(std::move(v)).equals(1)); + CHECK(x_mi.get(std::move(std::as_const(v))).equals(1)); + + CHECK(y_mi.get(v).equals(2)); + CHECK(y_mi.get(std::as_const(v)).equals(2)); + CHECK(y_mi.get(std::move(v)).equals(2)); + CHECK(y_mi.get(std::move(std::as_const(v))).equals(2)); + + static_assert(std::is_invocable_v); + static_assert(std::is_invocable_v); + static_assert(std::is_invocable_v); + static_assert(std::is_invocable_v); + } + + { + ivec2 v{1,2}; + + CHECK_NOTHROW(x_mi.set(v, 10)); CHECK(v.x == 10); + CHECK_THROWS(x_mi.set(std::as_const(v), 11)); CHECK(v.x == 10); + CHECK_NOTHROW(x_mi.set(std::move(v), 12)); CHECK(v.x == 12); + CHECK_THROWS(x_mi.set(std::move(std::as_const(v)), 13)); CHECK(v.x == 12); + + CHECK_NOTHROW(y_mi.set(v, 20)); CHECK(v.y == 20); + CHECK_THROWS(y_mi.set(std::as_const(v), 21)); CHECK(v.y == 20); + CHECK_NOTHROW(y_mi.set(std::move(v), 22)); CHECK(v.y == 22); + CHECK_THROWS(y_mi.set(std::move(std::as_const(v)), 23)); CHECK(v.y == 22); + + CHECK(v == ivec2{12,22}); + } + } + + SUBCASE("const") { + const member_info x_mi = db.get_member_by_path("vmath::ipnt2::x"); + const member_info y_mi = db.get_member_by_path("vmath::ipnt2::y"); + REQUIRE(x_mi); + REQUIRE(y_mi); + + { + CHECK(x_mi.name() == "x"); + CHECK(x_mi.type().class_type() == type_db::get()); + CHECK(x_mi.type().value_type() == type_db::get()); + + CHECK(y_mi.name() == "y"); + CHECK(y_mi.type().class_type() == type_db::get()); + CHECK(y_mi.type().value_type() == type_db::get()); + } + + { + ipnt2 v{1,2}; + + CHECK(x_mi.get(v).equals(1)); + CHECK(x_mi.get(std::as_const(v)).equals(1)); + CHECK(x_mi.get(std::move(v)).equals(1)); + CHECK(x_mi.get(std::move(std::as_const(v))).equals(1)); + + CHECK(y_mi.get(v).equals(2)); + CHECK(y_mi.get(std::as_const(v)).equals(2)); + CHECK(y_mi.get(std::move(v)).equals(2)); + CHECK(y_mi.get(std::move(std::as_const(v))).equals(2)); + + static_assert(std::is_invocable_v); + static_assert(std::is_invocable_v); + static_assert(std::is_invocable_v); + static_assert(std::is_invocable_v); + } + + { + ipnt2 v{1,2}; + + CHECK_THROWS(x_mi.set(v, 10)); CHECK(v.x == 1); + CHECK_THROWS(x_mi.set(std::as_const(v), 11)); CHECK(v.x == 1); + CHECK_THROWS(x_mi.set(std::move(v), 12)); CHECK(v.x == 1); + CHECK_THROWS(x_mi.set(std::move(std::as_const(v)), 13)); CHECK(v.x == 1); + + CHECK_THROWS(y_mi.set(v, 20)); CHECK(v.y == 2); + CHECK_THROWS(y_mi.set(std::as_const(v), 21)); CHECK(v.y == 2); + CHECK_THROWS(y_mi.set(std::move(v), 22)); CHECK(v.y == 2); + CHECK_THROWS(y_mi.set(std::move(std::as_const(v)), 23)); CHECK(v.y == 2); + + CHECK(v == ipnt2{1,2}); + } + } }