Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I define a "unary predicate" for copy_if, etc in C++?

Tags:

c++

stl

I'm trying to use std::copy_if() and I figured out somewhat how the syntax works from http://www.cplusplus.com/reference/algorithm/copy_if/ :

auto it = std::copy_if (foo.begin(), foo.end(), bar.begin(), [](int i){return !(i<0);} );

The last argument is the one that I am confused about. What are the brackets for? Can I use a function I wrote somewhere else as an argument and how would that work? Can I pass another argument to the function if I specify which variable to pass to it?

I guess my overall question is where I could find the syntax for these things. Using this example, I can declare some really simple things, but I'd like to be able to do more with it. I've found a few places that explain what a unary predicate should do and not do, but not actually how to declare one and what that would mean. I'm still somewhat new to the algorithms in c++ and hope to learn how to use them more effectively.

like image 313
Klaus Haukenstein Avatar asked May 21 '18 13:05

Klaus Haukenstein


People also ask

What is a unary predicate?

Unary predicates are simply properties of objects, binary ones are relations between pairs of objects and in general n-ary predicates express relations among n-tuples of objects. A reasonable answer to the question of what are 0-ary predicates might well be that they are propositions.

What is a unary predicate in C++?

A predicate is any unary C++ function that returns true or false. The result is end, if the value was not found. Suppose we wanted to find the first even number in some container. The easiest thing to do is to define an even number predicate: bool isEven (int x) { return ((x % 2) == 0); }

When to use std:: copy?

C++ STL std::copy() function copy() function is a library function of algorithm header, it is used to copy the elements of a container, it copies the elements of a container from given range to another container from a given beginning position.


1 Answers

You can pass anything that behaves like a function as the predicate to copy_if. There are a few common things you can use:

1) Functions

Functions indeed act like functions, so they can be passed as a predicate to copy_if:

bool is_less_than_zero(int i) { return i < 0; }

int main() {
    std::vector<int> a = {1, 2, -2, -1};
    std::vector<int> b;

    std::copy_if(a.begin(), a.end(), std::back_inserter(b), is_less_than_zero);
    // now b will contain the elements {-2, -1}
}

Live Demo

2) Objects with an overloaded operator()

Objects can overload operator() so that they act like functions. These are often called "function objects" or "functors". This lets you store state, which can't be achieved with raw functions:

struct IsLessThan {
    IsLessThan(int i) : i_{i} {}
    bool operator()(int i) { return i < i_; }
    int i_;
};

int main() {
    std::vector<int> a = {1, 2, -2, -1};
    std::vector<int> b;

    std::copy_if(a.begin(), a.end(), std::back_inserter(b), IsLessThan(0));
    // now b will contain the elements {-2, -1}
}

Live Demo

3) Lambdas

Lambdas are conceptually anonymous functions. In reality, they're just syntactic sugar for objects with an overloaded operator(), but that makes them a useful tool for creating simple predicates with little code:

int main() {
    std::vector<int> a = {1, 2, -2, -1};
    std::vector<int> b;

    std::copy_if(a.begin(), a.end(), std::back_inserter(b),
                 [](int i){ return i < 0; });
    // now b will contain the elements {-2, -1}
}

Live Demo

Since lambdas are really objects with an overloaded operator() they can also contain state, which is given via the lambda's capture list:

int main() {
    std::vector<int> a = {1, 2, -2, -1};
    std::vector<int> b;
    int number_to_compare_to = 0;

    std::copy_if(a.begin(), a.end(), std::back_inserter(b),
                 [number_to_compare_to](int i){ return i < number_to_compare_to; });
    // now b will contain the elements {-2, -1}
}

Live Demo

There are some facilities in the standard library to easily create function objects that contain state and use it to supply some of the parameters to a function (namely std::bind), but most of the places they were useful it's now easier to use a lambda instead. That is, the following code creates two objects that both act exactly the same:

bool first_less_than_second(int i, int j) { return i < j; }

int main() {
    auto bind_less_than_zero = std::bind(first_less_than_second, std::placeholders::_1, 0);
    auto lambda_less_than_zero = [](int i){ return first_less_than_second(i, 0); };
}

In general you should prefer the lambda version, but you will still sometimes see std::bind (or its pre-c++11 boost counterpart boost::bind) employed.

like image 54
Miles Budnek Avatar answered Nov 11 '22 06:11

Miles Budnek