33

I'd say that it's a fact that using goto is considered a bad practice when it comes to programming in C/C++.

However, given the following code

for (i = 0; i < N; ++i) 
{
    for (j = 0; j < N; j++) 
    {
        for (k = 0; k < N; ++k) 
        {
            ...
            if (condition)
                goto out;
            ...
        }
    }
}
out:
    ...

I wonder how to achieve the same behavior efficiently not using goto. What i mean is that we could do something like checking condition at the end of every loop, for example, but AFAIK goto will generate just one assembly instruction which will be a jmp. So this is the most efficient way of doing this I can think of.

Is there any other that is considered a good practice? Am I wrong when I say it is considered a bad practice to use goto? If I am, would this be one of those cases where it's good to use it?

Thank you

fuz
  • 88,405
  • 25
  • 200
  • 352
rual93
  • 553
  • 4
  • 11

14 Answers14

50

The (imo) best non-goto version would look something like this:

void calculateStuff()
{
    // Please use better names than this.
    doSomeStuff();
    doLoopyStuff();
    doMoreStuff();
}

void doLoopyStuff()
{
    for (i = 0; i < N; ++i) 
    {
        for (j = 0; j < N; j++) 
        {
            for (k = 0; k < N; ++k) 
            {
                /* do something */

                if (/*condition*/)
                    return; // Intuitive control flow without goto

                /* do something */
            }
        }
    }
}

Splitting this up is also probably a good idea because it helps you keep your functions short, your code readable (if you name the functions better than I did) and dependencies low.

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • 3
    This is reasonable, but it certainly isn't a panacea. It wouldn't work so well if `doLoopyStuff()` depended on, say, 20 variables in `calculateStuff`. It would also be awkward if `doMoreStuff()` wanted to do something with the loop indices that yielded the "hit." – Joshua Green May 26 '18 at 01:38
  • @JohnBollinger *It is often the most natural idiom C offers for the task, and that's what one should prefer. That it happens to take the question of function-call overhead right off the table is a bonus.* But I can stand that on its head, and say "Breaking it out to a function is a natural way to make the function nicely self-documenting, and that it happens to eliminate the troublesome `goto` is a bonus." – Steve Summit May 26 '18 at 02:16
  • @JoshuaGreen If you have 20 variables in scope, `calculateStuff` is way too long and complicated already and in dire need of refactoring. And returning a tuple of loop indices would not be hard - in fact, that would be another reason for separating this into a function: It is clearly a "find" operation and could look like `findVoxelSatisfying(Lambda&& condition)`. Even better, this trivially solves the "I need my local variables" - just capture them in the lambda. Abstraction to the rescue! – Max Langhof May 28 '18 at 07:53
26

If you have deeply-nested loops like that and you must break out, I believe that goto is the best solution. Some languages (not C) have a break(N) statement that will break out of more than one loop. The reason C doesn't have it is that it's even worse than a goto: you have to count the nested loops to figure out what it does, and it's vulnerable to someone coming along later and adding or removing a level of nesting, without noticing that the break count needs to be adjusted.

Yes, gotos are generally frowned upon. Using a goto here is not a good solution; it's merely the least of several evils.

In most cases, the reason you have to break out of a deeply-nested loop is because you're searching for something, and you've found it. In that case (and as several other comments and answers have suggested), I prefer to move the nested loop to its own function. In that case, a return out of the inner loop accomplishes your task very cleanly.

