diff --git a/lager/lenses.hpp b/lager/lenses.hpp index 1f8db7c6..7100103e 100644 --- a/lager/lenses.hpp +++ b/lager/lenses.hpp @@ -21,6 +21,15 @@ namespace lager { namespace detail { +template +struct should_move : + std::integral_constant && + !std::is_lvalue_reference_v> {}; + +template +constexpr bool should_move_v = should_move::value; + template struct const_functor; @@ -42,6 +51,15 @@ struct const_functor } }; +template +struct is_const_functor : public std::false_type {}; + +template +struct is_const_functor> : public std::true_type {}; + +template +constexpr bool is_const_functor_v = is_const_functor::value; + template struct identity_functor; @@ -59,10 +77,135 @@ struct identity_functor template auto operator()(Fn&& f) && { - return make_identity_functor( - std::forward(f)(std::forward(value))); + + if constexpr (!should_move_v) { + return make_identity_functor( + std::forward(f)(value)); + } else { + return make_identity_functor( + std::forward(f)(std::move(value))); + } + } +}; + +template +struct identity_functor_skip_first +{ + template + auto operator() (T&&) const & { + return make_identity_functor(part); + } + + template + auto operator() (T&&) && { + if constexpr (!should_move_v) { + return make_identity_functor(part); + } else { + return make_identity_functor(std::move(part)); + } + } + + Part part; +}; + +template +auto make_identity_functor_skip_first(T&& x) -> identity_functor_skip_first +{ + return {std::forward(x)}; +} + +template +struct getset_t +{ + template + auto operator() (Whole &&w) { + return functorImpl(*this, LAGER_FWD(w)); + } + + template + auto operator() (Whole &&w) const { + return functorImpl(*this, LAGER_FWD(w)); + } + + F f; + Getter getter; + Setter setter; + +private: + /** + * Deduce proper constness of 'this' via the functorImpl() call + */ + template + static auto functorImpl(Self &&self, Whole &&w) { + if constexpr (is_const_functor_v) { + /** + * We don't have a setter here, so it is safe to + * jus pass `w` as an rvalue. + * + * We also know that we are calling the const_functor, + * and it discards the passed argument, so just pass a + * noop to it. + * + * This branch is taken when viewing through the lens. + */ + return self.f(self.getter(LAGER_FWD(w))) // pass `w` into the getter as an rvalue! + (zug::noop); + } else { + /** + * Here we have both, getter and setter, so we pass + * `w` to getter as an lvalue, and to setter as an rvalue. + * The setter has a chance to reuse the resources of + * the passed value. + * + * This branch is taken on all the levels of setting the + * value through except of the tompost level. + */ + return self.f(self.getter(w)) // pass `w` into the getter as an lvalue! + ([&](auto&& x) { + return self.setter(LAGER_FWD(w), LAGER_FWD(x)); + }); + } } }; + +/** + * This specialization is called when a set() method is called over + * the lens. In such a case we can skip calling the getter branch + * of the lens. + * + * This branch is taken on the topmost level of setting the value + * through the lens. + */ +template +struct getset_t, Getter, Setter> +{ + template + auto operator() (Part &&p) { + return std::move(f)(zug::noop) + ([&](auto&& x) { + return setter(LAGER_FWD(p), LAGER_FWD(x)); + }); + } + + template + auto operator() (Part &&p) const { + return f(zug::noop) + ([&](auto&& x) { + return setter(LAGER_FWD(p), LAGER_FWD(x)); + }); + } + + identity_functor_skip_first &&f; + Getter getter; + Setter setter; +}; + +template +auto make_getset_t(F &&f, const Getter &getter, const Setter &setter) +{ + return getset_t{std::forward(f), getter, setter}; +} + } // namespace detail //! @defgroup lenses-api @@ -80,7 +223,7 @@ decltype(auto) view(LensT&& lens, T&& x) template decltype(auto) set(LensT&& lens, T&& x, U&& v) { - return lens([&v](auto&&) { return detail::make_identity_functor(v); })( + return lens(detail::make_identity_functor_skip_first(std::forward(v))) ( std::forward(x)) .value; } @@ -103,15 +246,10 @@ namespace lenses { //! @{ template -auto getset(Getter&& getter, Setter&& setter) +auto getset(const Getter& getter, const Setter& setter) { return zug::comp([=](auto&& f) { - return [&, f = LAGER_FWD(f)](auto&& p) { - return f(getter(std::forward(p)))([&](auto&& x) { - return setter(std::forward(p), - std::forward(x)); - }); - }; + return detail::make_getset_t(LAGER_FWD(f), getter, setter); }); } diff --git a/lager/lenses/attr.hpp b/lager/lenses/attr.hpp index 7e0b5e42..43c589b4 100644 --- a/lager/lenses/attr.hpp +++ b/lager/lenses/attr.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -17,15 +18,14 @@ namespace lenses { template auto attr(Member member) { - return zug::comp([member](auto&& f) { - return [&, f = LAGER_FWD(f)](auto&& p) { - return f(LAGER_FWD(p).*member)([&](auto&& x) { - auto r = LAGER_FWD(p); - r.*member = LAGER_FWD(x); - return r; - }); - }; - }); + return getset( + [=](auto &&p) -> decltype(auto) { + return LAGER_FWD(p).*member; + }, + [=](auto p, auto &&x) { + p.*member = LAGER_FWD(x); + return p; + }); } //! @} diff --git a/lager/lenses/unbox.hpp b/lager/lenses/unbox.hpp index 6c1e2f63..0c85e244 100644 --- a/lager/lenses/unbox.hpp +++ b/lager/lenses/unbox.hpp @@ -15,7 +15,7 @@ namespace lenses { * `Lens, T>` */ ZUG_INLINE_CONSTEXPR auto unbox = zug::comp([](auto&& f) { - return [f](auto&& p) { + return [f = LAGER_FWD(f)](auto&& p) { return f(LAGER_FWD(p).get())( [&](auto&& x) { return std::decay_t{LAGER_FWD(x)}; }); }; diff --git a/test/lenses.cpp b/test/lenses.cpp index 3446d756..46786fef 100644 --- a/test/lenses.cpp +++ b/test/lenses.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -30,6 +31,14 @@ struct yearday { int day; int month; + + friend bool operator==(const yearday &lhs, const yearday &rhs) { + return lhs.day == rhs.day && lhs.month == rhs.month; + } + + friend bool operator!=(const yearday &lhs, const yearday &rhs) { + return !(lhs == rhs); + } }; struct person @@ -43,6 +52,7 @@ using namespace lager; using namespace lager::lenses; using namespace zug; + TEST_CASE("lenses, minimal example") { auto month = zug::comp([](auto&& f) { @@ -200,6 +210,547 @@ TEST_CASE("lenses, attr2, references") } } +template +auto attr3(Member member, int *numGetCalls) +{ + return getset( + [=](auto&& x) -> decltype(auto) { + (*numGetCalls)++; + return std::forward(x).*member; + }, + [=](auto x, auto&& v) { + x.*member = std::forward(v); + return x; + }); +}; + +TEST_CASE("lenses, no getter on set") +{ + int numGetCalls = 0; + auto name = attr3(&person::name, &numGetCalls); + auto p1 = person{{5, 4}, "juanpe"}; + CHECK(view(name, p1) == "juanpe"); + CHECK(numGetCalls == 1); + + p1 = set(name, p1, "ncopernicus"); + CHECK(numGetCalls == 1); + + CHECK(view(name, p1) == "ncopernicus"); + CHECK(numGetCalls == 2); +} + +TEST_CASE("lenses, no getter on nested set") +{ + int numGetCallsOnBirthDay = 0; + int numGetCallsOnMonth = 0; + auto birthday = attr3(&person::birthday, &numGetCallsOnBirthDay); + auto month = attr3(&yearday::month, &numGetCallsOnMonth); + auto p1 = person{{5, 4}, "juanpe"}; + + CHECK(view(birthday, p1) == yearday{5, 4}); + CHECK(numGetCallsOnBirthDay == 1); + CHECK(numGetCallsOnMonth == 0); + numGetCallsOnBirthDay = 0; + numGetCallsOnMonth = 0; + + CHECK(view(birthday | month, p1) == 4); + CHECK(numGetCallsOnBirthDay == 1); + CHECK(numGetCallsOnMonth == 1); + numGetCallsOnBirthDay = 0; + numGetCallsOnMonth = 0; + + p1 = set(birthday | month, p1, 6); + CHECK(numGetCallsOnBirthDay == 1); + CHECK(numGetCallsOnMonth == 0); + numGetCallsOnBirthDay = 0; + numGetCallsOnMonth = 0; + + CHECK(view(birthday | month, p1) == 6); + CHECK(numGetCallsOnBirthDay == 1); + CHECK(numGetCallsOnMonth == 1); + numGetCallsOnBirthDay = 0; + numGetCallsOnMonth = 0; +} + + +struct copy_info +{ + copy_info(std::string _debugName) : debug_name(std::move(_debugName)) {}; + + bool operator== (const std::tuple &rhs) const { + return num_copy_constructions == std::get<0>(rhs) && + num_copy_assignments == std::get<1>(rhs) && + num_move_constructions == std::get<2>(rhs) && + num_move_assignments == std::get<3>(rhs); + } + + bool operator!= (const std::tuple &rhs) const { + return !(*this == rhs); + } + + void reset() { + num_copy_constructions = 0; + num_copy_assignments = 0; + num_move_constructions = 0; + num_move_assignments = 0; + } + + int num_copy_constructions = 0; + int num_copy_assignments = 0; + int num_move_constructions = 0; + int num_move_assignments = 0; + std::string debug_name; +}; + +std::ostream& operator << (std::ostream &out, const copy_info &info) +{ + out << "copy_info("; + out << "cc:" << info.num_copy_constructions << " "; + out << "ca:" << info.num_copy_assignments << " "; + out << "mc:" << info.num_move_constructions << " "; + out << "ma:" << info.num_move_assignments; + out << ")"; + return out; +} + +struct copy_tracker +{ + copy_tracker(copy_info &info) + : info(info) + { + } + + copy_tracker(const copy_tracker &rhs) + : info(rhs.info) + { + assert(!rhs.moved_from_here); + info.num_copy_constructions++; + } + + copy_tracker(copy_tracker &&rhs) + : info(rhs.info) + { + assert(!rhs.moved_from_here); + rhs.moved_from_here = true; + info.num_move_constructions++; + } + + copy_tracker& operator=(const copy_tracker &rhs) + { + assert(!rhs.moved_from_here); + info = rhs.info; + info.num_copy_assignments++; + + // the object becomes valid after it has been moved into + moved_from_here = false; + return *this; + } + + copy_tracker& operator=(copy_tracker &&rhs) + { + assert(!rhs.moved_from_here); + info = rhs.info; + rhs.moved_from_here = true; + info.num_move_assignments++; + + // the object becomes valid after it has been moved into + moved_from_here = false; + return *this; + } + + copy_info &info; + bool moved_from_here = false; +}; + +struct debug_yearday +{ + debug_yearday(copy_info &info) + : tracker(info) + { + } + + bool valid() { + return !tracker.moved_from_here; + } + + copy_tracker tracker; + int day {0}; + int month {0}; +}; + +struct debug_person +{ + debug_person(copy_info &person_copy_info, copy_info &birthday_copy_info) + : tracker(person_copy_info) + , birthday(birthday_copy_info) + + { + } + + bool valid() { + return !tracker.moved_from_here && + !birthday.tracker.moved_from_here; + } + + copy_tracker tracker; + debug_yearday birthday; + std::string name; + std::vector things{}; +}; + +struct debug_freelancer +{ + debug_freelancer(copy_info &freelancer_copy_info, copy_info &person_copy_info, copy_info &birthday_copy_info) + : tracker(freelancer_copy_info) + , person(person_copy_info, birthday_copy_info) + { + } + + bool valid() { + return !tracker.moved_from_here && + !person.tracker.moved_from_here && + !person.birthday.tracker.moved_from_here; + } + + copy_tracker tracker; + debug_person person; + int tax_id {}; +}; + +TEST_CASE("getset copy: view lvalue") +{ + copy_info person_copy_info("person"); + copy_info birthday_copy_info("birthday"); + auto birthday_lens = attr2(&debug_person::birthday); + + auto p1 = debug_person(person_copy_info, birthday_copy_info); + + CHECK(person_copy_info == std::make_tuple(0, 0, 0, 0)); + CHECK(birthday_copy_info == std::make_tuple(0, 0, 0, 0)); + + auto birthday = view(birthday_lens, p1); + + CHECK(person_copy_info == std::make_tuple(0, 0, 0, 0)); + CHECK(birthday_copy_info == std::make_tuple(1, 0, 0, 0)); + + CHECK(p1.valid()); + CHECK(birthday.valid()); +} + +TEST_CASE("getset copy: view rvalue") +{ + copy_info person_copy_info("person"); + copy_info birthday_copy_info("birthday"); + + auto birthday_lens = attr2(&debug_person::birthday); + + auto birthday = view(birthday_lens, debug_person(person_copy_info, birthday_copy_info)); + + CHECK(person_copy_info == std::make_tuple(0, 0, 0, 0)); + CHECK(birthday_copy_info == std::make_tuple(0, 0, 3, 0)); + + CHECK(birthday.valid()); +} + + +TEST_CASE("getset copy: set lvalue with rvalue") +{ + copy_info person_copy_info("person"); + copy_info birthday_copy_info("birthday"); + + auto birthday_lens = attr2(&debug_person::birthday); + + auto p1 = debug_person(person_copy_info, birthday_copy_info); + + auto birthday = + set(birthday_lens, p1, debug_yearday(birthday_copy_info)); + + CHECK(person_copy_info == std::make_tuple(1, 0, 3, 0)); + CHECK(birthday_copy_info == std::make_tuple(1, 0, 5, 1)); + + CHECK(p1.valid()); + CHECK(birthday.valid()); +} + +TEST_CASE("getset copy: set lvalue with lvalue") +{ + copy_info person_copy_info("person"); + copy_info birthday_copy_info("birthday"); + + auto birthday_lens = attr2(&debug_person::birthday); + + auto p1 = debug_person(person_copy_info, birthday_copy_info); + auto day1 = debug_yearday(birthday_copy_info); + + auto birthday = set(birthday_lens, p1, day1); + + CHECK(person_copy_info == std::make_tuple(1, 0, 3, 0)); + CHECK(birthday_copy_info == std::make_tuple(1, 1, 3, 0)); + + CHECK(p1.valid()); + CHECK(day1.valid()); + CHECK(birthday.valid()); +} + +TEST_CASE("getset copy: set rvalue with lvalue") +{ + copy_info person_copy_info("person"); + copy_info birthday_copy_info("birthday"); + + auto birthday_lens = attr2(&debug_person::birthday); + + auto day1 = debug_yearday(birthday_copy_info); + + auto birthday = + set(birthday_lens, debug_person(person_copy_info, birthday_copy_info), day1); + + CHECK(person_copy_info == std::make_tuple(0, 0, 4, 0)); + CHECK(birthday_copy_info == std::make_tuple(0, 1, 4, 0)); + + CHECK(day1.valid()); + CHECK(birthday.valid()); +} + +TEST_CASE("getset copy: set rvalue with rvalue") +{ + copy_info person_copy_info("person"); + copy_info birthday_copy_info("birthday"); + + auto birthday_lens = attr2(&debug_person::birthday); + + auto birthday = + set(birthday_lens, + debug_person(person_copy_info, birthday_copy_info), + debug_yearday(birthday_copy_info)); + + CHECK(person_copy_info == std::make_tuple(0, 0, 4, 0)); + CHECK(birthday_copy_info == std::make_tuple(0, 0, 6, 1)); + + CHECK(birthday.valid()); +} + +TEST_CASE("nested getset copy: view lvalue") +{ + copy_info freelancer_copy_info("freelancer"); + copy_info person_copy_info("person"); + copy_info birthday_copy_info("birthday"); + auto birthday_lens = attr2(&debug_person::birthday); + auto person_lens = attr2(&debug_freelancer::person); + auto freelancer_birthday_lens = person_lens | birthday_lens; + + auto e1 = debug_freelancer(freelancer_copy_info, person_copy_info, birthday_copy_info); + + CHECK(person_copy_info == std::make_tuple(0, 0, 0, 0)); + CHECK(birthday_copy_info == std::make_tuple(0, 0, 0, 0)); + + auto birthday = view(freelancer_birthday_lens, e1); + + CHECK(freelancer_copy_info == std::make_tuple(0, 0, 0, 0)); + CHECK(person_copy_info == std::make_tuple(0, 0, 0, 0)); + CHECK(birthday_copy_info == std::make_tuple(1, 0, 0, 0)); + + CHECK(e1.valid()); + CHECK(birthday.valid()); +} + +TEST_CASE("nested getset copy: view rvalue") +{ + copy_info freelancer_copy_info("freelancer"); + copy_info person_copy_info("person"); + copy_info birthday_copy_info("birthday"); + auto birthday_lens = attr2(&debug_person::birthday); + auto person_lens = attr2(&debug_freelancer::person); + auto freelancer_birthday_lens = person_lens | birthday_lens; + + CHECK(person_copy_info == std::make_tuple(0, 0, 0, 0)); + CHECK(birthday_copy_info == std::make_tuple(0, 0, 0, 0)); + + auto birthday = + view(freelancer_birthday_lens, + debug_freelancer(freelancer_copy_info, person_copy_info, birthday_copy_info)); + + CHECK(freelancer_copy_info == std::make_tuple(0, 0, 0, 0)); + CHECK(person_copy_info == std::make_tuple(0, 0, 0, 0)); + CHECK(birthday_copy_info == std::make_tuple(0, 0, 4, 0)); + + CHECK(birthday.valid()); +} + +TEST_CASE("nested getset copy: set lvalue with rvalue") +{ + copy_info freelancer_copy_info("freelancer"); + copy_info person_copy_info("person"); + copy_info birthday_copy_info("birthday"); + + auto birthday_lens = attr2(&debug_person::birthday); + auto person_lens = attr2(&debug_freelancer::person); + auto freelancer_birthday_lens = person_lens | birthday_lens; + + auto e1 = debug_freelancer(freelancer_copy_info, person_copy_info, birthday_copy_info); + + auto birthday = + set(freelancer_birthday_lens, + e1, + debug_yearday(birthday_copy_info)); + + CHECK(freelancer_copy_info == std::make_tuple(1, 0, 3, 0)); + + /** + * One copy is for passing a person into the setter of birthday_lens, + * the other one is for passing the source enterpreneur into the + * setter function of person_lens. + */ + CHECK(person_copy_info == std::make_tuple(2, 0, 5, 1)); + CHECK(birthday_copy_info == std::make_tuple(2, 0, 7, 2)); + + CHECK(e1.valid()); + CHECK(birthday.valid()); +} + +TEST_CASE("nested getset copy: set lvalue with lvalue") +{ + copy_info freelancer_copy_info("freelancer"); + copy_info person_copy_info("person"); + copy_info birthday_copy_info("birthday"); + + auto birthday_lens = attr2(&debug_person::birthday); + auto person_lens = attr2(&debug_freelancer::person); + auto freelancer_birthday_lens = person_lens | birthday_lens; + + auto e1 = debug_freelancer(freelancer_copy_info, person_copy_info, birthday_copy_info); + auto day1 = debug_yearday(birthday_copy_info); + + auto birthday = + set(freelancer_birthday_lens, + e1, + day1); + + CHECK(freelancer_copy_info == std::make_tuple(1, 0, 3, 0)); + + /** + * One copy is for passing a person into the setter of birthday_lens, + * the other one is for passing the source enterpreneur into the + * setter function of person_lens. + */ + CHECK(person_copy_info == std::make_tuple(2, 0, 5, 1)); + CHECK(birthday_copy_info == std::make_tuple(2, 1, 5, 1)); + + CHECK(e1.valid()); + CHECK(day1.valid()); + CHECK(birthday.valid()); +} + +TEST_CASE("nested getset copy: set rvalue with lvalue") +{ + copy_info freelancer_copy_info("freelancer"); + copy_info person_copy_info("person"); + copy_info birthday_copy_info("birthday"); + + auto birthday_lens = attr2(&debug_person::birthday); + auto person_lens = attr2(&debug_freelancer::person); + auto freelancer_birthday_lens = person_lens | birthday_lens; + + auto day1 = debug_yearday(birthday_copy_info); + + auto birthday = + set(freelancer_birthday_lens, + debug_freelancer(freelancer_copy_info, person_copy_info, birthday_copy_info), + day1); + + CHECK(freelancer_copy_info == std::make_tuple(0, 0, 4, 0)); + + /** + * The only copy is to pass the person into birthday_lens. Use const-ref + * for `whole` argument to avoid this copy. + */ + CHECK(person_copy_info == std::make_tuple(1, 0, 6, 1)); + CHECK(birthday_copy_info == std::make_tuple(1, 1, 6, 1)); + + CHECK(day1.valid()); + CHECK(birthday.valid()); +} + +TEST_CASE("nested getset copy: set rvalue with rvalue") +{ + copy_info freelancer_copy_info("freelancer"); + copy_info person_copy_info("person"); + copy_info birthday_copy_info("birthday"); + + auto birthday_lens = attr2(&debug_person::birthday); + auto person_lens = attr2(&debug_freelancer::person); + auto freelancer_birthday_lens = person_lens | birthday_lens; + + auto birthday = + set(freelancer_birthday_lens, + debug_freelancer(freelancer_copy_info, person_copy_info, birthday_copy_info), + debug_yearday(birthday_copy_info)); + + CHECK(freelancer_copy_info == std::make_tuple(0, 0, 4, 0)); + + /** + * The only copy is to pass the person into birthday_lens. Use const-ref + * for `whole` argument to avoid this copy. + */ + CHECK(person_copy_info == std::make_tuple(1, 0, 6, 1)); + CHECK(birthday_copy_info == std::make_tuple(1, 0, 8, 2)); + + CHECK(birthday.valid()); +} + +TEST_CASE("nested (attr | getset)") +{ + copy_info freelancer_copy_info("freelancer"); + copy_info person_copy_info("person"); + copy_info birthday_copy_info("birthday"); + + auto birthday_lens = attr2(&debug_person::birthday); + auto person_lens = attr(&debug_freelancer::person); + auto freelancer_birthday_lens = person_lens | birthday_lens; + + auto birthday = + set(freelancer_birthday_lens, + debug_freelancer(freelancer_copy_info, person_copy_info, birthday_copy_info), + debug_yearday(birthday_copy_info)); + + CHECK(freelancer_copy_info == std::make_tuple(0, 0, 4, 0)); + + /** + * The only copy is to pass the person into birthday_lens. Use const-ref + * for `whole` argument to avoid this copy. + */ + CHECK(person_copy_info == std::make_tuple(1, 0, 6, 1)); + CHECK(birthday_copy_info == std::make_tuple(1, 0, 8, 2)); + + CHECK(birthday.valid()); +} + +TEST_CASE("nested (getset | attr)") +{ + copy_info freelancer_copy_info("freelancer"); + copy_info person_copy_info("person"); + copy_info birthday_copy_info("birthday"); + + auto birthday_lens = attr(&debug_person::birthday); + auto person_lens = attr2(&debug_freelancer::person); + auto freelancer_birthday_lens = person_lens | birthday_lens; + + auto birthday = + set(freelancer_birthday_lens, + debug_freelancer(freelancer_copy_info, person_copy_info, birthday_copy_info), + debug_yearday(birthday_copy_info)); + + CHECK(freelancer_copy_info == std::make_tuple(0, 0, 4, 0)); + + /** + * The only copy is to pass the person into birthday_lens. Use const-ref + * for `whole` argument to avoid this copy. + */ + CHECK(person_copy_info == std::make_tuple(1, 0, 6, 1)); + CHECK(birthday_copy_info == std::make_tuple(1, 0, 8, 2)); + + CHECK(birthday.valid()); +} + TEST_CASE("lenses, at immutable index") { auto first = at(0);