How can I create iterators of a filtered vector?

Viewed 6385

Suppose I have a vector named spot_deals of SpotDeal that is a class:

class SpotDeal
{
public:
    int deal_id_; // primary key, and vector is sorted by id
    string ccy_pair_; // ccy pair, e.g. GBPUSD, AUDUSD
    double amount_;
}

Say I need to pass two subset of spot_deals to a function foo for some computation. I could make copies, however, that would cost memory and time. Actually foo only needs iterators of deals. So can I make 2 iterators of vector<SpotDeal>, namely it1 and it2 and pass them to foo?

The two subset of spot_deals could be filtered by ccy_pair_, e.g. deals of GBPUSD and AUDUSD, or by other conditions. So I'm looking for a way to define an iterator defined by a vector and a lambda function (could equivalently be a functor though).

Is there a way to write a helper function make_filtered_iterator so that I can have something like below?

auto it1 = make_filtered_iterator(spot_deals, filter_lambda1);
auto it2 = make_filtered_iterator(spot_deals, filter_lambda2);
foo(it1, it2);
5 Answers

I may suggest viewers of this question get a crash course with Eric Nibbler's rangev3 because that's the paradigm adopted for C++20 standard lib.

https://github.com/ericniebler/range-v3

How to do your filter iteration:

for (auto element : spot_deals | views::filter([](auto i) { return condition(i); }))
{ //....
}

I wouldn't use iterators pointing to your original vector, because they cannot convey the size of your subsets. (Basically, you would need one more iterator per subset to represent the end of the subsets.) As of C++20, I would work with ranges from the Ranges library, as mentioned in v.oddou's answer. More specifically, for your use case, I would use the range adaptor std::views::filter as follows:

auto gbpusd = [](const auto& sd) { return sd.ccy_pair_ == "GBPUSD"; };
auto audusd = [](const auto& sd) { return sd.ccy_pair_ == "AUDUSD"; };

auto range1 = spot_deals | std::views::filter(gbpusd);
auto range2 = spot_deals | std::views::filter(audusd);

foo(range1, range2);

This solution does not create temporary vectors for the filtered spot deals, because the view adaptors create ranges that don't contain elements. The resulting ranges range1 and range2 are just views over the vector spot_deals, but with customized iteration behaviors.

The declaration of foo() is a bit tricky, because the data types of the ranges are quite complex. I would therefore use the placeholder type auto for the function parameters and thus make foo() a function template:

void foo(auto& r1, auto& r2) {
    for (auto const& sd : r1)
        std::cout << sd.deal_id_ << std::endl;
    for (auto const& sd : r2)
        std::cout << sd.amount_ << std::endl;
}

(Alternatively, you could pass spot_deals by reference to foo() and declare the filtered ranges inside foo().)

Code on Wandbox

Related