diff --git a/.uncrustify.cfg b/.uncrustify.cfg index ab316995b7..9bb2f29c10 100644 --- a/.uncrustify.cfg +++ b/.uncrustify.cfg @@ -98,7 +98,7 @@ eat_blanks_after_open_brace=true eat_blanks_before_close_brace=true mod_pawn_semicolon=false mod_full_paren_if_bool=false -mod_remove_extra_semicolon=true +mod_remove_extra_semicolon=false mod_sort_import=false mod_sort_using=false mod_sort_include=false diff --git a/src/search/cartesian_abstractions/refinement_hierarchy.cc b/src/search/cartesian_abstractions/refinement_hierarchy.cc index 7431b118ef..704f54c48c 100644 --- a/src/search/cartesian_abstractions/refinement_hierarchy.cc +++ b/src/search/cartesian_abstractions/refinement_hierarchy.cc @@ -60,7 +60,7 @@ NodeID RefinementHierarchy::get_node_id(const State &state) const { NodeID id = 0; while (nodes[id].is_split()) { const Node &node = nodes[id]; - id = node.get_child(state[node.get_var()].get_value()); + id = node.get_child(state[node.get_var()]); } return id; } diff --git a/src/search/cartesian_abstractions/subtask_generators.cc b/src/search/cartesian_abstractions/subtask_generators.cc index 0229992891..dcde99f3aa 100644 --- a/src/search/cartesian_abstractions/subtask_generators.cc +++ b/src/search/cartesian_abstractions/subtask_generators.cc @@ -52,7 +52,7 @@ static void remove_initial_state_facts( const TaskProxy &task_proxy, Facts &facts) { State initial_state = task_proxy.get_initial_state(); facts.erase(remove_if(facts.begin(), facts.end(), [&](FactPair fact) { - return initial_state[fact.var].get_value() == fact.value; + return initial_state[fact.var] == fact.value; }), facts.end()); } diff --git a/src/search/heuristics/cea_heuristic.cc b/src/search/heuristics/cea_heuristic.cc index 12aba20f85..807a5b42bf 100644 --- a/src/search/heuristics/cea_heuristic.cc +++ b/src/search/heuristics/cea_heuristic.cc @@ -228,7 +228,7 @@ void ContextEnhancedAdditiveHeuristic::set_up_local_problem( LocalProblemNode *start = &problem->nodes[start_value]; start->cost = 0; for (size_t i = 0; i < problem->context_variables->size(); ++i) - start->context[i] = state[(*problem->context_variables)[i]].get_value(); + start->context[i] = static_cast(state[(*problem->context_variables)[i]]); add_to_heap(start); } @@ -379,10 +379,10 @@ void ContextEnhancedAdditiveHeuristic::mark_helpful_transitions( int precond_value = assignment.value; int local_var = assignment.local_var; int precond_var_no = context_vars[local_var]; - if (state[precond_var_no].get_value() == precond_value) + if (state[precond_var_no] == precond_value) continue; LocalProblem *subproblem = get_local_problem( - precond_var_no, state[precond_var_no].get_value()); + precond_var_no, state[precond_var_no]); LocalProblemNode *subnode = &subproblem->nodes[precond_value]; mark_helpful_transitions(subproblem, subnode, state); } diff --git a/src/search/heuristics/cg_cache.cc b/src/search/heuristics/cg_cache.cc index 2479136d34..c431e51312 100644 --- a/src/search/heuristics/cg_cache.cc +++ b/src/search/heuristics/cg_cache.cc @@ -119,7 +119,7 @@ int CGCache::get_index(int var, const State &state, int index = from_val; int multiplier = task_proxy.get_variables()[var].get_domain_size(); for (int dep_var : depends_on[var]) { - index += state[dep_var].get_value() * multiplier; + index += state[dep_var] * multiplier; multiplier *= task_proxy.get_variables()[dep_var].get_domain_size(); } if (to_val > from_val) diff --git a/src/search/heuristics/cg_heuristic.cc b/src/search/heuristics/cg_heuristic.cc index 8ec18a3deb..d8047bb53d 100644 --- a/src/search/heuristics/cg_heuristic.cc +++ b/src/search/heuristics/cg_heuristic.cc @@ -58,7 +58,7 @@ int CGHeuristic::compute_heuristic(const State &ancestor_state) { for (FactProxy goal : task_proxy.get_goals()) { const VariableProxy var = goal.get_variable(); int var_no = var.get_id(); - int from = state[var_no].get_value(), to = goal.get_value(); + int from = state[var_no], to = goal.get_value(); DomainTransitionGraph *dtg = transition_graphs[var_no].get(); int cost_for_goal = get_transition_cost(state, dtg, from, to); if (cost_for_goal == numeric_limits::max()) { @@ -114,7 +114,7 @@ int CGHeuristic::get_transition_cost(const State &state, start->children_state.resize(dtg->local_to_global_child.size()); for (size_t i = 0; i < dtg->local_to_global_child.size(); ++i) { start->children_state[i] = - state[dtg->local_to_global_child[i]].get_value(); + state[dtg->local_to_global_child[i]]; } // Initialize Heap for Dijkstra's algorithm. @@ -226,7 +226,7 @@ int CGHeuristic::get_transition_cost(const State &state, void CGHeuristic::mark_helpful_transitions(const State &state, DomainTransitionGraph *dtg, int to) { int var_no = dtg->var; - int from = state[var_no].get_value(); + int from = state[var_no]; if (from == to) return; diff --git a/src/search/landmarks/landmark.cc b/src/search/landmarks/landmark.cc index f9fb0f787c..0a19b1b6bc 100644 --- a/src/search/landmarks/landmark.cc +++ b/src/search/landmarks/landmark.cc @@ -7,7 +7,7 @@ using namespace std; namespace landmarks { bool Landmark::is_true_in_state(const State &state) const { auto is_atom_true_in_state = [&](const FactPair &atom) { - return state[atom.var].get_value() == atom.value; + return state[atom.var] == atom.value; }; if (type == DISJUNCTIVE) { return ranges::any_of(atoms, is_atom_true_in_state); diff --git a/src/search/landmarks/landmark_factory_rpg_sasp.cc b/src/search/landmarks/landmark_factory_rpg_sasp.cc index 7608ac696e..ed23d64321 100644 --- a/src/search/landmarks/landmark_factory_rpg_sasp.cc +++ b/src/search/landmarks/landmark_factory_rpg_sasp.cc @@ -120,12 +120,12 @@ static void add_binary_variable_conditions( effect_atom.get_variable().get_domain_size() == 2) { for (const FactPair &atom : landmark.atoms) { if (atom.var == var_id && - initial_state[var_id].get_value() != atom.value) { + initial_state[var_id] != atom.value) { assert(ranges::none_of(result, [&](const FactPair &result_atom) { return result_atom.var == var_id; })); - result.insert(initial_state[var_id].get_pair()); + result.insert(initial_state.get_fact(var_id).get_pair()); break; } } @@ -530,7 +530,7 @@ void LandmarkFactoryRpgSasp::generate_disjunctive_precondition_landmarks( they should not hold in the initial state. */ if (preconditions.size() < 5 && ranges::none_of( preconditions, [&](const FactPair &atom) { - return initial_state[atom.var].get_value() == atom.value; + return initial_state[atom.var] == atom.value; })) { add_disjunctive_landmark_and_ordering( preconditions, *node, OrderingType::GREEDY_NECESSARY); @@ -654,7 +654,7 @@ void LandmarkFactoryRpgSasp::approximate_lookahead_orderings( const FactPair landmark_atom = landmark.atoms[0]; const FactPair init_atom = - task_proxy.get_initial_state()[landmark_atom.var].get_pair(); + task_proxy.get_initial_state().get_fact(landmark_atom.var).get_pair(); vector critical_predecessors = get_critical_dtg_predecessors( init_atom.value, landmark_atom.value, reached[landmark_atom.var], dtg_successors[landmark_atom.var]); diff --git a/src/search/merge_and_shrink/fts_factory.cc b/src/search/merge_and_shrink/fts_factory.cc index 48604f1680..9528b1ed79 100644 --- a/src/search/merge_and_shrink/fts_factory.cc +++ b/src/search/merge_and_shrink/fts_factory.cc @@ -118,7 +118,7 @@ unique_ptr FTSFactory::create_labels() { void FTSFactory::build_state_data(VariableProxy var) { int var_id = var.get_id(); TransitionSystemData &ts_data = transition_system_data_by_var[var_id]; - ts_data.init_state = task_proxy.get_initial_state()[var_id].get_value(); + ts_data.init_state = task_proxy.get_initial_state()[var_id]; int range = task_proxy.get_variables()[var_id].get_domain_size(); ts_data.num_states = range; diff --git a/src/search/merge_and_shrink/merge_and_shrink_representation.cc b/src/search/merge_and_shrink/merge_and_shrink_representation.cc index 18731721ca..7fcd08657f 100644 --- a/src/search/merge_and_shrink/merge_and_shrink_representation.cc +++ b/src/search/merge_and_shrink/merge_and_shrink_representation.cc @@ -58,7 +58,7 @@ void MergeAndShrinkRepresentationLeaf::apply_abstraction_to_lookup_table( } int MergeAndShrinkRepresentationLeaf::get_value(const State &state) const { - int value = state[var_id].get_value(); + int value = state[var_id]; return lookup_table[value]; } diff --git a/src/search/operator_counting/state_equation_constraints.cc b/src/search/operator_counting/state_equation_constraints.cc index 877056d8b7..51c4531185 100644 --- a/src/search/operator_counting/state_equation_constraints.cc +++ b/src/search/operator_counting/state_equation_constraints.cc @@ -103,7 +103,7 @@ bool StateEquationConstraints::update_constraints(const State &state, double lower_bound = 0; /* If we consider the current value of var, there must be an additional consumer. */ - if (state[var].get_value() == value) { + if (state[var] == value) { --lower_bound; } /* If we consider the goal value of var, there must be an diff --git a/src/search/pdbs/cegar.cc b/src/search/pdbs/cegar.cc index 0c2c5e179c..911b6f3356 100644 --- a/src/search/pdbs/cegar.cc +++ b/src/search/pdbs/cegar.cc @@ -387,7 +387,7 @@ bool CEGAR::get_flaws_for_pattern( bool raise_goal_flaw = false; for (const FactPair &goal : goals) { int goal_var_id = goal.var; - if (final_state[goal_var_id].get_value() != goal.value && + if (final_state[goal_var_id] != goal.value && !blacklisted_variables.count(goal_var_id)) { flaws.emplace_back(collection_index, goal_var_id); raise_goal_flaw = true; diff --git a/src/search/pruning/stubborn_sets.h b/src/search/pruning/stubborn_sets.h index 402395ddec..43807046f0 100644 --- a/src/search/pruning/stubborn_sets.h +++ b/src/search/pruning/stubborn_sets.h @@ -62,7 +62,7 @@ class StubbornSets : public PruningMethod { inline FactPair find_unsatisfied_condition( const std::vector &conditions, const State &state) { for (const FactPair &condition : conditions) { - if (state[condition.var].get_value() != condition.value) + if (state[condition.var] != condition.value) return condition; } return FactPair::no_fact; diff --git a/src/search/pruning/stubborn_sets_atom_centric.cc b/src/search/pruning/stubborn_sets_atom_centric.cc index c3b6fc1027..4f78f757be 100644 --- a/src/search/pruning/stubborn_sets_atom_centric.cc +++ b/src/search/pruning/stubborn_sets_atom_centric.cc @@ -138,7 +138,7 @@ FactPair StubbornSetsAtomCentric::select_fact( choose it. Otherwise, choose the first unsatisfied fact. */ for (const FactPair &condition : facts) { - if (state[condition.var].get_value() != condition.value) { + if (state[condition.var] != condition.value) { if (marked_producers[condition.var][condition.value]) { fact = condition; break; @@ -150,7 +150,7 @@ FactPair StubbornSetsAtomCentric::select_fact( } else if (atom_selection_strategy == AtomSelectionStrategy::STATIC_SMALL) { int min_count = numeric_limits::max(); for (const FactPair &condition : facts) { - if (state[condition.var].get_value() != condition.value) { + if (state[condition.var] != condition.value) { int count = achievers[condition.var][condition.value].size(); if (count < min_count) { fact = condition; @@ -161,7 +161,7 @@ FactPair StubbornSetsAtomCentric::select_fact( } else if (atom_selection_strategy == AtomSelectionStrategy::DYNAMIC_SMALL) { int min_count = numeric_limits::max(); for (const FactPair &condition : facts) { - if (state[condition.var].get_value() != condition.value) { + if (state[condition.var] != condition.value) { const vector &ops = achievers[condition.var][condition.value]; int count = count_if( ops.begin(), ops.end(), [&](int op) {return !stubborn[op];}); diff --git a/src/search/pruning/stubborn_sets_ec.cc b/src/search/pruning/stubborn_sets_ec.cc index d6b2972fde..bc6f0a953b 100644 --- a/src/search/pruning/stubborn_sets_ec.cc +++ b/src/search/pruning/stubborn_sets_ec.cc @@ -18,7 +18,7 @@ static inline bool is_v_applicable(int var, const State &state, vector> &preconditions) { int precondition_on_var = preconditions[op_no][var]; - return precondition_on_var == -1 || precondition_on_var == state[var].get_value(); + return precondition_on_var == -1 || precondition_on_var == state[var]; } static vector build_dtgs(TaskProxy task_proxy) { @@ -164,7 +164,7 @@ void StubbornSetsEC::compute_active_operators(const State &state) { for (const FactPair &precondition : sorted_op_preconditions[op_no]) { int var_id = precondition.var; - int current_value = state[var_id].get_value(); + int current_value = state[var_id]; const vector &reachable_values = reachability_map[var_id][current_value]; if (!reachable_values[precondition.value]) { @@ -258,7 +258,7 @@ void StubbornSetsEC::get_disabled_vars( void StubbornSetsEC::apply_s5(int op_no, const State &state) { // Find a violated state variable and check if stubborn contains a writer for this variable. for (const FactPair &pre : sorted_op_preconditions[op_no]) { - if (state[pre.var].get_value() != pre.value && written_vars[pre.var]) { + if (state[pre.var] != pre.value && written_vars[pre.var]) { if (!nes_computed[pre.var][pre.value]) { add_nes_for_fact(pre, state); } diff --git a/src/search/state_registry.cc b/src/search/state_registry.cc index a494c05023..47a07b6374 100644 --- a/src/search/state_registry.cc +++ b/src/search/state_registry.cc @@ -56,7 +56,7 @@ const State &StateRegistry::get_initial_state() { State initial_state = task_proxy.get_initial_state(); for (size_t i = 0; i < initial_state.size(); ++i) { - state_packer.set(buffer.get(), i, initial_state[i].get_value()); + state_packer.set(buffer.get(), i, initial_state[i]); } state_data_pool.push_back(buffer.get()); StateID id = insert_id_or_pop_state(); diff --git a/src/search/task_proxy.h b/src/search/task_proxy.h index 0a134a93b6..a8446861f5 100644 --- a/src/search/task_proxy.h +++ b/src/search/task_proxy.h @@ -91,32 +91,42 @@ using PackedStateBin = int_packer::IntPacker::Bin; task_properties.h module. */ -template -concept has_item_type = requires { - typename T::ItemType; +template +concept proxy_iterator_enabled = requires(Container & container, std::size_t i) { + typename Container::ItemType; + requires std::same_as>; + { + container.size() + } + ->std::integral; + { + container[i] + } + ->std::same_as; }; /* Basic iterator support for proxy collections. */ -template +template class ProxyIterator { /* We store a pointer to collection instead of a reference because iterators have to be copy assignable. */ const ProxyCollection *collection; std::size_t pos; public: - using iterator_category = std::input_iterator_tag; - using value_type = typename ProxyCollection::ItemType; - using difference_type = int; - using pointer = const value_type *; - using reference = value_type; + using value_type = decltype((*collection)[0]); + using difference_type = int; // unused but required by the iterator concept + using iterator_category = std::input_iterator_tag; // required for legacy iterators + using pointer = value_type *; // required for legacy iterators + using reference = value_type; // required for legacy iterators + ProxyIterator() = default; ProxyIterator(const ProxyCollection &collection, std::size_t pos) : collection(&collection), pos(pos) { } - reference operator*() const { + value_type operator*() const { return (*collection)[pos]; } @@ -141,12 +151,22 @@ class ProxyIterator { } }; -template +template +inline ProxyIterator begin(const ProxyCollection &collection) { + return ProxyIterator(collection, 0); +} + +template +inline ProxyIterator end(const ProxyCollection &collection) { + return ProxyIterator(collection, collection.size()); +} + +template inline ProxyIterator begin(ProxyCollection &collection) { return ProxyIterator(collection, 0); } -template +template inline ProxyIterator end(ProxyCollection &collection) { return ProxyIterator(collection, collection.size()); } @@ -158,7 +178,6 @@ class FactProxy { public: FactProxy(const AbstractTask &task, int var_id, int value); FactProxy(const AbstractTask &task, const FactPair &fact); - ~FactProxy() = default; VariableProxy get_variable() const; @@ -194,11 +213,14 @@ class FactsProxyIterator { int var_id; int value; public: + using value_type = FactProxy; + using difference_type = int; // unused but required by the iterator concept + + FactsProxyIterator() = default; FactsProxyIterator(const AbstractTask &task, int var_id, int value) : task(&task), var_id(var_id), value(value) {} - ~FactsProxyIterator() = default; - FactProxy operator*() const { + value_type operator*() const { return FactProxy(*task, var_id, value); } @@ -214,6 +236,12 @@ class FactsProxyIterator { return *this; } + value_type operator++(int) { + value_type fact(**this); + ++(*this); + return fact; + } + bool operator==(const FactsProxyIterator &other) const { assert(task == other.task); return var_id == other.var_id && value == other.value; @@ -239,7 +267,6 @@ class FactsProxy { public: explicit FactsProxy(const AbstractTask &task) : task(&task) {} - ~FactsProxy() = default; FactsProxyIterator begin() const { return FactsProxyIterator(*task, 0, 0); @@ -275,7 +302,6 @@ class VariableProxy { public: VariableProxy(const AbstractTask &task, int id) : task(&task), id(id) {} - ~VariableProxy() = default; bool operator==(const VariableProxy &other) const { assert(task == other.task); @@ -332,7 +358,6 @@ class VariablesProxy { using ItemType = VariableProxy; explicit VariablesProxy(const AbstractTask &task) : task(&task) {} - ~VariablesProxy() = default; std::size_t size() const { return task->get_num_variables(); @@ -355,7 +380,6 @@ class PreconditionsProxy : public ConditionsProxy { public: PreconditionsProxy(const AbstractTask &task, int op_index, bool is_axiom) : ConditionsProxy(task), op_index(op_index), is_axiom(is_axiom) {} - ~PreconditionsProxy() = default; std::size_t size() const override { return task->get_num_operator_preconditions(op_index, is_axiom); @@ -377,7 +401,6 @@ class EffectConditionsProxy : public ConditionsProxy { EffectConditionsProxy( const AbstractTask &task, int op_index, int eff_index, bool is_axiom) : ConditionsProxy(task), op_index(op_index), eff_index(eff_index), is_axiom(is_axiom) {} - ~EffectConditionsProxy() = default; std::size_t size() const override { return task->get_num_operator_effect_conditions(op_index, eff_index, is_axiom); @@ -399,7 +422,6 @@ class EffectProxy { public: EffectProxy(const AbstractTask &task, int op_index, int eff_index, bool is_axiom) : task(&task), op_index(op_index), eff_index(eff_index), is_axiom(is_axiom) {} - ~EffectProxy() = default; EffectConditionsProxy get_conditions() const { return EffectConditionsProxy(*task, op_index, eff_index, is_axiom); @@ -420,7 +442,6 @@ class EffectsProxy { using ItemType = EffectProxy; EffectsProxy(const AbstractTask &task, int op_index, bool is_axiom) : task(&task), op_index(op_index), is_axiom(is_axiom) {} - ~EffectsProxy() = default; std::size_t size() const { return task->get_num_operator_effects(op_index, is_axiom); @@ -440,7 +461,6 @@ class OperatorProxy { public: OperatorProxy(const AbstractTask &task, int index, bool is_axiom) : task(&task), index(index), is_an_axiom(is_axiom) {} - ~OperatorProxy() = default; bool operator==(const OperatorProxy &other) const { assert(task == other.task); @@ -493,7 +513,6 @@ class OperatorsProxy { using ItemType = OperatorProxy; explicit OperatorsProxy(const AbstractTask &task) : task(&task) {} - ~OperatorsProxy() = default; std::size_t size() const { return task->get_num_operators(); @@ -520,7 +539,6 @@ class AxiomsProxy { using ItemType = OperatorProxy; explicit AxiomsProxy(const AbstractTask &task) : task(&task) {} - ~AxiomsProxy() = default; std::size_t size() const { return task->get_num_axioms(); @@ -541,7 +559,6 @@ class GoalsProxy : public ConditionsProxy { public: explicit GoalsProxy(const AbstractTask &task) : ConditionsProxy(task) {} - ~GoalsProxy() = default; std::size_t size() const override { return task->get_num_goals(); @@ -586,8 +603,6 @@ class State { const int_packer::IntPacker *state_packer; int num_variables; public: - using ItemType = FactProxy; - // Construct a registered state with only packed data. State(const AbstractTask &task, const StateRegistry ®istry, StateID id, const PackedStateBin *buffer); @@ -605,8 +620,9 @@ class State { void unpack() const; std::size_t size() const; - FactProxy operator[](std::size_t var_id) const; + int operator[](std::size_t var_id) const; FactProxy operator[](VariableProxy var) const; + FactProxy get_fact(std::size_t var_id) const; TaskProxy get_task() const; @@ -659,7 +675,6 @@ class TaskProxy { public: explicit TaskProxy(const AbstractTask &task) : task(&task) {} - ~TaskProxy() = default; TaskID get_id() const { return TaskID(task); @@ -805,19 +820,23 @@ inline std::size_t State::size() const { return num_variables; } -inline FactProxy State::operator[](std::size_t var_id) const { +inline int State::operator[](std::size_t var_id) const { assert(var_id < size()); if (values) { - return FactProxy(*task, var_id, (*values)[var_id]); + return (*values)[var_id]; } else { assert(buffer); assert(state_packer); - return FactProxy(*task, var_id, state_packer->get(buffer, var_id)); + return state_packer->get(buffer, var_id); } } inline FactProxy State::operator[](VariableProxy var) const { - return (*this)[var.get_id()]; + return get_fact(var.get_id()); +} + +inline FactProxy State::get_fact(std::size_t var_id) const { + return FactProxy(*task, var_id, (*this)[var_id]); } inline TaskProxy State::get_task() const { @@ -857,4 +876,76 @@ inline const std::vector &State::get_unpacked_values() const { } return *values; } + +class StateFactsIterator { + const State *state; + int var_id; +public: + using difference_type = int; // unused but required by the iterator concept + using value_type = FactProxy; + + StateFactsIterator() = default; + StateFactsIterator(const State &state, int var_id) + : state(&state), var_id(var_id) { + } + + value_type operator*() const { + return state->get_fact(var_id); + } + + StateFactsIterator &operator++() { + ++var_id; + return *this; + } + + value_type operator++(int) { + value_type fact(**this); + ++(*this); + return fact; + } + + bool operator==(const StateFactsIterator &other) const = default; +}; + +inline StateFactsIterator begin(const State &state) { + return StateFactsIterator(state, 0); +} + +inline StateFactsIterator end(const State &state) { + return StateFactsIterator(state, state.size()); +} + +inline StateFactsIterator begin(State &state) { + return StateFactsIterator(state, 0); +} + +inline StateFactsIterator end(State &state) { + return StateFactsIterator(state, state.size()); +} + +static_assert(std::input_iterator>); +static_assert(std::input_iterator>); +static_assert(std::input_iterator>); +static_assert(std::input_iterator>); +static_assert(std::input_iterator>); +static_assert(std::input_iterator>); +static_assert(std::input_iterator>); +static_assert(std::input_iterator>); + +static_assert(std::ranges::range); +static_assert(std::ranges::range); +static_assert(std::ranges::range); +static_assert(std::ranges::range); +static_assert(std::ranges::range); +static_assert(std::ranges::range); +static_assert(std::ranges::range); +static_assert(std::ranges::range); + +static_assert(std::input_iterator); +static_assert(std::ranges::range); + +static_assert(std::input_iterator); +static_assert(std::ranges::range); + + #endif diff --git a/src/search/utils/collections.h b/src/search/utils/collections.h index 5d13592ffa..c78f80ac3d 100644 --- a/src/search/utils/collections.h +++ b/src/search/utils/collections.h @@ -77,8 +77,7 @@ template std::vector map_vector(const Collection &collection, MapFunc map_func) { std::vector transformed; transformed.reserve(collection.size()); - std::transform(begin(collection), end(collection), - std::back_inserter(transformed), map_func); + std::ranges::transform(collection, std::back_inserter(transformed), map_func); return transformed; }