0

I've got a std::vector<Edge> edges and I'd like to copy some items from this array into a std::vector<Edge*> outputs using std library.

I know std::copy_if can be used to copy a vector of pointers to a vector of pointers:

std::vector<Edge*> edges;
//setup edges

std::vector<Edge*> outputs;

std::copy_if(edges.cbegin(), edges.cend(), std::back_insert_iterator<decltype(outputs)>(outputs), [](auto edge) {
   return true; //here should be some condition
});

but it's not possible to do this:

std::vector<Edge> edges;
//setup edges

std::vector<Edge*> outputs;

std::copy_if(edges.cbegin(), edges.cend(), std::back_insert_iterator<decltype(outputs)>(outputs), [](auto edge) {
   return true; //here should be some condition
});

I understand why it's not possible.

My question is: Is there any algorithm that would let me do this?

Edziju
  • 399
  • 11
  • I think you are looking for [`std::transform`](https://en.cppreference.com/w/cpp/algorithm/transform) – joergbrech Nov 18 '22 at 19:36
  • 1
    You would want something like `transform_if`: https://stackoverflow.com/q/23579832 . The answer on that question of using `std::vector>` and just using `copy_if` looks promising – Artyer Nov 18 '22 at 19:38
  • Great! Thanks. `std::reference_wrapper` is what I need – Edziju Nov 18 '22 at 19:41
  • Or if you can use C++20 you can do it efficiently with [`views::filter`](https://en.cppreference.com/w/cpp/ranges/filter_view) and [`views::transform`](https://en.cppreference.com/w/cpp/ranges/transform_view) – joergbrech Nov 18 '22 at 19:42
  • Why not just create something like `std::vector>>` directly from the `begin()` and `end()` iterators of the original instead? – Jesper Juhl Nov 18 '22 at 19:43

1 Answers1

2

You can use Eric Niebler's range-v3 library:

  • get the input vector,
  • filter out some of its elements,
  • transform the remainder into pointers to them, and
  • convert that view to a vector of pointers.

[Demo]

#include <iostream>
#include <range/v3/all.hpp>
#include <vector>

struct Edge {
    int value;
};

int main() {
    std::vector<Edge> edges{ {-10}, {-5}, {2}, {4} };
    auto outputs = edges
        | ranges::views::filter([](auto& e){ return e.value > 0; })
        | ranges::views::transform([](auto& e) { return &e; })
        | ranges::to<std::vector<Edge*>>();
    for (const auto o : outputs) {
        std::cout << o->value << " ";
    }
}

// Outputs: 2 4

Wouldn't you need to create an output vector, you could get along by using C++20 ranges (std::ranges::to will also be available at some point for C++23).

[Demo]

#include <iostream>
#include <ranges>
#include <vector>

struct Edge {
    int value;
};

int main() {
    std::vector<Edge> edges{ {-10}, {-5}, {2}, {4} };
    auto&& outputs{ edges
        | std::views::filter([](auto& e){ return e.value > 0; }) };
    for (auto&& o : outputs) {
        std::cout << o.value << " ";
    }
}

rturrado
  • 7,699
  • 6
  • 42
  • 62
  • 1
    I really like Eric Nieblers range-v3! I would just like to add: C++20 introduced ranges based on range-v3. C++20 has `views::filter` and `views::transform` and C++23 will have `ranges::to`. I have a hunch that creating a contiguous container of the filtered result is not really necessary for @Edzijus use case, it was probably meant just as a means to iterate over the filtered results *(why would it be references/pointers otherwise?)*. Iterating over the filtered result can be done without allocation using just ranges. – joergbrech Nov 18 '22 at 23:16
  • 1
    @joergbrech Many thanks for the comment. I keep on using range-v3 mostly because none of the compilers implement yet `ranges::to` AFAIK. But you may be right about supossing that creating an output vector is not needed. I'll update my answer contemplating that, and using standard ranges. – rturrado Nov 18 '22 at 23:19