From c3e4fad12e427e3a1eed7a817a0b02f1e007ff49 Mon Sep 17 00:00:00 2001
From: Charles Bousseau <cbousseau@anaconda.com>
Date: Tue, 9 Sep 2025 16:12:07 -0400
Subject: [PATCH] Revert "Use range in Solution (#3968)"

This reverts commit f771e520f9758ae811c4103a68f9cf106bb7dee7.
https://github.com/mamba-org/mamba/pull/3968

This PR is marked as not being strictly necessary.
https://github.com/mamba-org/mamba/pull/3968#issuecomment-2948922329

The change is unfortunately incompatible with our current compiler on linux.

---
 .github/workflows/brew.yml                    |   2 +-
 cmake/CompilerWarnings.cmake                  |   4 +-
 libmamba/include/mamba/solver/solution.hpp    | 188 ++++++---------
 libmamba/src/core/transaction.cpp             | 222 ++++++++++--------
 libmamba/src/solver/helpers.cpp               |  23 +-
 libmamba/tests/src/solver/test_solution.cpp   | 116 ++++-----
 libmambapy/src/libmambapy/bindings/solver.cpp |  36 ++-
 7 files changed, 270 insertions(+), 321 deletions(-)

diff --git a/.github/workflows/brew.yml b/.github/workflows/brew.yml
index faeaeee7..2a451fde 100644
--- a/.github/workflows/brew.yml
+++ b/.github/workflows/brew.yml
@@ -29,7 +29,7 @@ jobs:
 
   build_homebrew:
     name: Build on homebrew
-    runs-on: macos-15
+    runs-on: macos-13
 
     steps:
       - name: Checkout mamba repository
diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake
index 8deff360..c9e5d327 100644
--- a/cmake/CompilerWarnings.cmake
+++ b/cmake/CompilerWarnings.cmake
@@ -97,8 +97,8 @@ function(mamba_target_add_compile_warnings target)
         -Wconversion
         # Warn on sign conversions
         -Wsign-conversion
-        # Warn if a null dereference is detected Deactivated because it produced too many false
-        # positive with ranges while we are not doing many pointer operations. -Wnull-dereference
+        # Warn if a null dereference is detected
+        -Wnull-dereference
         # Warn if float is implicit promoted to double
         -Wdouble-promotion
         # Warn on security issues around functions that format output (ie printf)
diff --git a/libmamba/include/mamba/solver/solution.hpp b/libmamba/include/mamba/solver/solution.hpp
index 8cc04c52..9f52587f 100644
--- a/libmamba/include/mamba/solver/solution.hpp
+++ b/libmamba/include/mamba/solver/solution.hpp
@@ -7,12 +7,12 @@
 #ifndef MAMBA_CORE_SOLUTION_HPP
 #define MAMBA_CORE_SOLUTION_HPP
 
-#include <ranges>
 #include <type_traits>
 #include <variant>
 #include <vector>
 
 #include "mamba/specs/package_info.hpp"
+#include "mamba/util/loop_control.hpp"
 #include "mamba/util/type_traits.hpp"
 
 namespace mamba::solver
@@ -67,39 +67,22 @@ namespace mamba::solver
         using action_list = std::vector<Action>;
 
         action_list actions = {};
+    };
 
-        /**
-         * Return a view of all unique packages involved in the solution.
-         *
-         * The view is invalidated if @ref actions is modified.
-         */
-        [[nodiscard]] auto packages() const;
-        [[nodiscard]] auto packages();
-
-        /**
-         * Return a view of all packages that need to be removed.
-         *
-         * The view is invalidated if @ref actions is modified.
-         */
-        [[nodiscard]] auto packages_to_remove() const;
-        [[nodiscard]] auto packages_to_remove();
+    template <typename Iter, typename UnaryFunc>
+    void for_each_to_remove(Iter first, Iter last, UnaryFunc&& func);
+    template <typename Range, typename UnaryFunc>
+    void for_each_to_remove(Range&& actions, UnaryFunc&& func);
 
