tags:

views:

493

answers:

6

While using std::for_each algorithm how do I break when a certain condition is satisfied?

+2  A: 

You can't do it, unless you throw an exception, which is not a good idea because you don't do flow control with exceptions.

Update: apparently Boost has a for_each_if that might help, but you're not using Boost.

Dave Van den Eynde
you dont need boost to use a for_each_if(). It is simple to implement.
John Dibling
Right, but the question is not "how do I implement for_each_if()".
Dave Van den Eynde
for_each_if doesn't solve the question either. It was not "how do I make my code do nothing on the elements after a certain point", but "how do I stop the iteration entirely". There can be some serious performance costs to traversing the entire range if the iteration could have been terminated earlier. So the answer is exceptions or nothing. (where nothing means "redesign so you don't have to break, or don't use for_each for this".)
jalf
Never said it did help. Perhaps time for for_each_while? (or for_each_until?) I'm sure there's something that can be implemented that would solve the problem. The issue there is: at which point in time does it become more work to implement that "generic" solution instead of just using a for(;;) loop and break.
Dave Van den Eynde
+4  A: 

You can use find_if algorithm, which will stop and return the iterator where the predicate condition applied to the iterated element returns true. So your predicate should be changed to return a boolean as the continue/break condition.

However, this is a hack, so you can use the algorithms.

Another way is to use BOOST_FOREACH.

Cătălin Pitiș
Yes I can do that, but what if I want to apply some operations to the elements in the loop until the condition is satisfied?
Naveen
Naveen, what's with that? you can do any operation on the elements whatsoever. then at any time you can "break;" and the loop exits.
Johannes Schaub - litb
find_if accepts input iterators, not output iterators. Then, I would recommend to implement the loop by hand, or use BOOST_FOREACH.
Cătălin Pitiș
Johannes Schaub - litb
the fact that it takes InputIterator's is a lower bound. it doesn't mean that it can't take forward iterators, for example. But it means the algorithm itself won't require forward iterator capabilities. The passed function object, of course, may do that. It's then a concern of the user of find_if. But i will agree with you - it looks a bit hacky
Johannes Schaub - litb
@litb: I agree with you regarding the required features of find_if. This is a reason for considering using find_if in this context as a hack. Normally, you expect find_if not to change the objects in the sequence, yet... using it in this context will do so (hidden side effects).
Cătălin Pitiș
+9  A: 

You can break from the for_each() by throwing an exception from your functor. This is often not a good idea however, and there are alternatives.

You can retain state in your functor. If you detect the 'break' condition, simply set a flag in your functor and then for each subsequent iteration simply return without doing your functor's thing. Obviously this won't stop the iteration, which might be expensive for large collections, but it will at least stop the work from being performed.

If your collection is sorted, you can find() the element that you want to break at, then do for_each from begin() to the element find() returned.

Finally, you can implement a for_each_if(). This will again not stop the iteration but will not evaluate your functor which does the work if the predicate evaluates to false. Here are 2 flavors of for_each_xxx(), one which takes a value and performs the work if operator==() evaluates to true, and another which takes two functors; one which performs a comparison ala find_if(), and the other which performs the work if the comparison operator evaluates to true.

/* ---

    For each
    25.1.1

     template< class InputIterator, class Function, class T>
      Function for_each_equal(InputIterator first, InputIterator last, const T& value, Function f)

     template< class InputIterator, class Function, class Predicate >
      Function for_each_if(InputIterator first, InputIterator last, Predicate pred, Function f)

    Requires: 

     T is of type EqualityComparable (20.1.1) 

    Effects: 

      Applies f to each dereferenced iterator i in the range [first, last) where one of the following conditions hold:

      1: *i == value
      2: pred(*i) != false

    Returns: 

     f

    Complexity: 

     At most last - first applications of f

    --- */

    template< class InputIterator, class Function, class Predicate >
    Function for_each_if(InputIterator first, 
          InputIterator last, 
          Predicate pred, 
          Function f)
    {
     for( ; first != last; ++first)
     {
      if( pred(*first) )
       f(*first);
     }
     return f;
    };

    template< class InputIterator, class Function, class T>
    Function for_each_equal(InputIterator first, 
          InputIterator last, 
          const T& value, 
          Function f)
    {
     for( ; first != last; ++first)
     {
      if( *first == value )
       f(*first);
     }
     return f;
    };
John Dibling
You can break by throwing an exception. But the find() solution is better in some cases at least
jalf
True; edited response to incorporate other input.
John Dibling
A: 

You throw an exception. Whether or not it's a good idea is sort of a style question, pace @Dan, but may be more of an issue with your design. for_each is intended for a sort of functional-programming style, which implicitly assumes that your function can be applied uniformly across the set. So, if you do need to break, that could be consiered an unusual condition, and therefore worthy of an exception.

The other solution, and a more "functional" solution, is to write your function so that if it shouldn't have an effect on some applications, then write it to have no effect. So, for example, if you had a summing function, have it add 0 in the cases you would have "broken" from.

Charlie Martin
Throwing exceptions for flow control is not a matter of style. It's a matter of taste. Bad taste, that is.
Dave Van den Eynde
Sounds like it's a matter of blind zealotry more than taste. It does the job, simpler, cleaner and more elegantly than most of the other solutions. I kinda agree with Charlie on this. If you *do* need to break from a for_each, then 1) there's probably an underlying design issue, and 2) throwing an exception is the simplest solution.As long as the exception can be so cleanly isolated (it's one single line of code you need to wrap in try/catch), using it for flow control is tolerable.
jalf
It's kind of like the old anti-goto zealotry. Yes, gotos can be harmful, but sometimes in some languages they're the best of the available options.
Charlie Martin
Oh, so now knowing the difference between flow control and exceptions is called "zealotry".
Dave Van den Eynde
Really depends why you are breaking. If there is an "exceptional" reason for that, sure - throw an exception. However, if it is a "normal" thing I would be *very* careful with exceptions - they may kill the performance and make the code hard to debug.
Nemanja Trifunovic
I don't want to get in to a Holy War regarding the "correct" use of exceptions, but it isn't accurate to say that throwing exceptions is a matter of style. Whether or not throwing exceptions is a good idea depends on the structure of your program, and the decision to throw or not to throw is much more substantial than simply deciding where to put your curly braces.
John Dibling
I love you guys/girls. I'm not sure my opinion matters much, but if I came across an exception handler in this circumstance, my first thought "can I be certain of data integrity?" say the for_each was modifying elements: What state is the data in? Can this be recovered from?
veefu
Get a *grip* people. Dave is saying "it's not a matter of style its a matter of taste" as if that actually, you know, has technical meaning. LOOK AT WHAT I WROTE: "how do you do it? Throw an exception. Is that good? Maybe not. There's another way, that fits the functional paradigm better." What you're bitching about is that I wasn't *sufficiently* obnoxious about it. Oh, and Dave, re: knowing the difference? Keep a civil tongue in your head.
Charlie Martin
Who deleted the comments on this answer made yesterday? I for example linked to another answer of this guy, but the comment doesn't seem to exist anymore.
Johannes Schaub - litb
Pretty well has to be an admin, doesn't it? I certainly don't have a delete handle on any comments but my own.
Charlie Martin
+3  A: 

If you want do some actions while condition is not satisfied, maybe you need change algorithm on something like std::find_if?

bb
This is the easy way. Just return true when you want to stop.
Martin York
+2  A: 

As already shown by others it is only achievable with workarounds that IMHO obfuscate the code.

So my suggestions is to change the for_each into a regular for loop. This will make it more visible to others that you are using break (and maybe even continue).

lothar