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