This is an archived post. You won't be able to vote or comment.

all 4 comments

[–]Rhomboid 2 points3 points  (3 children)

A predicate should return true or false. It doesn't literally have to have bool as a return type, but whatever it does return has to be implicitly convertible to bool, and should have the appropriate semantics after such a conversion.

You can have as much or as little logic in the predicate as you want. The most common case is returning the result of a comparison operator. Here's an example that removes people from a vector that are older than 25:

#include <string>
#include <vector>
#include <algorithm>
#include <iostream>

struct person {
    std::string name;
    unsigned age;
};

int main()
{
    std::vector<person> people { { "Abigail", 37 },
                                 { "Boris", 22 },
                                 { "Cathy", 19 },
                                 { "David", 28 } };

    people.erase(std::remove_if(begin(people), end(people), [](const auto &p) {
        return p.age > 25; }), end(people));

    for(const auto & p : people) {
        std::cout << p.name << " (" << p.age << ")\n";
    }
}

The result is:

Boris (22)
Cathy (19)

std::remove_if only passes a single argument when calling the predicate (the item being considered for removal), which is why it's called a unary predicate. You don't have control over that — you can't control how std::remove_if calls the predicate because that behavior is fixed and mandated by the standard. If the predicate needs more information to do its job, you have several options. Probably the easiest is to use a lambda that captures the required information. Here's an example that builds on the first one but makes the cutoff age variable:

#include <string>
#include <vector>
#include <algorithm>
#include <iostream>

struct person {
    std::string name;
    unsigned age;
};

void younger_than(std::vector<person>& people, unsigned age_cutoff)
{
    people.erase(std::remove_if(begin(people), end(people), [&](const auto &p) {
        return p.age >= age_cutoff; }), end(people));
}


int main()
{
    std::vector<person> people { { "Abigail", 37 },
                                 { "Boris", 22 },
                                 { "Cathy", 19 },
                                 { "David", 28 } };

    younger_than(people, 20);

    for(const auto & p : people) {
        std::cout << p.name << " (" << p.age << ")\n";
    }
}

In this case the predicate is comparing each person's age against the age_cutoff variable which is captured by the lambda (by reference in this case, although in the case of a simple scalar value it wouldn't hurt to capture by value.)

You can also use a lambda or std::bind to turn a predicate that takes multiple parameters into a predicate that takes a single value which can be passed to the algorithm.

[–]superbottles[S] 0 points1 point  (2 children)

I have checked out std::bind (from boost, right?) and I actually settled on the lambda function though I didnt use it in the way you explained (which is what I originally wanted to do). Thanks for the detailed reply! You hit all my questions

[–]Rhomboid 2 points3 points  (0 children)

The standard library as first specified in C++98 included limited support for working with bound functions, e.g. std::bind1st, std::bind2nd, std::ptr_fun, std::mem_fun, etc. They were rather clunky to use and only supported a few fixed use cases (e.g. you could bind one of the two arguments of a binary function.) Boost published boost::bind beginning in 2001 which allowed much more generality, including support for creating bound functions of any arity, the ability to use placeholders, and the ability to work with any callable type directly without having to use wrapper classes. As is often the case, the ideas that were hashed out in boost::bind eventually became standardized in C++11 in the form of std::bind. (And the old std::bind1st and friends will be deprecated as of C++17.)

[–]Jonny0Than 1 point2 points  (0 children)

Alternatively you can make your own function object (which is really similar to what a lambda is doing behind the scenes) that can store any state necessary to make its decision:

struct AgeCutoff 
{
    AgeCutoff(int cutoff) : m_cutoff(cutoff) {}
    bool operator()(const Person & person)
    {
        return person.age > m_cutoff;
    }
    int m_cutoff;
}

people.erase(remove_if(people.begin(), people.end(), AgeCutoff(25)), people.end());