Skip to main content
GameDev.net gamedev.net
🔒 Locked 🤖 Godot

Breaking out of a nested loop

Started by TheComet Jul 25, 2015 at 6:13 PM 50 replies 24.5k views
Original Post
TheComet
TheComet

I'm a little disappointed to say the least that breaking from a nested loop feels this dirty in C/C++. It's not an uncommon occurrence, and yet, the "cleanest" solution we have is to use a label and a goto statement.

(Obligatory xkcd joke)

In C/C++ the "cleanest" code for breaking from an inner loop:


int i, j;
for(i = 0; i < 10; i++) {
    for(j = 0; j < 10; j++) {
        if(i - 1 == j + 1)
            goto outer;
    }
}
outer:

I feel like we need a new keyword. Perhaps a break_harder or combo_break keyword to break from 2 loops or something?

Turns out PHP has this feature. In PHP:


<?php
for($i = 0; $i < 10; $i++) {
    for($j = 0; $j < 10; $j++) {
        if($i - 1 == $j + 1)
            break 2;
    }
}
?>

In Java you can use named blocks to achieve the same result:


outer: {
    for(int i = 0; i < 10; i++) {
        for(int j = 0; j < 10; j++) {
            if(i - 1 == j + 1)
                break outer;
        }
    }
}

How do you guys do this in C/C++? Do you think having a break statement like in PHP would be beneficial in C/C++?

People have told me to extract the loops into a separate static function and use return instead of a goto. Do you think this is better practice?

"I would try to find halo source code by bungie best fps engine ever created, u see why call of duty loses speed due to its detail." -- GettingNifty
21st Century Moose
21st Century Moose

I'd use the separate function and return approach; any of the other languages break variants are really just - when you think about it - goto in fancy dress.

Another way that occurs to me is to put the nested loop into a try/catch block and throw an exception. I'm not sure if that's elegant or horrible though.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls. 
SmkViper
SmkViper
With a nested loop like that, maybe the cleanest way is to set the counter of each loop to its end value? (Obviously won't work for ranged-for or a for_each algorithm)
ferrous
ferrous

I'm with mhagain's first approach, just make it a function and return from the whole shebang. Exceptions sound terrible as a way out, unless something actually exceptional happened, like ran into a null pointer or some invalid arguments.

On the other hand, i'm not super opposed to a Goto, depending on how it's used, it's pretty common to see windows code like:

{

hr = S_OK // or E_FAIL or whatever

IFC(DoThing1());

IFC(DoThing2());

Clean:

CleanUp();

return hr;

}

with IFC being a macro that checks for failure, assigns hr, and if there was a failure, goto Clean:

Oberon_Command
Oberon_Command

In C/C++ the "cleanest" code for breaking from an inner loop:


int i, j;
for(i = 0; i < 10; i++) {
    for(j = 0; j < 10; j++) {
        if(i - 1 == j + 1)
            goto outer;
    }
}
outer:



What about this:

auto inner = [&](int i) {
   for(int j = 0; j < 10; j++) {
        if(i - 1 == j + 1)
            return false;
   }
   return true;
};
for(int i = 0; i < 10 && inner(i); i++) {}
Now you don't need a static function declared somewhere outside the scope, all the original code is right there with a little extra boilerplate.

Another way that occurs to me is to put the nested loop into a try/catch block and throw an exception. I'm not sure if that's elegant or horrible though.


This is apparently common in Python (not just for brekaing out of inner loops - iterators throw an exception when they reach the end of the container!), but in C++ I wouldn't recommend this. Even in Python I'm leery of doing this, since as ferrous points out exceptions are supposed to be for exceptional cases meaning abusing exceptions like this dilutes their meaning.
samoth
samoth

When something looks like goto and walks like goto and smells like goto, too... why not just use goto?

It's not like goto is inherently evil. Yes, there are holy wars going over it all over the internet (and predating the internet) but so what. Someone on the internet is wrong.

It is just what you're doing! You're doing a jump out of a nested loop, don't pretend you're doing anything different. It's exactly what the assembly that the compiler produces will look like, too. And besides, it's pretty close to the "most obvious, least astonishing" way of doing it.

Given the option of simply returning from the surrounding function, I'd choose that, because it avoids using goto for no reason. But in every other case, I wouldn't try to hide the goto that I'm doing secretly.

Awesome approach. But then again, it's setting up a lambda object (with captures and all) for no good reason. The compiler might optimize that out again, but that isn't granted.

Similar is true for any such construct involving function calls of which you can assume but don't know for sure that the compiler will optimize them away.

That's somewhat going against Sutter/Alexandrescu's rule: Don't pessimize prematurely (that one immediately follows "Don't optimize prematurely"). It's not like you're going to use goto all over the place. There are singular cases where goto is just appropriate, and in these singular cases you can just use it. You can, but you probably shouldn't invest both development time and runtime into making something "better" that is already... well... good.

Adam_42
Adam_42

You could also use a boolean.


bool escape = false;
for(int i = 0; i < 10 && !escape; i++)
{
    for(j = 0; j < 10; j++)
    {
        if(i - 1 == j + 1)
        {
            escape = true;
            break;
        }
    }
}
Alpha_ProgDes
Alpha_ProgDes

If you've got two for loops you're looking at O(n*n) execution times.

I'd be tempted to refactor the code and eliminate the for loops if possible but that's just me...

In all seriousness, I'd be interested to see that.

Beginner in Game Development?  Read here. And read here.  
Brain
Brain

If you've got two for loops you're looking at O(n*n) execution times.

I'd be tempted to refactor the code and eliminate the for loops if possible but that's just me...

In all seriousness, I'd be interested to see that.

I'm on my phone right now so can't give code, but usually when you have two for loops like this, you're executing a search for a value in one list to see if it is contained in another list or similar. It is rare you actually want to iterate over each item n times and then n times again.

In that case you represent each list as a hash_map or map and you perform two find() calls, giving you a total execution time of O(2(log(n))).

Of course if you really do need to iterate both lists inside each other for some legit reason then you're screwed performance wise anyway...

WozNZ
WozNZ

Its hard to give another approach given how abstract the code samples are, but as braindigitalis say if you are searching for something change the storage to something more suitable to perform a lookup. Also have others have said create a function that does the scan and returns the result so the return does the break.

From you sample this is faster to do the same lol

int i = 2;

int j = 0;

Ignore the try/catch exception route as it carries costs that make it perform worse than the other solutions presented.

3pic_F4il_FTW
3pic_F4il_FTW


If you've got two for loops you're looking at O(n*n) execution times.

I'd be tempted to refactor the code and eliminate the for loops if possible but that's just me...


In all seriousness, I'd be interested to see that.

for(int i = 0; i < 100; ++i)
{
    if(((i / 10) - 1) == ((i % 10) + 1))
        break;
}
at least 1 is gone and we only need a normal break :P
Oluseyi
Oluseyi

This is apparently common in Python (not just for breaking out of inner loops - iterators throw an exception when they reach the end of the container!), but in C++ I wouldn't recommend this. Even in Python I'm leery of doing this, since as ferrous points out exceptions are supposed to be for exceptional cases meaning abusing exceptions like this dilutes their meaning.

Python exceptions are not meant to represent exceptional error conditions alone, so it isn't an "abuse." As you point out, sequence iteration is terminated by raising StopIteration. This kind and level of exception usage is key to Python's dynamic, introspective nature.

As to the OP's question about breaking out of nested loops, assuming the code can not be refactored into a function a pair of adjacent linear iterations, and your language does not have labeled breaks, just use a goto.

(Amusingly, this very topic was covered in this very forum as far back as 2006, with the same conclusions drawn. The more things change…)

swiftcoder
swiftcoder

In all seriousness, I'd be interested to see that.


In idiomatic Python:

[(i, j) for i, j in itertools.product(range(10), range(10)) if i - 1 == j + 1]

C++ lacks the cartesian product built-in, but it isn't all that hard to write one in terms of iterator ranges.
Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]
SeanMiddleditch
SeanMiddleditch
There was a lengthy debate/argument about this topic on the ISO C++ mailing lists earlier this year.

