From 6ef437416c0baa755893f0a8e65e65fa7091f8d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luiz=20Rom=C3=A1rio=20Santana=20Rios?= <luizromario@tecgraf.puc-rio.br> Date: Fri, 7 Feb 2025 12:41:23 -0300 Subject: [PATCH 1/6] =?UTF-8?q?[VTOD-88015][VTOD-87680]=20ranges::view::jo?= =?UTF-8?q?in:=20implementa=C3=A7=C3=A3o=20inicial?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Suporte a range não observável de observáveis --- include/coruja/ranges/view/join.hpp | 65 ++++++++++++++++++++++ include/coruja/support/dyn_connections.hpp | 4 ++ test/CMakeLists.txt | 5 ++ test/join.cpp | 54 ++++++++++++++++++ 4 files changed, 128 insertions(+) create mode 100644 include/coruja/ranges/view/join.hpp create mode 100644 test/join.cpp diff --git a/include/coruja/ranges/view/join.hpp b/include/coruja/ranges/view/join.hpp new file mode 100644 index 0000000..e3bad31 --- /dev/null +++ b/include/coruja/ranges/view/join.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include <coruja/ranges/view/reference.hpp> +#include <coruja/support/dyn_connections.hpp> +#include <coruja/support/signal/any_connection.hpp> +#include <range/v3/detail/config.hpp> +#include <range/v3/functional/pipeable.hpp> +#include <range/v3/range/conversion.hpp> +#include <range/v3/range/traits.hpp> +#include <range/v3/view/join.hpp> +#include <range/v3/view/transform.hpp> + +namespace coruja::ranges::view +{ + +template <typename Rng> +class join_view : public ::ranges::join_view<Rng> +{ + using base = ::ranges::join_view<Rng>; + + Rng rng; + +public: + using for_each_connection_t = coruja::dyn_any_connections; + using before_erase_connection_t = coruja::dyn_any_connections; + using value_type = ::ranges::range_value_t<base>; + + join_view(Rng rng) : + base{rng}, + rng{rng} + {} + + template <typename F> + for_each_connection_t for_each(F f) + { + return rng + | ::ranges::views::transform([&](auto&& r){ + return coruja::any_connection{r.for_each(f)}; + }) + | ::ranges::to_vector; + } + + template <typename F> + before_erase_connection_t before_erase(F f) + { + return rng + | ::ranges::views::transform([&](auto&& r){ + return coruja::any_connection{r.before_erase(f)}; + }) + | ::ranges::to_vector; + } +}; + +struct join_fn : ::ranges::pipeable_base +{ + template <typename Rng> + auto operator()(Rng rng) const + { + return join_view<Rng>{rng}; + } +}; + +RANGES_INLINE_VARIABLE(join_fn, join) + +} diff --git a/include/coruja/support/dyn_connections.hpp b/include/coruja/support/dyn_connections.hpp index 45b0256..6ad12f9 100644 --- a/include/coruja/support/dyn_connections.hpp +++ b/include/coruja/support/dyn_connections.hpp @@ -21,6 +21,10 @@ struct dyn_connections : connection_base, SequenceContainer dyn_connections() = default; + dyn_connections(SequenceContainer&& conns) : + base(std::move(conns)) + {} + void disconnect() { for(auto&& c : *this) c.disconnect(); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 017be4b..d338894 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -11,6 +11,7 @@ set(TEST_CASES container_view filter for_each_n + join lift_object list map @@ -36,6 +37,10 @@ set(TEST_PREFIX coruja_test_) foreach(CASE IN LISTS TEST_CASES) set(TEST_NAME ${TEST_PREFIX}${CASE}) + add_executable(${TEST_NAME} ${CASE}.cpp) add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) endforeach() + + +target_link_libraries(coruja_test_join Boost::unit_test_framework) diff --git a/test/join.cpp b/test/join.cpp new file mode 100644 index 0000000..8d9d174 --- /dev/null +++ b/test/join.cpp @@ -0,0 +1,54 @@ +#define BOOST_TEST_MODULE CORUJA +#include <boost/test/unit_test.hpp> + +#include <coruja/ranges/container/list.hpp> +#include <coruja/ranges/view/filter.hpp> +#include <coruja/ranges/view/join.hpp> +#include <range/v3/view/addressof.hpp> +#include <range/v3/view/any_view.hpp> +#include <range/v3/view/indirect.hpp> +#include <range/v3/view/join.hpp> + +BOOST_AUTO_TEST_CASE(NonObservableList_Of_ObservableList) +{ + coruja::list<int> l1{1, 2, 3}; + coruja::list<int> l2{4, 5, 6}; + coruja::list<int> l3{7, 8, 9}; + std::list<coruja::list<int>*> listlist {&l1, &l2, &l3}; + auto dereffed = listlist | ranges::views::indirect; + + auto flattened = dereffed | coruja::ranges::view::join; + std::vector expected{1, 2, 3, 4, 5, 6, 7, 8, 9}; + + BOOST_TEST(flattened == expected, boost::test_tools::per_element()); + + int lastReaction = 0; + coruja::scoped_any_connection conn = + flattened.for_each([&](int n){ lastReaction = n; }); + + l1.push_back(10); + + BOOST_TEST(lastReaction == 10); + + l2.push_back(20); + + BOOST_TEST(lastReaction == 20); + + l3.push_back(30); + + BOOST_TEST(lastReaction == 30); + + auto gt10 = flattened | coruja::ranges::view::filter([](int n){ return n > 10; }); + std::vector gt10Expected{20, 30}; + + BOOST_TEST(gt10 == gt10Expected, boost::test_tools::per_element()); + + int lastGt10Reaction = 0; + auto gt10Conn = gt10.for_each([&](int n){ lastGt10Reaction = n; }); + + l1.push_back(5); + BOOST_TEST(lastGt10Reaction == 30); // não deve reagir por causa do filter + + l2.push_back(11); + BOOST_TEST(lastGt10Reaction == 11); +} -- GitLab From c02143172819698255dad19cc31ea9cd00f6672e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luiz=20Rom=C3=A1rio=20Santana=20Rios?= <luizromario@tecgraf.puc-rio.br> Date: Mon, 10 Feb 2025 14:45:39 -0300 Subject: [PATCH 2/6] =?UTF-8?q?[VTOD-88015][VTOD-87680]=20Funcionar=20com?= =?UTF-8?q?=20range=20observ=C3=A1vel=20de=20range=20observ=C3=A1vel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/coruja/ranges/view/indirect.hpp | 8 ++ include/coruja/ranges/view/join.hpp | 98 ++++++++++++++++++-- include/coruja/support/shared_connection.hpp | 30 ++++++ test/join.cpp | 19 ++++ 4 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 include/coruja/ranges/view/indirect.hpp create mode 100644 include/coruja/support/shared_connection.hpp diff --git a/include/coruja/ranges/view/indirect.hpp b/include/coruja/ranges/view/indirect.hpp new file mode 100644 index 0000000..d4a94ff --- /dev/null +++ b/include/coruja/ranges/view/indirect.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include <coruja/ranges/view/transform.hpp> + +namespace coruja::ranges::view +{ + auto indirect = transform([](auto&& ptr) -> auto& { return *ptr; }); +} diff --git a/include/coruja/ranges/view/join.hpp b/include/coruja/ranges/view/join.hpp index e3bad31..28b17d2 100644 --- a/include/coruja/ranges/view/join.hpp +++ b/include/coruja/ranges/view/join.hpp @@ -2,14 +2,23 @@ #include <coruja/ranges/view/reference.hpp> #include <coruja/support/dyn_connections.hpp> +#include <coruja/support/shared_connection.hpp> #include <coruja/support/signal/any_connection.hpp> +#include <coruja/support/type_traits.hpp> #include <range/v3/detail/config.hpp> #include <range/v3/functional/pipeable.hpp> +#include <range/v3/range/access.hpp> #include <range/v3/range/conversion.hpp> #include <range/v3/range/traits.hpp> #include <range/v3/view/join.hpp> #include <range/v3/view/transform.hpp> +#include <functional> +#include <list> +#include <memory> +#include <utility> +#include <variant> + namespace coruja::ranges::view { @@ -17,38 +26,113 @@ template <typename Rng> class join_view : public ::ranges::join_view<Rng> { using base = ::ranges::join_view<Rng>; - - Rng rng; + using connection_t = coruja::shared_connection<coruja::dyn_any_connections>; public: - using for_each_connection_t = coruja::dyn_any_connections; - using before_erase_connection_t = coruja::dyn_any_connections; + using for_each_connection_t = connection_t; + using before_erase_connection_t = connection_t; using value_type = ::ranges::range_value_t<base>; join_view(Rng rng) : base{rng}, rng{rng} - {} + { + if constexpr(coruja::is_observable_erasable_range<Rng>::value) + { + // observa cada novo range observável inserido e cadastra + // todas as reações feitas até agora, removendo as reações + // cuja conexão morreu + + auto conn = rng.for_each([this](auto& r) + { + for(auto it = _for_each_conns.begin(); + it != _for_each_conns.end();) + { + auto& c = *it; + auto prev = it++; + + if(c.ptr.expired()) + { + _for_each_conns.erase(prev); + continue; + } + + connection_t conn(c.ptr.lock()); + conn->push_back( + std::visit([&](auto f){ + return coruja::any_connection{r.for_each(f)}; + }, c.function)); + } + + for(auto it = _before_erase_conns.begin(); + it != _before_erase_conns.end();) + { + auto& c = *it; + auto prev = it++; + + if(c.ptr.expired()) + { + _before_erase_conns.erase(prev); + continue; + } + + connection_t conn = c.ptr.lock(); + conn->push_back( + std::visit([&](auto f){ + return coruja::any_connection{r.before_erase(f)}; + }, c.function)); + } + }); + + _rng_conn = std::make_shared<coruja::scoped_any_connection>(std::move(conn)); + } + } template <typename F> for_each_connection_t for_each(F f) { - return rng + auto conns = rng | ::ranges::views::transform([&](auto&& r){ return coruja::any_connection{r.for_each(f)}; }) | ::ranges::to_vector; + auto shr_conn = coruja::make_shared_connection<coruja::dyn_any_connections>(std::move(conns)); + + _for_each_conns.push_back(Conn{shr_conn, f}); + return shr_conn; } template <typename F> before_erase_connection_t before_erase(F f) { - return rng + auto conns = rng | ::ranges::views::transform([&](auto&& r){ return coruja::any_connection{r.before_erase(f)}; }) | ::ranges::to_vector; + auto shr_conn = coruja::make_shared_connection<coruja::dyn_any_connections>(std::move(conns)); + + _before_erase_conns.push_back(Conn{shr_conn, f}); + return shr_conn; } + +private: + struct Conn + { + connection_t::weak_type ptr; + + using cont_t = typename ::ranges::range_value_t<Rng>; + using it_t = typename cont_t::iterator; + + std::variant< + std::function<void(value_type)>, + std::function<void(cont_t&, it_t)>> + function; + }; + + Rng rng; + std::list<Conn> _for_each_conns, _before_erase_conns; + std::shared_ptr<coruja::scoped_any_connection> _rng_conn; }; struct join_fn : ::ranges::pipeable_base diff --git a/include/coruja/support/shared_connection.hpp b/include/coruja/support/shared_connection.hpp new file mode 100644 index 0000000..3b4bb1f --- /dev/null +++ b/include/coruja/support/shared_connection.hpp @@ -0,0 +1,30 @@ +#include <coruja/support/signal/any_connection.hpp> +#include <coruja/support/signal/connection_base.hpp> + +#include <memory> + +namespace coruja { + +template<typename Conn> +struct shared_connection : connection_base, std::shared_ptr<Conn> +{ + using base = std::shared_ptr<Conn>; + + using base::base; + shared_connection(base b) : base(b) {} + + inline void disconnect() { (*this)->disconnect(); } + [[nodiscard]] inline bool blocked() const noexcept { return (*this)->blocked(); } + inline void block() { (*this)->block(); } + inline void unblock() { (*this)->unblock(); } +}; + +using shared_any_connection = shared_connection<any_connection>; + +template<typename Conn, typename... Args> +shared_connection<Conn> make_shared_connection(Args&&... args) +{ + return shared_connection<Conn>(new Conn(std::forward<Args>(args)...)); +} + +} diff --git a/test/join.cpp b/test/join.cpp index 8d9d174..8fe0f8a 100644 --- a/test/join.cpp +++ b/test/join.cpp @@ -4,6 +4,8 @@ #include <coruja/ranges/container/list.hpp> #include <coruja/ranges/view/filter.hpp> #include <coruja/ranges/view/join.hpp> +#include <coruja/ranges/view/transform.hpp> +#include <coruja/ranges/view/indirect.hpp> #include <range/v3/view/addressof.hpp> #include <range/v3/view/any_view.hpp> #include <range/v3/view/indirect.hpp> @@ -52,3 +54,20 @@ BOOST_AUTO_TEST_CASE(NonObservableList_Of_ObservableList) l2.push_back(11); BOOST_TEST(lastGt10Reaction == 11); } + +BOOST_AUTO_TEST_CASE(ObservableList_Of_ObservableList) +{ + coruja::list<int> l1{1, 2, 3}; + coruja::list<coruja::list<int>*> listlist{&l1}; + auto dereffed = listlist | coruja::ranges::view::indirect; + + auto flattened = dereffed | coruja::ranges::view::join; + + int lastReaction = 0; + auto conn = flattened.for_each([&](int n){ lastReaction = n; }); + + coruja::list<int> l2{4}; + listlist.push_back(&l2); + + BOOST_TEST(lastReaction == 4); +} -- GitLab From d677b72d2b5b60c0e106248aa9bb02a3856e6ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luiz=20Rom=C3=A1rio=20Santana=20Rios?= <luizromario@tecgraf.puc-rio.br> Date: Mon, 10 Feb 2025 15:13:31 -0300 Subject: [PATCH 3/6] =?UTF-8?q?[VTOD-88015][VTOD-87680]=20Funcionar=20com?= =?UTF-8?q?=20cont=C3=AAiner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/coruja/ranges/view/join.hpp | 4 ++-- test/join.cpp | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/include/coruja/ranges/view/join.hpp b/include/coruja/ranges/view/join.hpp index 28b17d2..35dae04 100644 --- a/include/coruja/ranges/view/join.hpp +++ b/include/coruja/ranges/view/join.hpp @@ -138,9 +138,9 @@ private: struct join_fn : ::ranges::pipeable_base { template <typename Rng> - auto operator()(Rng rng) const + auto operator()(Rng&& rng) const { - return join_view<Rng>{rng}; + return join_view{view(rng)}; } }; diff --git a/test/join.cpp b/test/join.cpp index 8fe0f8a..f44ed2e 100644 --- a/test/join.cpp +++ b/test/join.cpp @@ -71,3 +71,24 @@ BOOST_AUTO_TEST_CASE(ObservableList_Of_ObservableList) BOOST_TEST(lastReaction == 4); } + +BOOST_AUTO_TEST_CASE(ApplyDirectlyOnContainer) +{ + coruja::list<coruja::list<int>> ll; + + coruja::list<int> l1_temp{1, 2, 3}; + [[maybe_unused]] auto& l1 = ll.emplace_back(std::move(l1_temp)); + + coruja::list<int> l2_temp{4, 5, 6}; + [[maybe_unused]] auto& l2 = ll.emplace_back(std::move(l2_temp)); + + auto flattened = ll | coruja::ranges::view::join; + std::vector<int> expected{{1, 2, 3, 4, 5, 6}}; + BOOST_TEST(flattened == expected, boost::test_tools::per_element()); + + int lastReaction = 0; + auto conn = flattened.for_each([&](int n){ lastReaction = n; }); + + l1.push_back(10); + BOOST_TEST(lastReaction == 10); +} -- GitLab From bd67f3ea6ecd6a5de89f1574bb9d3456f413147d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luiz=20Rom=C3=A1rio=20Santana=20Rios?= <luizromario@tecgraf.puc-rio.br> Date: Tue, 11 Feb 2025 16:49:05 -0300 Subject: [PATCH 4/6] =?UTF-8?q?[VTOD-88015][VTOD-87680]=20Remover=20duplic?= =?UTF-8?q?a=C3=A7=C3=A3o=20de=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/coruja/ranges/view/join.hpp | 132 ++++++++++++++-------------- test/join.cpp | 7 ++ 2 files changed, 75 insertions(+), 64 deletions(-) diff --git a/include/coruja/ranges/view/join.hpp b/include/coruja/ranges/view/join.hpp index 35dae04..d5da0a4 100644 --- a/include/coruja/ranges/view/join.hpp +++ b/include/coruja/ranges/view/join.hpp @@ -39,99 +39,103 @@ public: { if constexpr(coruja::is_observable_erasable_range<Rng>::value) { - // observa cada novo range observável inserido e cadastra - // todas as reações feitas até agora, removendo as reações - // cuja conexão morreu - auto conn = rng.for_each([this](auto& r) { - for(auto it = _for_each_conns.begin(); - it != _for_each_conns.end();) - { - auto& c = *it; - auto prev = it++; - - if(c.ptr.expired()) - { - _for_each_conns.erase(prev); - continue; - } - - connection_t conn(c.ptr.lock()); - conn->push_back( - std::visit([&](auto f){ - return coruja::any_connection{r.for_each(f)}; - }, c.function)); - } - - for(auto it = _before_erase_conns.begin(); - it != _before_erase_conns.end();) - { - auto& c = *it; - auto prev = it++; - - if(c.ptr.expired()) - { - _before_erase_conns.erase(prev); - continue; - } - - connection_t conn = c.ptr.lock(); - conn->push_back( - std::visit([&](auto f){ - return coruja::any_connection{r.before_erase(f)}; - }, c.function)); - } + register_prev_conns_for( + [&r](auto f){ return r.for_each(f); }, _for_each_conns); + register_prev_conns_for( + [&r](auto f){ return r.before_erase(f); }, _before_erase_conns); }); _rng_conn = std::make_shared<coruja::scoped_any_connection>(std::move(conn)); + + // FIXME não reagimos à remoção de um dos contêineres externos. + // devemos chamar todas as reações de before_erase cadastradas + // para cada elemento da range interna sendo removida } } template <typename F> for_each_connection_t for_each(F f) { - auto conns = rng - | ::ranges::views::transform([&](auto&& r){ - return coruja::any_connection{r.for_each(f)}; - }) - | ::ranges::to_vector; - auto shr_conn = coruja::make_shared_connection<coruja::dyn_any_connections>(std::move(conns)); - - _for_each_conns.push_back(Conn{shr_conn, f}); - return shr_conn; + return register_reaction( + [](auto f, auto&& r){ return r.for_each(f); }, f, _for_each_conns); } template <typename F> before_erase_connection_t before_erase(F f) { - auto conns = rng - | ::ranges::views::transform([&](auto&& r){ - return coruja::any_connection{r.before_erase(f)}; - }) - | ::ranges::to_vector; - auto shr_conn = coruja::make_shared_connection<coruja::dyn_any_connections>(std::move(conns)); - - _before_erase_conns.push_back(Conn{shr_conn, f}); - return shr_conn; + return register_reaction( + [](auto f, auto&& r){ return r.before_erase(f); }, f, _before_erase_conns); } private: - struct Conn + using inner_t = typename ::ranges::range_value_t<Rng>; + + struct prev_conn_t { connection_t::weak_type ptr; - using cont_t = typename ::ranges::range_value_t<Rng>; - using it_t = typename cont_t::iterator; + using it_t = typename inner_t::iterator; std::variant< std::function<void(value_type)>, - std::function<void(cont_t&, it_t)>> + // FIXME o segundo overload do for_each, onde ele recebe um range + // e um iterador, não tem um comportamento tão bom porque + // ele expõe uma referência para uma range interna em vez de + // passar uma referência para a range externa. isso significa + // que, por exemplo, uma chamada a `distance()` aqui dentro + // vai dar a posição do elemento _na range interna_, não na + // externa. a solução não deve ser simples, pois envolve de + // alguma forma mapear o iterador interno para um iterador + // externo. uma alternativa seria impedir o funcionamento + // desse overload do for_each para evitar esse problema + std::function<void(inner_t&, it_t)>> function; }; + template <typename Reg, typename F> + connection_t register_reaction(Reg reg, F f, std::list<prev_conn_t>& prev_conns) + { + auto conns = rng + | ::ranges::views::transform([reg, f](auto&& r){ + return coruja::any_connection{reg(f, std::forward<decltype(r)>(r))}; + }) + | ::ranges::to_vector; + auto shr_conn = coruja::make_shared_connection<coruja::dyn_any_connections>(std::move(conns)); + + prev_conns.push_back(prev_conn_t{shr_conn, f}); + return shr_conn; + } + + template <typename Reg> + void register_prev_conns_for(Reg reg, std::list<prev_conn_t>& conns) + { + // observa cada novo range observável inserido e cadastra + // todas as reações feitas até agora, removendo as reações + // cuja conexão morreu + + for(auto it = conns.begin(); it != conns.end();) + { + auto& c = *it; + auto prev = it++; + + if(c.ptr.expired()) + { + conns.erase(prev); + continue; + } + + connection_t conn(c.ptr.lock()); + conn->push_back( + std::visit([&](auto f){ + return coruja::any_connection{reg(f)}; + }, c.function)); + } + } + Rng rng; - std::list<Conn> _for_each_conns, _before_erase_conns; + std::list<prev_conn_t> _for_each_conns, _before_erase_conns; std::shared_ptr<coruja::scoped_any_connection> _rng_conn; }; diff --git a/test/join.cpp b/test/join.cpp index f44ed2e..38ccf3d 100644 --- a/test/join.cpp +++ b/test/join.cpp @@ -66,10 +66,17 @@ BOOST_AUTO_TEST_CASE(ObservableList_Of_ObservableList) int lastReaction = 0; auto conn = flattened.for_each([&](int n){ lastReaction = n; }); + int beforeEraseLastReaction = 0; + auto be_conn = flattened.before_erase([&](int n){ beforeEraseLastReaction = n; }); + coruja::list<int> l2{4}; listlist.push_back(&l2); BOOST_TEST(lastReaction == 4); + + l2.remove(4); + + BOOST_TEST(beforeEraseLastReaction == 4); } BOOST_AUTO_TEST_CASE(ApplyDirectlyOnContainer) -- GitLab From dc3049dd2210a7ae5b7f8572b62392795f649c25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luiz=20Rom=C3=A1rio=20Santana=20Rios?= <luizromario@tecgraf.puc-rio.br> Date: Fri, 14 Feb 2025 12:23:42 -0300 Subject: [PATCH 5/6] [VTOD-88015][VTOD-87860] Adicionar suporte a sintaxe de iterador do for_each --- include/coruja/ranges/view/join.hpp | 90 ++++++++++++++++++++++------- test/join.cpp | 89 ++++++++++++++++++---------- 2 files changed, 128 insertions(+), 51 deletions(-) diff --git a/include/coruja/ranges/view/join.hpp b/include/coruja/ranges/view/join.hpp index d5da0a4..f55ccd2 100644 --- a/include/coruja/ranges/view/join.hpp +++ b/include/coruja/ranges/view/join.hpp @@ -5,8 +5,10 @@ #include <coruja/support/shared_connection.hpp> #include <coruja/support/signal/any_connection.hpp> #include <coruja/support/type_traits.hpp> +#include <range/v3/algorithm/find_if.hpp> #include <range/v3/detail/config.hpp> #include <range/v3/functional/pipeable.hpp> +#include <range/v3/iterator/operations.hpp> #include <range/v3/range/access.hpp> #include <range/v3/range/conversion.hpp> #include <range/v3/range/traits.hpp> @@ -71,40 +73,84 @@ public: private: using inner_t = typename ::ranges::range_value_t<Rng>; + using inner_it_t = typename inner_t::iterator; + using value_function_t = std::function<void(value_type&)>; + using inner_iterator_function_t = std::function<void(inner_t&, inner_it_t)>; + + using inner_func_variant_t = std::variant< + value_function_t, inner_iterator_function_t>; + + using outer_it_t = ::ranges::iterator_t<base>; + using outer_iterator_function_t = std::function<void(base&, outer_it_t)>; + + using outer_func_variant_t = std::variant< + value_function_t, outer_iterator_function_t>; + + struct to_inner_visitor + { + inner_func_variant_t operator()(value_function_t f) + { + return f; + } + + inner_func_variant_t operator()(outer_iterator_function_t f) + { + // traduz a função de reação definida pelo usuário + // para uma função de reação interna para cada range + // interna + // + // FIXME essa tradução pode ser problemática no caso do + // before_erase porque ela acessa as outras ranges, + // que podem p. ex. ter sido destruÃdas numa chamada + // de destrutor do contêiner original + // (ver teste unitário para um exemplo dessa + // situação) + return [f, &b=b, &rng=rng](inner_t& r, inner_it_t it) + { + int pos = 0; + for(auto& other : rng) + { + if(&r != &other) + pos += ::ranges::distance(other); + else + break; + } + + pos += ::ranges::distance(r.begin(), it); + f(b, ::ranges::next(b.begin(), pos)); + }; + } + + Rng& rng; + base& b; + }; + + inner_func_variant_t to_inner(outer_func_variant_t outer_f_var) + { + return std::visit(to_inner_visitor{rng, *this}, outer_f_var); + } struct prev_conn_t { connection_t::weak_type ptr; - - using it_t = typename inner_t::iterator; - - std::variant< - std::function<void(value_type)>, - // FIXME o segundo overload do for_each, onde ele recebe um range - // e um iterador, não tem um comportamento tão bom porque - // ele expõe uma referência para uma range interna em vez de - // passar uma referência para a range externa. isso significa - // que, por exemplo, uma chamada a `distance()` aqui dentro - // vai dar a posição do elemento _na range interna_, não na - // externa. a solução não deve ser simples, pois envolve de - // alguma forma mapear o iterador interno para um iterador - // externo. uma alternativa seria impedir o funcionamento - // desse overload do for_each para evitar esse problema - std::function<void(inner_t&, it_t)>> - function; + inner_func_variant_t function; }; - template <typename Reg, typename F> - connection_t register_reaction(Reg reg, F f, std::list<prev_conn_t>& prev_conns) + template <typename Reg> + connection_t register_reaction( + Reg reg, outer_func_variant_t outer_f_var, std::list<prev_conn_t>& prev_conns) { + auto f_var = to_inner(outer_f_var); auto conns = rng - | ::ranges::views::transform([reg, f](auto&& r){ - return coruja::any_connection{reg(f, std::forward<decltype(r)>(r))}; + | ::ranges::views::transform([reg, f_var](auto&& r){ + return std::visit([reg, &r](auto f) { + return coruja::any_connection{reg(f, std::forward<decltype(r)>(r))}; + }, f_var); }) | ::ranges::to_vector; auto shr_conn = coruja::make_shared_connection<coruja::dyn_any_connections>(std::move(conns)); - prev_conns.push_back(prev_conn_t{shr_conn, f}); + prev_conns.push_back(prev_conn_t{shr_conn, f_var}); return shr_conn; } diff --git a/test/join.cpp b/test/join.cpp index 38ccf3d..8641e1f 100644 --- a/test/join.cpp +++ b/test/join.cpp @@ -1,23 +1,45 @@ #define BOOST_TEST_MODULE CORUJA #include <boost/test/unit_test.hpp> +#include <range/v3/iterator/operations.hpp> #include <coruja/ranges/container/list.hpp> #include <coruja/ranges/view/filter.hpp> +#include <coruja/ranges/view/indirect.hpp> #include <coruja/ranges/view/join.hpp> #include <coruja/ranges/view/transform.hpp> -#include <coruja/ranges/view/indirect.hpp> +#include <range/v3/iterator/operations.hpp> #include <range/v3/view/addressof.hpp> #include <range/v3/view/any_view.hpp> #include <range/v3/view/indirect.hpp> #include <range/v3/view/join.hpp> -BOOST_AUTO_TEST_CASE(NonObservableList_Of_ObservableList) +struct JoinFixture { + std::list<coruja::list<int>*> list_of_olistptr; + coruja::list<coruja::list<int>*> olist_of_olistptr; + coruja::list<coruja::list<int>> olist_of_olist; coruja::list<int> l1{1, 2, 3}; coruja::list<int> l2{4, 5, 6}; coruja::list<int> l3{7, 8, 9}; - std::list<coruja::list<int>*> listlist {&l1, &l2, &l3}; - auto dereffed = listlist | ranges::views::indirect; + coruja::list<int> l4{-9}; + coruja::list<int> &rl1, &rl2, &rl3; + + JoinFixture() : + list_of_olistptr{&l1, &l2, &l3}, + olist_of_olistptr{&l1, &l2, &l3}, + rl1{olist_of_olist.emplace_back()}, + rl2{olist_of_olist.emplace_back()}, + rl3{olist_of_olist.emplace_back()} + { + rl1 = {1, 2, 3}; + rl2 = {4, 5, 6}; + rl3 = {7, 8, 9}; + } +}; + +BOOST_FIXTURE_TEST_CASE(NonObservableList_Of_ObservableList, JoinFixture) +{ + auto dereffed = list_of_olistptr | ranges::views::indirect; auto flattened = dereffed | coruja::ranges::view::join; std::vector expected{1, 2, 3, 4, 5, 6, 7, 8, 9}; @@ -55,47 +77,56 @@ BOOST_AUTO_TEST_CASE(NonObservableList_Of_ObservableList) BOOST_TEST(lastGt10Reaction == 11); } -BOOST_AUTO_TEST_CASE(ObservableList_Of_ObservableList) +BOOST_FIXTURE_TEST_CASE(ObservableList_Of_ObservableList, JoinFixture) { - coruja::list<int> l1{1, 2, 3}; - coruja::list<coruja::list<int>*> listlist{&l1}; - auto dereffed = listlist | coruja::ranges::view::indirect; - + auto dereffed = olist_of_olistptr | coruja::ranges::view::indirect; auto flattened = dereffed | coruja::ranges::view::join; int lastReaction = 0; - auto conn = flattened.for_each([&](int n){ lastReaction = n; }); - int beforeEraseLastReaction = 0; - auto be_conn = flattened.before_erase([&](int n){ beforeEraseLastReaction = n; }); - coruja::list<int> l2{4}; - listlist.push_back(&l2); + { + // escopo para garantir que as conexões serão destruÃdas antes da + // destruição do JoinFixture e evitar que qualquer reação seja + // disparada durante a chamada do destrutor - BOOST_TEST(lastReaction == 4); + auto conn = flattened.for_each([&](int n){ lastReaction = n; }); + auto be_conn = flattened.before_erase([&](int n){ beforeEraseLastReaction = n; }); - l2.remove(4); - - BOOST_TEST(beforeEraseLastReaction == 4); -} + olist_of_olistptr.push_back(&l4); -BOOST_AUTO_TEST_CASE(ApplyDirectlyOnContainer) -{ - coruja::list<coruja::list<int>> ll; + BOOST_TEST(lastReaction == -9); - coruja::list<int> l1_temp{1, 2, 3}; - [[maybe_unused]] auto& l1 = ll.emplace_back(std::move(l1_temp)); + l4.remove(-9); - coruja::list<int> l2_temp{4, 5, 6}; - [[maybe_unused]] auto& l2 = ll.emplace_back(std::move(l2_temp)); + BOOST_TEST(beforeEraseLastReaction == -9); + } +} - auto flattened = ll | coruja::ranges::view::join; - std::vector<int> expected{{1, 2, 3, 4, 5, 6}}; +BOOST_FIXTURE_TEST_CASE(ApplyDirectlyOnContainer, JoinFixture) +{ + auto flattened = olist_of_olist | coruja::ranges::view::join; + std::vector<int> expected{{1, 2, 3, 4, 5, 6, 7, 8, 9}}; BOOST_TEST(flattened == expected, boost::test_tools::per_element()); int lastReaction = 0; auto conn = flattened.for_each([&](int n){ lastReaction = n; }); - l1.push_back(10); + rl1.push_back(10); BOOST_TEST(lastReaction == 10); } + +BOOST_FIXTURE_TEST_CASE(Alternative_ForEach_Overload, JoinFixture) +{ + auto flattened = olist_of_olist | coruja::ranges::view::join; + + int rngSize = 0; + int value = 0; + auto conn = flattened.for_each([&](auto&& r, auto it){ + rngSize = ranges::distance(r); + value = *it; + }); + + BOOST_TEST(rngSize == ranges::distance(flattened)); + BOOST_TEST(value == 9); +} -- GitLab From f94576af40a6df9c4008e7ff4f88365078066339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luiz=20Rom=C3=A1rio=20Santana=20Rios?= <luizromario@tecgraf.puc-rio.br> Date: Fri, 14 Feb 2025 15:48:15 -0300 Subject: [PATCH 6/6] =?UTF-8?q?[VTOD-88015][VTOD-87860]=20Reagir=20=C3=A0?= =?UTF-8?q?=20remo=C3=A7=C3=A3o=20de=20cont=C3=AAineres?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/coruja/ranges/view/join.hpp | 63 +++++++++++++++++++++++------ test/join.cpp | 23 +++++++++-- 2 files changed, 71 insertions(+), 15 deletions(-) diff --git a/include/coruja/ranges/view/join.hpp b/include/coruja/ranges/view/join.hpp index f55ccd2..3b8b993 100644 --- a/include/coruja/ranges/view/join.hpp +++ b/include/coruja/ranges/view/join.hpp @@ -30,10 +30,19 @@ class join_view : public ::ranges::join_view<Rng> using base = ::ranges::join_view<Rng>; using connection_t = coruja::shared_connection<coruja::dyn_any_connections>; +public: + using value_type = ::ranges::range_value_t<base>; + +private: + using inner_t = typename ::ranges::range_value_t<Rng>; + using inner_it_t = typename inner_t::iterator; + + using value_function_t = std::function<void(value_type&)>; + using inner_iterator_function_t = std::function<void(inner_t&, inner_it_t)>; + public: using for_each_connection_t = connection_t; using before_erase_connection_t = connection_t; - using value_type = ::ranges::range_value_t<base>; join_view(Rng rng) : base{rng}, @@ -41,7 +50,7 @@ public: { if constexpr(coruja::is_observable_erasable_range<Rng>::value) { - auto conn = rng.for_each([this](auto& r) + auto for_each_conn = rng.for_each([this](auto& r) { register_prev_conns_for( [&r](auto f){ return r.for_each(f); }, _for_each_conns); @@ -49,11 +58,44 @@ public: [&r](auto f){ return r.before_erase(f); }, _before_erase_conns); }); - _rng_conn = std::make_shared<coruja::scoped_any_connection>(std::move(conn)); + _for_each_rng_conn = + std::make_shared<coruja::scoped_any_connection>(std::move(for_each_conn)); - // FIXME não reagimos à remoção de um dos contêineres externos. - // devemos chamar todas as reações de before_erase cadastradas - // para cada elemento da range interna sendo removida + auto before_erase_conn = rng.before_erase([this](auto& r) + { + for(auto it = _before_erase_conns.begin(); it != _before_erase_conns.end();) + { + auto& c = *it; + auto prev = it++; + + if(c.ptr.expired()) + { + _before_erase_conns.erase(prev); + continue; + } + + struct visit_function + { + void operator()(value_function_t& f) + { + for(auto& v : r) f(v); + } + + void operator()(inner_iterator_function_t& f) + { + for(auto it = r.begin(); it != r.end(); ++it) + f(r, it); + } + + inner_t& r; + }; + + std::visit(visit_function{r}, c.function); + } + }); + + _before_erase_rng_conn = + std::make_shared<coruja::scoped_any_connection>(std::move(before_erase_conn)); } } @@ -72,11 +114,6 @@ public: } private: - using inner_t = typename ::ranges::range_value_t<Rng>; - using inner_it_t = typename inner_t::iterator; - using value_function_t = std::function<void(value_type&)>; - using inner_iterator_function_t = std::function<void(inner_t&, inner_it_t)>; - using inner_func_variant_t = std::variant< value_function_t, inner_iterator_function_t>; @@ -182,7 +219,9 @@ private: Rng rng; std::list<prev_conn_t> _for_each_conns, _before_erase_conns; - std::shared_ptr<coruja::scoped_any_connection> _rng_conn; + + std::shared_ptr<coruja::scoped_any_connection> + _for_each_rng_conn, _before_erase_rng_conn; }; struct join_fn : ::ranges::pipeable_base diff --git a/test/join.cpp b/test/join.cpp index 8641e1f..4108df5 100644 --- a/test/join.cpp +++ b/test/join.cpp @@ -21,7 +21,7 @@ struct JoinFixture coruja::list<int> l1{1, 2, 3}; coruja::list<int> l2{4, 5, 6}; coruja::list<int> l3{7, 8, 9}; - coruja::list<int> l4{-9}; + coruja::list<int> l4{-10, -9}; coruja::list<int> &rl1, &rl2, &rl3; JoinFixture() : @@ -84,14 +84,25 @@ BOOST_FIXTURE_TEST_CASE(ObservableList_Of_ObservableList, JoinFixture) int lastReaction = 0; int beforeEraseLastReaction = 0; + // int beforeEraseItLastReaction = 0; { // escopo para garantir que as conexões serão destruÃdas antes da // destruição do JoinFixture e evitar que qualquer reação seja // disparada durante a chamada do destrutor - auto conn = flattened.for_each([&](int n){ lastReaction = n; }); - auto be_conn = flattened.before_erase([&](int n){ beforeEraseLastReaction = n; }); + auto conn = flattened.for_each([&](int n){ + lastReaction = n; + }); + + auto be_conn = flattened.before_erase([&](int n){ + beforeEraseLastReaction = n; + }); + + // // FIXME before_erase iterador causa abort no destrutor da fixture + // auto bei_conn = flattened.before_erase([&](auto&, auto it){ + // beforeEraseItLastReaction = *it; + // }); olist_of_olistptr.push_back(&l4); @@ -100,6 +111,12 @@ BOOST_FIXTURE_TEST_CASE(ObservableList_Of_ObservableList, JoinFixture) l4.remove(-9); BOOST_TEST(beforeEraseLastReaction == -9); + // BOOST_TEST(beforeEraseItLastReaction == -9); + + olist_of_olistptr.remove(&l4); + + BOOST_TEST(beforeEraseLastReaction == -10); + // BOOST_TEST(beforeEraseItLastReaction == -10); } } -- GitLab