Immediately I apologize for such a vague name, I do not know how to briefly describe the task.

Suppose we have a class that performs some kind of work:

class Worker{ public: bool doWork(int arg); }; 

The doWork method returns true if the job was successful. It is necessary that someone from the existing workers performed the work. For this I want to use std::find_if

 struct DoWork{ int arg; explicit DoWork(int arg): arg(arg) {} bool operator()(Worker &worker) const{ return worker.doWork(arg); } }; std::vector<Worker> workers; //... std::find_if(workers.begin(), workers.end(), DoWork(42)); 

The idea is this. The algorithm will go through the workers until one does the work or they run out. But I am tormented by vague doubts that this can be done. Is there any uncertain behavior here? Will this code always work the same way on different implementations of stl?

  • one
    The idea is cool - to use the capabilities of the tool outside the box, but still I will try to dissuade you from it: it will be hard to maintain such a code, this is antipattern, because the behavior is not obvious. The function created for search-if should be used for this. - Arkady
  • If you think about it. this can be done if the doWork method is constants, the state of the worker must be changed through the iterator returned by find_if. Perhaps you should have an “invoice” object that will be saved in DoWork or stored somewhere else and its state will change, not the worker state. At the request of the standard doWork should be constant - Swift

3 answers 3

The predicate requirements passed to std::find_if indicate that the predicate parameter does not have to be a constant reference, but nevertheless the predicate is not allowed to modify the object passed to it.

25.1 General [algorithms.general]

If you require data modification, it is necessary to do so.

For the std::find_if algorithm, this permission is not given (unlike, say, std::for_each ). As a modifying operation is formally determined, I will not say out of DoWork , but if your DoWork function modifies a container element, then there is a possibility that you cannot formally do so.

In addition further in the same place

If the algorithm is selected, it returns the value of the corresponding iterator. [...] The function through the dereferenced iterator.

The std::find_if is just the Predicate pred , i.e. This part already explicitly prohibits your option if DoWork is a non-constant member of the Worker class. Again, this definition has its own holes, but the idea, I think, is clear.

Offhand, of course, it is difficult to imagine what could go wrong here if the implementation is not specifically targeted to malicious sabotage and catch violators of the standard ...

  • 2
    The first quote (25.1 / 4) about racing and about the algorithm itself. It is a guarantee that the code of the find_if function template does not change the collection. This has nothing to do with the user predicate. The second quote (/ 8) is more relevant. But the meaning of this section is that the predicate should not change the properties of the collection important for the algorithm. In the case of a linear search find_if collection can be changed. - Abyx
  • @Abyx en.cppreference.com/w/cpp/algorithm/find the signature passed to it. There are no guarantees as to how the container is organized, as well as the peculiarity of the called object being used may violate the rule for saving aliases, so it is incorrect to use the modifying predicate. - Swift
  • @Swift, what is another rule for saving aliases? By the way, the container has nothing to do with it, the algorithm works with InputIterator , all it can do is make predicate(*iterator) , maximum - several times. - Abyx
  • @Abyx: It is clear that it is impossible to come up with one requirement of what can and cannot be done, which would be well suited for all algorithms. Either describe each in detail individually, or follow the path of such a sweeping ban: you cannot call non-constant methods and a full stop. The authors of the specification went the second way. That is, formally all the same it can not be done. - AnT
  • one
    @AnT agrees, but the programmer has his own head to think about where to follow the standard, and where to ignore it. - Abyx

For the unary predicate of the functions std :: find, std :: find_if, std :: find_if_not, the following is clearly defined:

1) The function must return true for the required element.

2) The function prototype should be similar to the record:

 bool pred(const Type &a); 

const can be omitted, but the function should in no case change the data available through the transmitted object.

3) Type must be such that the iterator can be derefenced and implicitly converted to Type.

Why is that? The compiler corresponding to C ++ 11 allows parallelization and optimization organized by the compiler itself . What happens if a predicate can change a value? This can lead to the fact that another predicate pass will give a different, that is ambiguous, result. The collection may be such that changing a member should lead to a change in order, which is impossible when transferring the value of an object by reference, that is, in theory, iterators should change - whether the element is the first in a row from those encountered after the change (and is in the correct place) turns out to be unknown.

find_if should be similar in behavior with the following implementation.

 template<class InputIterator, class UnaryPredicate> InputIterator find_if (InputIterator first, InputIterator last, UnaryPredicate pred) { while (first!=last) { if (pred(*first)) return first; ++first; } return last; } 

Not to be confused: this is by no means a mandatory implementation. The function can be processed in a special way through a static compiler analyzer, turning it into the most appropriate code for a particular case. The predicate argument is passed through the iterator name, and it is assumed that the constant version of dereferencing is used.

  • If the predicate does not change its result, then nothing terrible will happen. - Abyx
  • @ Abyx corrected. Back to back translated. As for, “nothing bad will happen: this is also an indefinite behavior if it contradicted opsana. To change collections there are algorithms for_each, replace_if, remove_if, etc. The collection may be such that changing a member must lead to a change in order, which is impossible When transferring the value of an object by reference, that is, in theory, the iterators change. - Swift

No indefinite behavior — as long as you pass the correct iterators of the beginning and the end, and I do not see a predicate with the correct behavior.

As long as the STL implementation conforms to the standard, the code will work.

"I think so" (c) Pooh