The take away I got was that few people on the committee want to spend any energy complicating C++ to handle the situation better under the strong belief that nested loops are bad code and just shouldn't be used.

I disagree with the reasoning, naturally, but it'll take someone with more eloquence than I and a very thorough and well-defended paper to change things, I think.
Sean Middleditch – Game Systems Engineer – Join my team!
wintertime
wintertime

If you've got two for loops you're looking at O(n*n) execution times.

I'd be tempted to refactor the code and eliminate the for loops if possible but that's just me...


In all seriousness, I'd be interested to see that.

for(int i = 0; i < 100; ++i)
{
    if(((i / 10) - 1) == ((i % 10) + 1))
        break;
}
at least 1 is gone and we only need a normal break tongue.png

And suddenly the code is more complicated and about 10-100x slower, because the divisions are taking many more cycles than the other simple operations.

Madhed
Madhed

Look ma! no breaks!


int i, j;

for(i = 0; i < 2; i++) {
    for(j = 0; j < 10; j++) {
        // if(i - 1 == j + 1) <- will be true for i==2 and j==0
    }
}

seriously though, how often will you have a situation where you need a nested loop and break out on an arbitrary condition and continue work after the loop?

Smells to me like a violation of SRP.

Some possible solutions depending on the actual algorithm:

Factor out the loop into another function and just return instead of breaking

or calculate the terminating condition beforehand and adjust the loop boundaries

or collect the affected elements and iterate over all of them

Oberon_Command
Oberon_Command

Awesome approach. But then again, it's setting up a lambda object (with captures and all) for no good reason. The compiler might optimize that out again, but that isn't granted.
Similar is true for any such construct involving function calls of which you can assume but don't know for sure that the compiler will optimize them away.


Just because I marked the lambda as capturing doesn't mean it necessarily will capture. It will (should?) only capture symbols that it references. That lambda should take up no space on the stack besides the argument to it. I'd be pretty disappointed with my compiler if it didn't optimize out the lambda.

Python exceptions are not meant to represent exceptional error conditions alone, so it isn't an "abuse." As you point out, sequence iteration is terminated by raising StopIteration. This kind and level of exception usage is key to Python's dynamic, introspective nature.


How so? What is done with exceptions that don't represent an error condition that can't be done with any other construct and is so essential to Python's nature?
alvaro
alvaro
One situation where I find myself doing this is when searching a 2-D board for a spot that satisfies some condition. I am perfectly happy to use a goto here, and some of the solutions that have been proposed wouldn't work (I don't want to change the looping variables, because I am interested in them, putting the loops in a function and using `return' might require writing a function that I cannot give a decent name to (a big no-no in my book) and having to pass a bunch of local variables for no good reason and returning two indices is messy...).
swiftcoder
swiftcoder
Honestly, I think it's hard to do better than the lambda solution, as far as clarity goes
    int i, j;
    [&] () {
        for(i = 0; i < 10; i++) {
            for(j = 0; j < 10; j++) {
                if(i - 1 == j + 1)
                    return;
            }
        }
    }();
Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.