-        /**
-         * Return a view of all packages that need to be installed.
-         *
-         * The view is invalidated if @ref actions is modified.
-         */
-        [[nodiscard]] auto packages_to_install() const;
-        [[nodiscard]] auto packages_to_install();
+    template <typename Iter, typename UnaryFunc>
+    void for_each_to_install(Iter first, Iter last, UnaryFunc&& func);
+    template <typename Range, typename UnaryFunc>
+    void for_each_to_install(Range&& actions, UnaryFunc&& func);
 
-        /**
-         * Return a view of all packages that are omitted.
-         *
-         * The view is invalidated if @ref actions is modified.
-         */
-        [[nodiscard]] auto packages_to_omit() const;
-        [[nodiscard]] auto packages_to_omit();
-    };
+    template <typename Iter, typename UnaryFunc>
+    void for_each_to_omit(Iter first, Iter last, UnaryFunc&& func);
+    template <typename Range, typename UnaryFunc>
+    void for_each_to_omit(Range&& actions, UnaryFunc&& func);
 
     /********************************
      *  Implementation of Solution  *
@@ -132,27 +115,35 @@ namespace mamba::solver
                 action
             );
         }
-
-        template <std::ranges::range Range>
-        auto packages_to_remove_impl(Range& actions)
-        {
-            namespace views = std::ranges::views;
-            return actions  //
-                   | views::transform([](auto& a) { return detail::to_remove_ptr(a); })
-                   | views::filter([](const auto* ptr) { return ptr != nullptr; })
-                   | views::transform([](auto* ptr) -> decltype(auto) { return *ptr; });
-        }
-
     }
 
-    inline auto Solution::packages_to_remove() const
+    // TODO(C++20): Poor man's replacement to range filter transform
+    template <typename Iter, typename UnaryFunc>
+    void for_each_to_remove(Iter first, Iter last, UnaryFunc&& func)
     {
-        return detail::packages_to_remove_impl(actions);
+        for (; first != last; ++first)
+        {
+            if (auto* const ptr = detail::to_remove_ptr(*first))
+            {
+                if constexpr (std::is_same_v<decltype(func(*ptr)), util::LoopControl>)
+                {
+                    if (func(*ptr) == util::LoopControl::Break)
+                    {
+                        break;
+                    }
+                }
+                else
+                {
+                    func(*ptr);
+                }
+            }
+        }
     }
 
-    inline auto Solution::packages_to_remove()
+    template <typename Range, typename UnaryFunc>
+    void for_each_to_remove(Range&& actions, UnaryFunc&& func)
     {
-        return detail::packages_to_remove_impl(actions);
+        return for_each_to_remove(actions.begin(), actions.end(), std::forward<UnaryFunc>(func));
     }
 
     namespace detail
@@ -182,27 +173,35 @@ namespace mamba::solver
                 action
             );
         }
-
-        template <std::ranges::range Range>
-        auto packages_to_install_impl(Range& actions)
-        {
-            namespace views = std::ranges::views;
-            return actions  //
-                   | views::transform([](auto& a) { return detail::to_install_ptr(a); })
-                   | views::filter([](const auto* ptr) { return ptr != nullptr; })
-                   | views::transform([](auto* ptr) -> decltype(auto) { return *ptr; });
-        }
-
     }
 
-    inline auto Solution::packages_to_install() const
+    template <typename Iter, typename UnaryFunc>
+    void for_each_to_install(Iter first, Iter last, UnaryFunc&& func)
     {
-        return detail::packages_to_install_impl(actions);
+        for (; first != last; ++first)
+        {
+            if (auto* const ptr = detail::to_install_ptr(*first))
+            {
+                if constexpr (std::is_same_v<decltype(func(*ptr)), util::LoopControl>)
+                {
+                    if (func(*ptr) == util::LoopControl::Break)
+                    {
+                        break;
+                    }
+                }
+                else
+                {
+                    func(*ptr);
+                }
+            }
+        }
     }
 
-    inline auto Solution::packages_to_install()
+    // TODO(C++20): Poor man's replacement to range filter transform
+    template <typename Range, typename UnaryFunc>
+    void for_each_to_install(Range&& actions, UnaryFunc&& func)
     {
-        return detail::packages_to_install_impl(actions);
+        return for_each_to_install(actions.begin(), actions.end(), std::forward<UnaryFunc>(func));
     }
 
     namespace detail
@@ -228,72 +227,35 @@ namespace mamba::solver
                 action
             );
         }
-
-        template <std::ranges::range Range>
-        auto packages_to_omit_impl(Range& actions)
-        {
-            namespace views = std::ranges::views;
-            return actions  //
-                   | views::transform([](auto& a) { return detail::to_omit_ptr(a); })
-                   | views::filter([](const auto* ptr) { return ptr != nullptr; })
-                   | views::transform([](auto* ptr) -> decltype(auto) { return *ptr; });
-        }
-
-    }
-
-    inline auto Solution::packages_to_omit() const
-    {
-        return detail::packages_to_omit_impl(actions);
     }
 
-    inline auto Solution::packages_to_omit()
+    // TODO(C++20): Poor man's replacement to range filter transform
+    template <typename Iter, typename UnaryFunc>
+    void for_each_to_omit(Iter first, Iter last, UnaryFunc&& func)
     {
-        return detail::packages_to_omit_impl(actions);
-    }
-
-    namespace detail
-    {
-        template <typename Action>
-        constexpr auto package_unique_ptrs(Action& action)
+        for (; first != last; ++first)
         {
-            auto out = std::array{
-                to_omit_ptr(action),
-                to_install_ptr(action),
-                to_remove_ptr(action),
-            };
-            for (std::size_t i = 1; i < out.size(); ++i)
+            if (auto* const ptr = detail::to_omit_ptr(*first))
             {
-                for (std::size_t j = i + 1; j < out.size(); ++j)
+                if constexpr (std::is_same_v<decltype(func(*ptr)), util::LoopControl>)
                 {
-                    if (out[j] == out[i])
+                    if (func(*ptr) == util::LoopControl::Break)
                     {
-                        out[j] = nullptr;
+                        break;
                     }
                 }
+                else
+                {
+                    func(*ptr);
+                }
             }
-            return out;
         }
-
-        template <std::ranges::range Range>
-        auto packages_impl(Range& actions)
-        {
-            namespace views = std::ranges::views;
-            return actions                                                             //
-                   | views::transform([](auto& a) { return package_unique_ptrs(a); })  //
-                   | views::join                                                       //
-                   | views::filter([](const auto* ptr) { return ptr != nullptr; })     //
-                   | views::transform([](auto* ptr) -> decltype(auto) { return *ptr; });
-        }
-    }
-
-    inline auto Solution::packages() const
-    {
-        return detail::packages_impl(actions);
     }
 
-    inline auto Solution::packages()
+    template <typename Range, typename UnaryFunc>
+    void for_each_to_omit(Range&& actions, UnaryFunc&& func)
     {
-        return detail::packages_impl(actions);
+        return for_each_to_omit(actions.begin(), actions.end(), std::forward<UnaryFunc>(func));
     }
 }
 #endif
diff --git a/libmamba/src/core/transaction.cpp b/libmamba/src/core/transaction.cpp
index 9abb58fe..486c8715 100644
--- a/libmamba/src/core/transaction.cpp
+++ b/libmamba/src/core/transaction.cpp
@@ -7,7 +7,6 @@
 #include <algorithm>
 #include <iostream>
 #include <iterator>
-#include <ranges>
 #include <stack>
 #include <string>
 #include <utility>
@@ -251,13 +250,16 @@ namespace mamba
         else
         {
             // The specs to install become all the dependencies of the non intstalled specs
-            for (const specs::PackageInfo& pkg : m_solution.packages_to_omit())
-            {
-                for (const auto& dep : pkg.dependencies)
+            for_each_to_omit(
+                m_solution.actions,
+                [&](const specs::PackageInfo& pkg)
                 {
-                    m_history_entry.update.push_back(dep);
+                    for (const auto& dep : pkg.dependencies)
+                    {
+                        m_history_entry.update.push_back(dep);
+                    }
                 }
-            }
+            );
         }
 
         using Request = solver::Request;
@@ -396,7 +398,7 @@ namespace mamba
         // to be URL like (i.e. explicit). Below is a loop to fix the channel of the linked
         // packages (fix applied to the unlinked packages to avoid potential bugs). Ideally, this
         // should be normalised when reading the data.
-        for (specs::PackageInfo& pkg : m_solution.packages())
+        const auto fix_channel = [&](specs::PackageInfo& pkg)
         {
             auto unresolved_pkg_channel = mamba::specs::UnresolvedChannel::parse(pkg.channel).value();
             auto pkg_channel = mamba::specs::Channel::resolve(
@@ -407,6 +409,9 @@ namespace mamba
             auto channel_url = pkg_channel[0].platform_url(pkg.platform).str();
             pkg.channel = channel_url;
         };
+        for_each_to_install(m_solution.actions, fix_channel);
+        for_each_to_remove(m_solution.actions, fix_channel);
+        for_each_to_omit(m_solution.actions, fix_channel);
 
         TransactionRollback rollback;
         TransactionContext transaction_context(
@@ -416,11 +421,25 @@ namespace mamba
             m_requested_specs
         );
 
-        for (const specs::PackageInfo& pkg : m_solution.packages_to_remove())
+        const auto link = [&](const specs::PackageInfo& pkg)
         {
             if (is_sig_interrupted())
             {
-                break;
+                return util::LoopControl::Break;
+            }
+            Console::stream() << "Linking " << pkg.str();
+            const fs::u8path cache_path(m_multi_cache.get_extracted_dir_path(pkg, false));
+            LinkPackage lp(pkg, cache_path, &transaction_context);
+            lp.execute();
+            rollback.record(lp);
+            m_history_entry.link_dists.push_back(pkg.long_str());
+            return util::LoopControl::Continue;
+        };
+        const auto unlink = [&](const specs::PackageInfo& pkg)
+        {
+            if (is_sig_interrupted())
+            {
+                return util::LoopControl::Break;
             }
             Console::stream() << "Unlinking " << pkg.str();
             const fs::u8path cache_path(m_multi_cache.get_extracted_dir_path(pkg));
@@ -428,21 +447,11 @@ namespace mamba
             up.execute();
             rollback.record(up);
             m_history_entry.unlink_dists.push_back(pkg.long_str());
-        }
+            return util::LoopControl::Continue;
+        };
 
-        for (const specs::PackageInfo& pkg : m_solution.packages_to_install())
-        {
-            if (is_sig_interrupted())
-            {
-                break;
-            }
-            Console::stream() << "Linking " << pkg.str();
-            const fs::u8path cache_path(m_multi_cache.get_extracted_dir_path(pkg, false));
-            LinkPackage lp(pkg, cache_path, &transaction_context);
-            lp.execute();
-            rollback.record(lp);
-            m_history_entry.link_dists.push_back(pkg.long_str());
-        }
+        for_each_to_remove(m_solution.actions, unlink);
+        for_each_to_install(m_solution.actions, link);
 
         if (is_sig_interrupted())
         {
@@ -461,29 +470,25 @@ namespace mamba
 
     auto MTransaction::to_conda() -> to_conda_type
     {
-        namespace views = std::ranges::views;
-
-        auto to_remove_range = m_solution.packages_to_remove()  //
-                               | views::transform(
-                                   [](const auto& pkg)
-                                   { return to_remove_type::value_type(pkg.channel, pkg.filename); }
-                               );
-        // TODO(C++23): std::ranges::to
-        auto to_remove_structured = to_remove_type(to_remove_range.begin(), to_remove_range.end());
-
-        auto to_install_range = m_solution.packages_to_install()  //
-                                | views::transform(
-                                    [](const auto& pkg)
-                                    {
-                                        return to_install_type::value_type(
-                                            pkg.channel,
-                                            pkg.filename,
-                                            nl::json(pkg).dump(4)
-                                        );
-                                    }
-                                );
-        // TODO(C++23): std::ranges::to
-        auto to_install_structured = to_install_type(to_install_range.begin(), to_install_range.end());
+        to_remove_type to_remove_structured = {};
+        to_remove_structured.reserve(m_solution.actions.size());  // Upper bound
+        for_each_to_remove(
+            m_solution.actions,
+            [&](const auto& pkg)
+            {
+                to_remove_structured.emplace_back(pkg.channel, pkg.filename);  //
+            }
+        );
+
+        to_install_type to_install_structured = {};
+        to_install_structured.reserve(m_solution.actions.size());  // Upper bound
+        for_each_to_install(
+            m_solution.actions,
+            [&](const auto& pkg)
+            {
+                to_install_structured.emplace_back(pkg.channel, pkg.filename, nl::json(pkg).dump(4));  //
+            }
+        );
 
         to_specs_type specs;
         std::get<0>(specs) = m_history_entry.update;
@@ -494,22 +499,27 @@ namespace mamba
 
     void MTransaction::log_json()
     {
-        namespace views = std::ranges::views;
-
-        // TODO(C++23): std::ranges::to
-        auto to_fetch_range = m_solution.packages_to_install()
-                              | views::filter([this](const auto& pkg)
-                                              { return need_pkg_download(pkg, m_multi_cache); });
-        auto to_fetch = std::vector<nl::json>(to_fetch_range.begin(), to_fetch_range.end());
-
+        std::vector<nl::json> to_fetch, to_link, to_unlink;
 
-        // TODO(C++23): std::ranges::to
-        auto to_link_range = m_solution.packages_to_install();
-        auto to_link = std::vector<nl::json>(to_link_range.begin(), to_link_range.end());
+        for_each_to_install(
+            m_solution.actions,
+            [&](const auto& pkg)
+            {
+                if (need_pkg_download(pkg, m_multi_cache))
+                {
+                    to_fetch.push_back(nl::json(pkg));
+                }
+                to_link.push_back(nl::json(pkg));
+            }
+        );
 
-        // TODO(C++23): std::ranges::to
-        auto to_unlink_range = m_solution.packages_to_remove();
-        auto to_unlink = std::vector<nl::json>(to_unlink_range.begin(), to_unlink_range.end());
+        for_each_to_remove(
+            m_solution.actions,
+            [&](const auto& pkg)
+            {
+                to_unlink.push_back(nl::json(pkg));  //
+            }
+        );
 
         auto add_json = [](const auto& jlist, const char* s)
         {
@@ -549,59 +559,67 @@ namespace mamba
             {
                 LOG_INFO << "Content trust is enabled, package(s) signatures will be verified";
             }
-            for (const auto& pkg : solution.packages_to_install())
-            {
-                if (ctx.validation_params.verify_artifacts)
+            for_each_to_install(
+                solution.actions,
+                [&](const auto& pkg)
                 {
-                    LOG_INFO << "Creating RepoChecker...";
-                    auto repo_checker_store = RepoCheckerStore::make(ctx, channel_context, multi_cache);
-                    for (auto& chan : channel_context.make_channel(pkg.channel))
+                    if (ctx.validation_params.verify_artifacts)
                     {
-                        auto repo_checker = repo_checker_store.find_checker(chan);
-                        if (repo_checker)
-                        {
-                            LOG_INFO << "RepoChecker successfully created.";
-                            repo_checker->generate_index_checker();
-                            repo_checker->verify_package(
-                                pkg.json_signable(),
-                                std::string_view(pkg.signatures)
-                            );
-                        }
-                        else
+                        LOG_INFO << "Creating RepoChecker...";
+                        auto repo_checker_store = RepoCheckerStore::make(
+                            ctx,
+                            channel_context,
+                            multi_cache
+                        );
+                        for (auto& chan : channel_context.make_channel(pkg.channel))
                         {
-                            LOG_ERROR << "Could not create a valid RepoChecker.";
-                            throw std::runtime_error(fmt::format(
-                                R"(Could not verify "{}". Please make sure the package signatures are available and 'trusted-channels' are configured correctly. Alternatively, try downloading without '--verify-artifacts' flag.)",
-                                pkg.name
-                            ));
+                            auto repo_checker = repo_checker_store.find_checker(chan);
+                            if (repo_checker)
+                            {
+                                LOG_INFO << "RepoChecker successfully created.";
+                                repo_checker->generate_index_checker();
+                                repo_checker->verify_package(
+                                    pkg.json_signable(),
+                                    std::string_view(pkg.signatures)
+                                );
+                            }
+                            else
+                            {
+                                LOG_ERROR << "Could not create a valid RepoChecker.";
+                                throw std::runtime_error(fmt::format(
+                                    R"(Could not verify "{}". Please make sure the package signatures are available and 'trusted-channels' are configured correctly. Alternatively, try downloading without '--verify-artifacts' flag.)",
+                                    pkg.name
+                                ));
+                            }
                         }
+                        LOG_INFO << "'" << pkg.name << "' trusted from '" << pkg.channel << "'";
                     }
-                    LOG_INFO << "'" << pkg.name << "' trusted from '" << pkg.channel << "'";
-                }
 
-                // FIXME: only do this for micromamba for now
-                if (ctx.command_params.is_mamba_exe)
-                {
-                    using Credentials = typename specs::CondaURL::Credentials;
-                    auto l_pkg = pkg;
+                    // FIXME: only do this for micromamba for now
+                    if (ctx.command_params.is_mamba_exe)
                     {
-                        auto channels = channel_context.make_channel(pkg.package_url);
-                        assert(channels.size() == 1);  // A URL can only resolve to one channel
-                        l_pkg.package_url = channels.front().platform_urls().at(0).str(Credentials::Show
-                        );
+                        using Credentials = typename specs::CondaURL::Credentials;
+                        auto l_pkg = pkg;
+                        {
+                            auto channels = channel_context.make_channel(pkg.package_url);
+                            assert(channels.size() == 1);  // A URL can only resolve to one channel
+                            l_pkg.package_url = channels.front().platform_urls().at(0).str(
+                                Credentials::Show
+                            );
+                        }
+                        {
+                            auto channels = channel_context.make_channel(pkg.channel);
+                            assert(channels.size() == 1);  // A URL can only resolve to one channel
+                            l_pkg.channel = channels.front().id();
+                        }
+                        fetchers.emplace_back(l_pkg, multi_cache);
                     }
+                    else
                     {
-                        auto channels = channel_context.make_channel(pkg.channel);
-                        assert(channels.size() == 1);  // A URL can only resolve to one channel
-                        l_pkg.channel = channels.front().id();
+                        fetchers.emplace_back(pkg, multi_cache);
                     }
-                    fetchers.emplace_back(l_pkg, multi_cache);
-                }
-                else
-                {
-                    fetchers.emplace_back(pkg, multi_cache);
                 }
-            }
+            );
 
             if (ctx.validation_params.verify_artifacts)
             {
diff --git a/libmamba/src/solver/helpers.cpp b/libmamba/src/solver/helpers.cpp
index e1b7457f..9a2d826c 100644
--- a/libmamba/src/solver/helpers.cpp
+++ b/libmamba/src/solver/helpers.cpp
@@ -4,8 +4,6 @@
 //
 // The full license is in the file LICENSE, distributed with this software.
 
-#include <ranges>
-
 #include "solver/helpers.hpp"
 
 namespace mamba::solver
@@ -13,13 +11,20 @@ namespace mamba::solver
     auto find_new_python_in_solution(const Solution& solution)
         -> std::optional<std::reference_wrapper<const specs::PackageInfo>>
     {
-        auto packages = solution.packages_to_install();
-        auto it = std::ranges::find_if(packages, [](const auto& pkg) { return pkg.name == "python"; });
-        if (it != packages.end())
-        {
-            return std::cref(*it);
-        }
-        return std::nullopt;
+        auto out = std::optional<std::reference_wrapper<const specs::PackageInfo>>{};
+        for_each_to_install(
+            solution.actions,
+            [&](const auto& pkg)
+            {
+                if (pkg.name == "python")
+                {
+                    out = std::cref(pkg);
+                    return util::LoopControl::Break;
+                }
+                return util::LoopControl::Continue;
+            }
+        );
+        return out;
     }
 
     auto python_binary_compatible(const specs::Version& older, const specs::Version& newer) -> bool
diff --git a/libmamba/tests/src/solver/test_solution.cpp b/libmamba/tests/src/solver/test_solution.cpp
index 1031295b..1e511709 100644
--- a/libmamba/tests/src/solver/test_solution.cpp
+++ b/libmamba/tests/src/solver/test_solution.cpp
@@ -29,108 +29,80 @@ namespace
             Solution::Install{ PackageInfo("install") },
         } };
 
-        constexpr auto as_const = [](const auto& x) -> decltype(auto) { return x; };
-
-        SECTION("Const iterate over packages")
+        SECTION("Iterate over packages")
         {
-            SECTION("Packages to remove")
-            {
-                auto remove_count = std::size_t(0);
-                for (const PackageInfo& pkg : as_const(solution).packages_to_remove())
+            auto remove_count = std::size_t(0);
+            for_each_to_remove(
+                solution.actions,
+                [&](const PackageInfo& pkg)
                 {
                     remove_count++;
                     const auto has_remove = util::ends_with(pkg.name, "remove")
                                             || (pkg.name == "reinstall");
                     REQUIRE(has_remove);
                 }
-                REQUIRE(remove_count == 5);
-            }
+            );
+            REQUIRE(remove_count == 5);
 
-            SECTION("Packages to install")
-            {
-                auto install_count = std::size_t(0);
-                for (const PackageInfo& pkg : as_const(solution).packages_to_install())
+            auto install_count = std::size_t(0);
+            for_each_to_install(
+                solution.actions,
+                [&](const PackageInfo& pkg)
                 {
                     install_count++;
                     const auto has_install = util::ends_with(pkg.name, "install")
                                              || (pkg.name == "reinstall");
                     REQUIRE(has_install);
                 }
-                REQUIRE(install_count == 5);
-            }
+            );
+            REQUIRE(install_count == 5);
 
-            SECTION("Packages to omit")
-            {
-                auto omit_count = std::size_t(0);
-                for (const PackageInfo& pkg : as_const(solution).packages_to_omit())
+            auto omit_count = std::size_t(0);
+            for_each_to_omit(
+                solution.actions,
+                [&](const PackageInfo& pkg)
                 {
                     omit_count++;
                     REQUIRE(util::ends_with(pkg.name, "omit"));
                 }
-                REQUIRE(omit_count == 1);
-            }
-
-            SECTION("All packages")
-            {
-                auto count = std::size_t(0);
-                for (const PackageInfo& pkg : as_const(solution).packages())
-                {
-                    count++;
-                    REQUIRE(!pkg.name.empty());
-                }
-                REQUIRE(count == 10);
-            }
+            );
+            REQUIRE(omit_count == 1);
         }
 
-        SECTION("Ref iterate over packages")
+        SECTION("Iterate over packages and break")
         {
-            SECTION("Packages to remove")
-            {
-                for (PackageInfo& pkg : solution.packages_to_remove())
-                {
-                    pkg.name = "";
-                }
-                for (const PackageInfo& pkg : solution.packages_to_remove())
-                {
-                    CHECK(pkg.name == "");
-                }
-            }
-
-            SECTION("Packages to install")
-            {
-                for (PackageInfo& pkg : solution.packages_to_install())
+            auto remove_count = std::size_t(0);
+            for_each_to_remove(
+                solution.actions,
+                [&](const PackageInfo&)
                 {
-                    pkg.name = "";
-                }
-                for (const PackageInfo& pkg : solution.packages_to_install())
-                {
-                    CHECK(pkg.name == "");
+                    remove_count++;
+                    return util::LoopControl::Break;
                 }
-            }
+            );
+            REQUIRE(remove_count == 1);
 
-            SECTION("Packages to omit")
-            {
-                for (PackageInfo& pkg : solution.packages_to_omit())
-                {
-                    pkg.name = "";
-                }
-                for (const PackageInfo& pkg : solution.packages_to_omit())
+            auto install_count = std::size_t(0);
+            for_each_to_install(
+                solution.actions,
+                [&](const PackageInfo&)
                 {
-                    CHECK(pkg.name == "");
+                    install_count++;
+                    return util::LoopControl::Break;
                 }
-            }
+            );
+            REQUIRE(install_count == 1);
 
-            SECTION("All packages")
-            {
-                for (PackageInfo& pkg : solution.packages())
-                {
-                    pkg.name = "";
-                }
-                for (const PackageInfo& pkg : solution.packages())
+            auto omit_count = std::size_t(0);
+            for_each_to_omit(
+                solution.actions,
+                [&](const PackageInfo&)
                 {
-                    CHECK(pkg.name == "");
+                    omit_count++;
+                    return util::LoopControl::Break;
                 }
-            }
+            );
+            REQUIRE(omit_count == 1);
         }
     }
 }
diff --git a/libmambapy/src/libmambapy/bindings/solver.cpp b/libmambapy/src/libmambapy/bindings/solver.cpp
index 6bc6510c..7e7176dd 100644
--- a/libmambapy/src/libmambapy/bindings/solver.cpp
+++ b/libmambapy/src/libmambapy/bindings/solver.cpp
@@ -302,44 +302,36 @@ namespace mambapy
             ))
             .def_readwrite("actions", &Solution::actions)
             .def(
-                "packages",
+                "to_install",
                 [](const Solution& solution) -> std::vector<specs::PackageInfo>
                 {
-                    // TODO(C++23): std::ranges::to
                     auto out = std::vector<specs::PackageInfo>{};
-                    out.reserve(solution.actions.size());  // Lower bound
-                    for (const auto& pkg : solution.packages())
-                    {
-                        out.push_back(pkg);
-                    }
+                    out.reserve(solution.actions.size());  // Upper bound
+                    for_each_to_install(
+                        solution.actions,
+                        [&](const auto& pkg) { out.push_back(pkg); }
+                    );
                     return out;
                 }
             )
-            .def(
-                "to_install",
-                [](const Solution& solution) -> std::vector<specs::PackageInfo>
-                {
-                    // TODO(C++23): std::ranges::to
-                    auto range = solution.packages_to_install();
-                    return { range.begin(), range.end() };
-                }
-            )
             .def(
                 "to_remove",
                 [](const Solution& solution) -> std::vector<specs::PackageInfo>
                 {
-                    // TODO(C++23): std::ranges::to
-                    auto range = solution.packages_to_remove();
-                    return { range.begin(), range.end() };
+                    auto out = std::vector<specs::PackageInfo>{};
+                    out.reserve(solution.actions.size());  // Upper bound
+                    for_each_to_remove(solution.actions, [&](const auto& pkg) { out.push_back(pkg); });
+                    return out;
                 }
             )
             .def(
                 "to_omit",
                 [](const Solution& solution) -> std::vector<specs::PackageInfo>
                 {
-                    // TODO(C++23): std::ranges::to
-                    auto range = solution.packages_to_omit();
-                    return { range.begin(), range.end() };
+                    auto out = std::vector<specs::PackageInfo>{};
+                    out.reserve(solution.actions.size());  // Upper bound
+                    for_each_to_omit(solution.actions, [&](const auto& pkg) { out.push_back(pkg); });
+                    return out;
                 }
             )
             .def("__copy__", &copy<Solution>)
-- 
2.39.5 (Apple Git-154)

