If you look at std::copy_if source code, you should see how that works.
Essentially what copy_if does is this:
void copy_if(It1 f1, It1 l1, It2 f2, Pred pred) {
for (; f1 != l1; ++f1) {
if (pred(*f1)) {
*f2 = *f1;
++f2;
}
}
}
It does not know about containers, nor does it have to. The pair of iterators (f1, l1) specify a range where the items are copied from, the iterators contain all the knowledge about how to get to the element. The iterator f2 specifies the starting point of the destination range. If the destination range is not big enough woul will have a buffer overflow bug. The predicate is a function that indicates whether to copy an element or not. It does not have to know anything about the container, it just needs to be able to tell copy_if whether an element being visited should be copied or not.
Difference between capturing a variable and passing as argument should be explained by the following snippets. This lambda
int captured = 42;
auto functor = [captured](int x) { return x%2 == 0; };
essentially means this:
int captured = 42;
struct Blah
{
Blah(int c) : captured_(c) { }
bool operator()(int x) const { return x%2 == 0; }
int captured_;
} functor(captured);