Breaking out of a nested loop

Started by
50 comments, last by ExErvus 7 years, 9 months ago

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
Advertisement

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.

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)

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:

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.

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.

<lambda>

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.

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;
        }
    }
}

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...

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.

 

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...

This topic is closed to new replies.

Advertisement