From c3e4fad12e427e3a1eed7a817a0b02f1e007ff49 Mon Sep 17 00:00:00 2001 From: Charles Bousseau 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 #include #include #include #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_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 + void for_each_to_remove(Iter first, Iter last, UnaryFunc&& func); + template + 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 + void for_each_to_install(Iter first, Iter last, UnaryFunc&& func); + template + 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 + void for_each_to_omit(Iter first, Iter last, UnaryFunc&& func); + template + void for_each_to_omit(Range&& actions, UnaryFunc&& func); /******************************** * Implementation of Solution * @@ -132,27 +115,35 @@ namespace mamba::solver action ); } - - template - 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 + 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) + { + if (func(*ptr) == util::LoopControl::Break) + { + break; + } + } + else + { + func(*ptr); + } + } + } } - inline auto Solution::packages_to_remove() + template + 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(func)); } namespace detail @@ -182,27 +173,35 @@ namespace mamba::solver action ); } - - template - 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 + 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) + { + 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 + 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(func)); } namespace detail @@ -228,72 +227,35 @@ namespace mamba::solver action ); } - - template - 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 + void for_each_to_omit(Iter first, Iter last, UnaryFunc&& func) { - return detail::packages_to_omit_impl(actions); - } - - namespace detail - { - template - 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) { - if (out[j] == out[i]) + if (func(*ptr) == util::LoopControl::Break) { - out[j] = nullptr; + break; } } + else + { + func(*ptr); + } } - return out; } - - template - 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 + 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(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 #include #include -#include #include #include #include @@ -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(to_fetch_range.begin(), to_fetch_range.end()); - + std::vector 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(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(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 - #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> { - 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>{}; + 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 { - // TODO(C++23): std::ranges::to auto out = std::vector{}; - 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 - { - // 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 { - // TODO(C++23): std::ranges::to - auto range = solution.packages_to_remove(); - return { range.begin(), range.end() }; + auto out = std::vector{}; + 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 { - // TODO(C++23): std::ranges::to - auto range = solution.packages_to_omit(); - return { range.begin(), range.end() }; + auto out = std::vector{}; + out.reserve(solution.actions.size()); // Upper bound + for_each_to_omit(solution.actions, [&](const auto& pkg) { out.push_back(pkg); }); + return out; } ) .def("__copy__", ©) -- 2.39.5 (Apple Git-154)