(There are those who say that functions must always return at the end, not from the middle. Those people would say that the easy break-it-out-to-a-function solution is therefore invalid, and they'd force the use of the same awkward break-out-of-the-inner-loop technique(s) even when the search was split off to its own function. Personally, I believe those people are wrong, but your mileage may vary.)

If you insist on not using a goto, and if you insist on not using a separate function with an early return, then yes, you can do something like maintaining extra Boolean control variables and testing them redundantly in the control condition of each nested loop, but that's just a nuisance and a mess. (It's one of the greater evils that I was saying using a simple goto is lesser than.)

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • 3
    Yes! Use the best tool for the job, even if it has a bad reputation. – Deduplicator May 25 '18 at 13:26
  • 14
    *There are those who say that functions must always return at the end, not from the middle* I dislike those people. Why not return as soon as we have found what we need I always ask them. – NathanOliver May 25 '18 at 13:26
  • 10
    Even the [C++ Core Guidelines](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#es76-avoid-goto) say that using `goto` to break out of nested loops is good! – ricco19 May 25 '18 at 13:37
  • 1
    I'm one of that persons that, programming in C, say that function must (well... should) have a single `return`, possibly at the end. It's because, in C, there aren't classes with destructors and a single return simplify memory and file management. But I'm agree that this point is completely outmoded in C++ (with exceptions that completely ruin the one-return strategy and with destructors that can cleanly substitute it). Anyway, I'm old and it's difficult modify old habits. So, also in C++, I tend to have a single `return` at the end of the functions. – max66 May 25 '18 at 13:40
17

I think that goto is a perfectely sane thing to do here, and is one of it's exceptional use cases per the C++ Core Guidelines.

However, perhaps another solution to be considered is an IIFE lambda. In my opinion this is slightly more elegant than declaring a separate function!

[&] {
    for (int i = 0; i < N; ++i)
        for (int j = 0; j < N; j++)
            for (int k = 0; k < N; ++k)
                if (condition)
                    return;
}();

Thanks to JohnMcPineapple on reddit for this suggestion!

ricco19
  • 713
  • 5
  • 16
12

In this case you don't wan't to avoid using goto.

In general the use of goto should be avoided, however there are exceptions to this rule, and your case is a good example of one of them.

Let's look at the alternatives:

for (i = 0; i < N; ++i) {
    for (j = 0; j < N; j++) {
        for (k = 0; k < N; ++k) {
            ...
            if (condition)
                break;
            ...
        }
        if (condition)
            break;
    }
    if (condition)
        break;
}

Or:

int flag = 0
for (i = 0; (i < N) && !flag; ++i) {
    for (j = 0; (j < N) && !flag; j++) {
        for (k = 0; (k < N) && !flag; ++k) {
            ...
            if (condition) {
                flag = 1
                break;
            ...
        }
    }
}

Neither of these is as concise or as readable as the goto version.

Using a goto is considered acceptable in cases where you're only jumping ahead (not backward) and doing so makes your code more readable and understandable.

If on the other hand you use goto to jump in both directions, or to jump into a scope which could potentially bypass variable initialization, that would be bad.

Here's a bad example of goto:

int x;
scanf("%d", &x);
if (x==4) goto bad_jump;

{
    int y=9;

// jumping here skips the initialization of y
bad_jump:

    printf("y=%d\n", y);
}

A C++ compiler will throw an error here because the goto jumps over the initialization of y. C compilers however will compile this, and the above code will invoke undefined behavior when attempting to print y which will be uninitialized if the goto occurs.

Another example of proper use of goto is in error handling:

void f()
{
    char *p1 = malloc(10);
    if (!p1) {
        goto end1;
    }
    char *p2 = malloc(10);
    if (!p2) {
        goto end2;
    }
    char *p3 = malloc(10);
    if (!p3) {
        goto end3;
    }
    // do something with p1, p2, and p3

end3:
    free(p3);
end2:
    free(p2);
end1:
    free(p1);
}

This performs all of the cleanup at the end of the function. Compare this to the alternative:

void f()
{
    char *p1 = malloc(10);
    if (!p1) {
        return;
    }
    char *p2 = malloc(10);
    if (!p2) {
        free(p1);
        return;
    }
    char *p3 = malloc(10);
    if (!p3) {
        free(p2);
        free(p1);
        return;
    }
    // do something with p1, p2, and p3

    free(p3);
    free(p2);
    free(p1);
}

Where the cleanup is done in multiple places. If you later add more resources that need to be cleaned up, you have to remember to add the cleanup in all of these places plus the cleanup of any resources that were obtained earlier.

The above example is more relevant to C than C++ since in the latter case you can use classes with proper destructors and smart pointers to avoid manual cleanup.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • 3
    Notably, your cleanup example is easily accomplished goto-free by idiomatic C++. OP still hasn't decided which language he wants though. – Max Langhof May 25 '18 at 13:38
  • @MaxLanghof Correct. I added a relevant note at the end. – dbush May 25 '18 at 13:40
  • Anyway, in this case coalescing allocations might be a good idea, if you don't simply switch to stack-allocation because it's so much memory. – Deduplicator May 25 '18 at 13:40
  • 3
    @Deduplicator I just used memory allocation in this case as a simple example. In more complicated cases it could involve opening files or sockets that need to be closed, obtaining resources from libraries that need to be released, or any combination of those plus allocating memory. – dbush May 25 '18 at 13:42
5

Lambdas let you create local scopes:

[&]{
  for (i = 0; i < N; ++i) 
  {
    for (j = 0; j < N; j++) 
    {
      for (k = 0; k < N; ++k) 
      {
        ...
        if (condition)
          return;
        ...
      }
    }
  }
}();

if you also want the ability to return out of that scope:

if (auto r = [&]()->boost::optional<RetType>{
  for (i = 0; i < N; ++i) 
  {
    for (j = 0; j < N; j++) 
    {
      for (k = 0; k < N; ++k) 
      {
        ...
        if (condition)
          return {};
        ...
      }
    }
  }
}()) {
  return *r;
}

where returning {} or boost::nullopt is a "break", and returning a value returns a value from the enclosing scope.

Another approach is:

for( auto idx : cube( {0,N}, {0,N}, {0,N} ) {
  auto i = std::get<0>(idx);
  auto j = std::get<1>(idx);
  auto k = std::get<2>(idx);
}

where we generate an iterable over all 3 dimensions and make it a 1 deep nested loop. Now break works fine. You do have to write cube.

In this becomes

for( auto[i,j,k] : cube( {0,N}, {0,N}, {0,N} ) ) {
}

which is nice.

Now, in an application where you are supposed to be responsive, looping over a large 3 dimensional range at primiary control flow level is often a bad idea. You can thread it off, but even then you end up with problem that the thread runs too-long. And most 3 dimensional large iterations I've played with can benefit from using sub-task threading themselves.

To that end, you'll end up wanting to categorize your operation based on what kind of data it accesses, then pass your operation to something that schedules the iteration for you.

auto work = do_per_voxel( volume,
  [&]( auto&& voxel ) {
    // do work on the voxel
    if (condition)
      return Worker::abort;
    else
      return Worker::success;
  }
);

then the control flow involved goes into the do_per_voxel function.

do_per_voxel isn't going to be a simple naked loop, but rather a system to rewrite the per-voxel tasks into per-scanline tasks (or even per-plane tasks depending on how large the planes/scanlines are at runtime (!)) then dispatch them in turn to a thread pool managed task scheduler, stitch up the resulting task handles, and return a future-like work that can be awaited on or used as a continuation trigger for when the work is done.

And sometimes you just use goto. Or you manually break out functions for subloops. Or you use flags to break out of deep recursion. Or you put the entire 3 layer loop in its own function. Or you compose the looping operators using a monad library. Or you can throw an exception (!) and catch it.

The answer to almost every question in is "it depends". The scope of problem and the number of techniques you have available is large, and the details of the problem change the details of the solution.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 2
    It's like a case study in how complicated you can make your code in order to cargo-cult-avoid a simple `goto` :) – Lightness Races in Orbit May 25 '18 at 13:59
  • 2
    @LightnessRacesinOrbit *nod* true, but in many cases you get other benefits. I've stripped `goto`s out of code as I'm RAII resources (moving from C to C++ style error handling). Once I've lambdaed a section of code, I can strip out dependencies and refactor into a function (shortening 100s of line long functions to make them easier to understand), or permit a naive N-d loop to be safely converted into a task-pool based one (as it was identified as a performance bottleneck). I wouldn't take working, tested code and rewrite it just to remove a goto. But stripping them simplifies control flow. – Yakk - Adam Nevraumont May 25 '18 at 14:04
  • @LightnessRacesinOrbit You can't say the C++17 solution is complicated - I think it's actually very expressive. It puts a name on all the involved steps and clearly expresses the task to be done, unlike OP's code. Presumably a lot of the mentioned things would be library code. – Max Langhof May 25 '18 at 14:11
  • 1
    @MaxLanghof: That's not untrue. I'm not yet convinced I'd use such new tools (let's face it, it's esoteric at the moment) if I just had this one candidate in the code. But for a codebase with this pattern in a few places I'd consider it. – Lightness Races in Orbit May 25 '18 at 14:40
4

Alternative - 1

You can do something like follows:

  1. Set a bool variable in the beginning isOkay = true
  2. All of your forloop conditions, add an extra condition isOkay == true
  3. When your your custom condition is satisfied/ fails, set isOkay = false.

This will make your loops stop. An extra bool variable would be sometimes handy though.

bool isOkay = true;
for (int i = 0; isOkay && i < N; ++i)
{
    for (int j = 0; isOkay && j < N; j++)
    {
        for (int k = 0; isOkay && k < N; ++k)
        {
            // some code
            if (/*your condition*/)
                 isOkay = false;
        }
     }
}

Alternative - 2

Secondly. if the above loop iterations are in a function, best choice is to return result, when ever the custom condition is satisfied.

bool loop_fun(/* pass the array and other arguments */)
{
    for (int i = 0; i < N ; ++i)
    {
        for (int j = 0; j < N ; j++)
        {
            for (int k = 0; k < N ; ++k)
            {
                // some code
                if (/* your condition*/)
                    return false;
            }
        }
    }
    return true;
}
JeJo
  • 30,635
  • 6
  • 49
  • 88
3

Break your for loops out into functions. It makes things significantly easier to understand because now you can see what each loop is actually doing.

bool doHerpDerp() {
    for (i = 0; i < N; ++i) 
    {
        if (!doDerp())
            return false;
    }
    return true;
}

bool doDerp() {
    for (int i=0; i<X; ++i) {
        if (!doHerp())
            return false;
    }
    return true;
}

bool doHerp() {
    if (shouldSkip)
        return false;
    return true;
}
UKMonkey
  • 6,941
  • 3
  • 21
  • 30
  • 2
    It was so short and easy to understand, no wonder it had to be turned into a puzzle. – Deduplicator May 25 '18 at 13:36
  • @Deduplicator The art of software engineering is to take a problem and cut it down into smaller problems. With that many nested for loops and an exit strategy like that, the problem can very clearly be broken down more. – UKMonkey May 25 '18 at 13:46
  • 2
    No, the art of software engineering isn't to take a problem and cut it down into smaller problems without sense, but breaking it down into the right, easily handled, and potentially reusable, pieces. Spaghetti-code comes in different guises. – Deduplicator May 25 '18 at 13:55
  • @Deduplicator I agree, and trust me I've seen them all... but if you can't give Herp and Derp good names at this point, then it demonstrates that you've cut your problem down incorrectly and puts the whole nested loops solution in question. In any case, there's absolutely no need for a goto in c++ at all; since it demonstrates only that the problem hasn't been cut up small enough. – UKMonkey May 25 '18 at 14:16
  • The concept of "iterate over a 3D array" is most sensibly and obviously expressed by three nested loops. Breaking them into separate functions results in pure boilerplate overhead in most cases (and even if you keep them generic with lambdas you obstruct return values and save virtually no code at the use site compared to the simple loops). – Max Langhof May 28 '18 at 07:59
  • @MaxLanghof That's opinion, and exactky the reason why this question was closed (aside - if it is that much clearer, then I don't disagree with your answer; but almost every time I've worked with an array, one is only concerned about a single dimension of it for the given calculation) – UKMonkey May 29 '18 at 08:38
3

Is there any other that is considered a good practice? Am I wrong when I say it is considered a bad practice to use goto?

goto can be misused and overused, but I dont see any of the two in your example. Breaking out of a deeply nested loop is most clearly expressed by a simple goto label_out_of_the_loop;.

It is bad practice to use many gotos that jump to different labels, but in such cases it isnt the keyword goto itself that makes your code bad. It is the fact that you are jumping around in the code making it hard to follow that makes it bad. If however, you need a single jump out of nested loops then why not use the tool that was made for exactly that purpose.

To use a made up out of thin air analogy: Imagine you live in a world where in the past it was hip to hammer nails into walls. In recent times it became more fashinable to drill screws into walls using screwdrivers and hammers are completely out of fashion. Now consider you have to (despite being a bit old-fashinoned) get a nail into a wall. You should not refrain from using a hammer to do that, but maybe you should rather ask yourself if you really need a nail in the wall instead of a screw.

(Just in case it isnt clear: The hammer is goto and the nail in the wall is a jump out of a nested loop while the screw in the wall would be using functions to avoid the deep nesting alltogether ;)

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
2

One possible way is to assign a boolean value to a variable that represents the state. This state can later be tested using an "IF" conditional statement for other purposes later on in the code.

Chigozie Orunta
  • 448
  • 3
  • 13
  • 6
    This is indeed the typical contortion employed to avoid using `goto` in situations where `goto` is the right thing to do. – John Bollinger May 25 '18 at 13:22
2

as far as your comment on efficiency compiling the both options in release mode on visual studio 2017 produces the exact same assembly.

for (int i = 0; i < 5; ++i)
{
    for (int j = 0; j < 5; ++j)
    {
        for (int k = 0; k < 5; ++k)
        {
            if (i == 1 && j == 2 && k == 3) {
                goto end;

            }
        }
    }
}
end:;

and with a flag.

bool done = false;
for (int i = 0; i < 5; ++i)
{
    for (int j = 0; j < 5; ++j)
    {
        for (int k = 0; k < 5; ++k)
        {
            if (i == 1 && j == 2 && k == 3) {
                done = true;
                break;
            }
        }
        if (done) break;
    }
    if (done) break;
}

both produce..

xor         edx,edx  
xor         ecx,ecx  
xor         eax,eax  
cmp         edx,1  
jne         main+15h (0C11015h)  
cmp         ecx,2  
jne         main+15h (0C11015h)  
cmp         eax,3  
je          main+27h (0C11027h)  
inc         eax  
cmp         eax,5  
jl          main+6h (0C11006h)  
inc         ecx  
cmp         ecx,5  
jl          main+4h (0C11004h)  
inc         edx  
cmp         edx,5  
jl          main+2h (0C11002h)    

so there is no gain. Another option if your using a modern c++ compiler is to wrap it in a lambda.

[](){
for (int i = 0; i < 5; ++i)
{
    for (int j = 0; j < 5; ++j)
    {
        for (int k = 0; k < 5; ++k)
        {
            if (i == 1 && j == 2 && k == 3) {
                return;
            }
        }

    }
}
}();

again this produces the exact same assembly. Personally I think using goto in your example is perfectly acceptable. It is clear what is happening to anyone else, and makes for more concise code. Arguably the lambda is equally as concise.

rmawatson
  • 1,909
  • 12
  • 20
  • 2
    I'd expect a decent compiler given the right optimizer flags to just generate a `nop` out of this code. – nriedlin May 26 '18 at 14:39
  • @nriedlin [Indeed. MSVC is definitely behind on this.](https://godbolt.org/g/UYtLxw) – Max Langhof May 28 '18 at 08:11
  • 1
    With proper motivation, each one tends to produce [nearly identical assembly](https://godbolt.org/g/ygpLe3) for all three cases though. – Max Langhof May 28 '18 at 08:18
2

Specific

IMO, in this specific example, I think it is important to notice a common functionality between your loops. (Now I know that your example isn't necessarily literal here, but just bear with me for a sec) as each loop iterates N times, you can restructure your code like the following:

Example

int max_iterations = N * N * N;
for (int i = 0; i < max_iterations; i++)
{
    /* do stuff, like the following for example */
    *(some_ptr + i) = 0; // as opposed to *(some_3D_ptr + i*X + j*Y + Z) = 0;
    // some_arr[i] = 0; // as oppose to some_3D_arr[i][j][k] = 0;
}

Now, it is important to remember that all loops, while for or otherwise, are really just syntatic sugar for the if-goto paradigm. I agree with the others that you ought to have a function return the result, however I wanted to show an example like the above in which that may not be the case. Granted, I'd flag the above in a code review but if you replaced the above with a goto I'd consider that a step in the wrong direction. (NOTE -- Make sure that you can reliably fit it into your desired datatype)

General

Now, as a general answer, the exit conditions for your loop may not be the same everytime (like the post in question). As a general rule, pull as many unneeded operations out of your loops (multiplications, etc.) as far out as you can as, while compilers are getting smarter everyday, there is no replacement for writing efficient and readable code.

Example

/* matrix_length: int of m*n (row-major order) */
int num_squared = num * num;
for (int i = 0; i < matrix_length; i++)
{
    some_matrix[i] *= num_squared; // some_matrix is a pointer to an array of ints of size matrix_length
}

rather than writing *= num * num, we no longer have to rely on the compiler to optimize this out for us (though any good compiler should). So any doubly or triply nested loops which perform the above functionality would also benefit not only your code, but IMO writing clean and efficient code on your part. In the first example, we could have instead had *(some_3D_ptr + i*X + j*Y + Z) = 0;! Do we trust the compiler to optimize out i*X and j*Y, so that they aren't computed every iteration?

bool check_threshold(int *some_matrix, int max_value)
{
    for (int i = 0; i < rows; i++)
    {
        int i_row = i*cols; // no longer computed inside j loop unnecessarily.
        for (int j = 0; j < cols; j++)
        {
            if (some_matrix[i_row + j] > max_value) return true;
        }
    }
    return false;
}

Yuck! Why aren't we using classes provided by the STL or a library like Boost? (we must be doing some low level/high performant code here). I couldn't even write a 3D version, due to the complexity. Even though we have hand optimized something, it may even be better to use #pragma unroll or similar preprocessor hints if your compiler allows.

Conclusion

Generally, the higher the abstraction level you can use, the better, however if say aliasing a 1-Dimensional row-major order matrix of integers to a 2-Dimensional array makes your code-flow harder to understand/extend, is it worth it? Likewise, that also may be an indicator to make something into its own function. I hope that, given these examples, you can see that different paradigms are called for in different places, and its your job as the programmer to figure that out. Don't go crazy with the above, but make sure you know what they mean, how to use them, and when they are called for, and most importantly, make sure the other people using your codebase know what they are as well and have no qualms about it. Good luck!

jfh
  • 163
  • 13
  • As soon as you need to use the 3D indices of the original example, you run into trouble with your primary suggestion and have to write code that is certainly uglier than the original. Also: _"Do we trust the compiler to optimize out i*X and j*Y, so that they aren't computed every iteration?"_ Yes, we do. – Max Langhof May 28 '18 at 08:06
  • While you’re right that it’s uglier, I’ve written some 3D cuda code that used shared memory in which ugly code was the only way (to construct the 3D shared matrix). Now, in regards to your last comment, would you like me to embolden where I say #pragma unroll or did you just gloss over it, making your mind up before you got there? You’re entirely welcome to put all your faith in the compiler, especially when only writing higher level code, but could you guarantee those optimization’s for every target machine a programmer is writing code for? Do you trust a compiler to optimize an inline goto? – jfh May 28 '18 at 13:41
1
bool meetCondition = false;
for (i = 0; i < N && !meetCondition; ++i) 
{
    for (j = 0; j < N && !meetCondition; j++) 
    {
        for (k = 0; k < N && !meetCondition; ++k) 
        {
            ...
            if (condition)
                meetCondition = true;
            ...
        }
    }
}
nameless1
  • 203
  • 1
  • 6
1

There are already several excellent answers that tell you how you can refactor your code, so I won’t repeat them. There isn’t a need to code that way for efficiency any more; the question is whether it’s inelegant. (Okay, one refinement I’ll suggest: if your helper functions are only ever intended to be used inside the body of that one function, you might help the optimizer out by declaring them static, so it knows for certain that the function does not have external linkage and will never be called from any other module, and the hint inline can’t hurt. However, previous answers say that, when you use a lambda, modern compilers don’t need any such hints.)

I’m going to challenge the framing of the question a bit. You’re correct that most programmers have a taboo against using goto. This has, in my opinion, lost sight of the original purpose. When Edsger Dijkstra wrote, “Go To Statement Considered Harmful,” there was a specific reason he thought so: the “unbridled” use of go to makes it too hard to reason formally about the current program state, and what conditions must currently be true, compared to control flow from recursive function calls (which he preferred) or iterative loops (which he accepted). He concluded:

The go to statement as it stands is just too primitive; it is too much an invitation to make a mess of one's program. One can regard and appreciate the clauses considered as bridling its use. I do not claim that the clauses mentioned are exhaustive in the sense that they will satisfy all needs, but whatever clauses are suggested (e.g. abortion clauses) they should satisfy the requirement that a programmer independent coordinate system can be maintained to describe the process in a helpful and manageable way.

Many C-like programming languages, for example Rust and Java, do have an additional “clause considered as bridling its use,” the break to a label. An even more restricted syntax might be something like break 2 continue; to break out of two levels of the nested loop and resume at the top of the loop containing them. This presents no more of a problem than a C-style break to what Dijkstra wanted to do: defining a concise description of the program state that programmers can keep track of in their heads or a static analyzer would find tractable.

Restricting goto to constructions like this makes it simply a renamed break to a label. The remaining problem with it is that the compiler and the programmer don’t necessarily know you’re only going to use it this way.

If there’s an important post-condition that holds after the loop, and your concern with goto is the same as Dijkstra’s, you might consider stating it in a short comment, something like // We have done foo to every element, or encountered condition and stopped. That would alleviate the problem for humans, and a static analyzer should do fine.

Davislor
  • 14,674
  • 2
  • 34
  • 49
0

The best solution is to put the loops in a function and then return from that function.

This is essentially the same thing as your goto example, but with the massive benefit that you avoid having yet another goto debate.

Simplified pseudo code:

bool function (void)
{
  bool result = something;


  for (i = 0; i < N; ++i) 
    for (j = 0; j < N; j++) 
      for (k = 0; k < N; ++k) 
        if (condition)
          return something_else;
  ...
  return result;
}

Another benefit here is that you can upgrade from bool to an enum if you come across more than 2 scenarios. You can't really do that with goto in a readable way. The moment you start to use multiple gotos and multiple labels, is the moment you embrace spaghetti coding. Yes, even if you just branch downwards - it will not be pretty to read and maintain.

Notably, if you have 3 nested for loops, that may be an indication that you should try to split your code up in several functions and then this whole discussion might not even be relevant.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 1
    While all you say is correct the fact remains that you replace the *goto* debate with the *return* debate ;-) which in my experience actually has greater merit (because it's effectively a jump which is even less local). In my experience multiple returns are elegant at first but create problems later when the complexity of the function is growing, introducing cleanup etc. (Of course arguably that complexity would be an indicator to split the function, but still.) – Peter - Reinstate Monica May 25 '18 at 14:51
  • @PeterA.Schneider When there is a need for clean-up, it becomes obvious that the return version is so much better. What you do then, is to add another function so that the call stack becomes: caller -> resource_allocator -> algorithm. This separates resource management from the actual algorithm, improving the design and making the program more maintainable. This is superior to clean-up à la BASIC "on error goto". – Lundin May 28 '18 at 06:47
  • That is true too ;-). A good design (or refactoring) should look like that. – Peter - Reinstate Monica May 28 '18 at 07:36