Modern way to filter STL container?

C++C++11Stl

C++ Problem Overview


Coming back to C++ after years of C# I was wondering what the modern - read: C++11 - way of filtering an array would be, i.e. how can we achieve something similar to this Linq query:

var filteredElements = elements.Where(elm => elm.filterProperty == true);

In order to filter a vector of elements (strings for the sake of this question)?

I sincerely hope the old STL style algorithms (or even extensions like boost::filter_iterator) requiring explicit methods to be defined are superseded by now?

C++ Solutions


Solution 1 - C++

See the example from cplusplus.com for std::copy_if:

std::vector<int> foo = {25,15,5,-5,-15};
std::vector<int> bar;

// copy only positive numbers:
std::copy_if (foo.begin(), foo.end(), std::back_inserter(bar), [](int i){return i>=0;} );

std::copy_if evaluates the lambda expression for every element in foo here and if it returns true it copies the value to bar.

The std::back_inserter allows us to actually insert new elements at the end of bar (using push_back()) with an iterator without having to resize it to the required size first.

Solution 2 - C++

In C++20, use filter view from the ranges library: (requires #include <ranges>)

// namespace views = std::ranges::views;
vec | views::filter([](int a){ return a % 2 == 0; })

lazily returns the even elements in vec.

(See [range.adaptor.object]/4 and [range.filter])


This is already supported by GCC 10 (live demo). For Clang and older versions of GCC, the original range-v3 library can be used too, with #include <range/v3/view/filter.hpp> (or #include <range/v3/all.hpp>) and the ranges::views namespace instead of std::ranges::views (live demo).

Solution 3 - C++

A more efficient approach, if you don't actually need a new copy of the list, is remove_if, which actually removes the elements from the original container.

Solution 4 - C++

I think Boost.Range deserves a mention too. The resulting code is pretty close to the original:

#include <boost/range/adaptors.hpp>

// ...

using boost::adaptors::filtered;
auto filteredElements = elements | filtered([](decltype(elements)::value_type const& elm)
    { return elm.filterProperty == true; });

The only downside is having to explicitly declare the lambda's parameter type. I used decltype(elements)::value_type because it avoids having to spell out the exact type, and also adds a grain of genericity. Alternatively, with C++14's polymorphic lambdas, the type could be simply specified as auto:

auto filteredElements = elements | filtered([](auto const& elm)
    { return elm.filterProperty == true; });

filteredElements would be a range, suitable for traversal, but it's basically a view of the original container. If what you need is another container filled with copies of the elements satisfying the criteria (so that it's independent from the lifetime of the original container), it could look like:

using std::back_inserter; using boost::copy; using boost::adaptors::filtered;
decltype(elements) filteredElements;
copy(elements | filtered([](decltype(elements)::value_type const& elm)
    { return elm.filterProperty == true; }), back_inserter(filteredElements));

Solution 5 - C++

My suggestion for C++ equivalent of C#

var filteredElements = elements.Where(elm => elm.filterProperty == true);

Define a template function to which you pass a lambda predicate to do the filtering. The template function returns the filtered result. eg:

template<typename T>
vector<T> select_T(const vector<T>& inVec, function<bool(const T&)> predicate)
{
  vector<T> result;
  copy_if(inVec.begin(), inVec.end(), back_inserter(result), predicate);
  return result;
}

to use - giving a trivial examples:

std::vector<int> mVec = {1,4,7,8,9,0};

// filter out values > 5
auto gtFive = select_T<int>(mVec, [](auto a) {return (a > 5); });

// or > target
int target = 5;
auto gt = select_T<int>(mVec, [target](auto a) {return (a > target); });

    

Solution 6 - C++

Improved pjm code following underscore-d suggestions:

template <typename Cont, typename Pred>
Cont filter(const Cont &container, Pred predicate) {
    Cont result;
    std::copy_if(container.begin(), container.end(), std::back_inserter(result), predicate);
    return result;
}

Usage:

std::vector<int> myVec = {1,4,7,8,9,0};

auto filteredVec = filter(myVec, [](int a) { return a > 5; });

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionATVView Question on Stackoverflow
Solution 1 - C++Sebastian HoffmannView Answer on Stackoverflow
Solution 2 - C++L. F.View Answer on Stackoverflow
Solution 3 - C++djhaskin987View Answer on Stackoverflow
Solution 4 - C++user2478832View Answer on Stackoverflow
Solution 5 - C++pjmView Answer on Stackoverflow
Solution 6 - C++Alex P.View Answer on Stackoverflow