2

Following is the definition of std::find_if

template< class InputIt, class UnaryPredicate >
constexpr InputIt find_if( InputIt first, InputIt last, UnaryPredicate p );

Why doesn't it use UnaryPredicate&& p as parameter?

If my predicate is heavy to copy, what should I do?

Should the following cases be treated differently?

  1. UnaryPredicate::operator() is const qualified (which should usually be the case)
  2. UnaryPredicate::operator() is not const qualified.
  3. has the following the overload: UnaryPredicate::operator() && (is this crazy to see?) and I am going to pass a rvalue as predictor.

When the functor has rvalue reference overloads and if a temporary is passed as a parameter, after the first call, the object may have its managed resources being moved or disposed, it might make the following calls invalid. In such a case, what should the caller do?

doraemon
  • 2,296
  • 1
  • 17
  • 36

1 Answers1

3

Most predicates are cheap to copy, so this isn't usually an issue.

If you have one that really is expensive to copy, you can (often) use std::reference_wrapper to provide a wrapper that's cheap to copy. Here's a quick demo:

#include <iostream>
#include <thread>
#include <algorithm>
#include <vector>
#include <chrono>

using namespace std::literals;

struct Predicate {

    Predicate() = default;
    Predicate(Predicate const &) { std::this_thread::sleep_for(1s); }

    bool operator()(int i) const { return i == 10; }
};

int main() { 
    Predicate p;

    using namespace std::chrono;

    std::vector<int> foo { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    auto start = high_resolution_clock::now();
    auto pos1 = std::find_if(foo.begin(), foo.end(), p);
    auto mid = high_resolution_clock::now();   
    auto pos = std::find_if(foo.begin(), foo.end(), std::ref(p));
    auto end = high_resolution_clock::now();

    std::cout << pos - foo.begin() << '\n';
    std::cout << pos1 - foo.begin() << '\n';

    std::cout << "bare: " << duration_cast<seconds>(mid-start).count() << " s\n";
    std::cout << "wrap: " << duration_cast<nanoseconds>(end-mid).count() << " ns\n";
}

When I run this, I get output like this:

9
9
bare: 5 s
wrap: 168 ns

So, it looks like 5 copies are being made. When using the "bare" Predicate object, the search takes 5 seconds. When using the wrapped one, it takes 168 nanoseconds (including the search, not just copying the Predicate).

I believe that should work with operator() either const qualified or not. I haven't checked on the rvalue reference qualified version, but I'm pretty sure that would take more work (reference_wrapper explicitly deletes ref(&&), and that's probably for good reason, so supporting rvalue references may be non-trivial, but I've never dealt with that specific situation so I haven't tried to think though why it's a problem).

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • Thanks very much for your reply. I asked about the ravlue reference because if a temporary is passed as a parameter, and the rvalue reference qualified overload version got some of the resources moved or disposed after the first call, it will make the following calls invalid. (I edited the OP to add this part). What is the standard strategy to handle this case? – doraemon Mar 18 '23 at 13:04
  • @doraemon: You *typically* want to bind a temporary Foo to a reference to const Foo (`Foo const &`). This can bind to either a temporary or a normal variable. – Jerry Coffin Mar 18 '23 at 16:28
  • But then i lost the ability to move construct it – doraemon Mar 18 '23 at 23:54
  • @doraemon: I'm getting a bit lost here. In your previous comment you seemed to be saying that moving would "make the following calls invalid." Now you're saying you want to do moves anyway? Your real goal seems unclear... – Jerry Coffin Mar 19 '23 at 03:52