A case where i consistently use gotos; alternatives?

Hi,
there's a case in which i always use gotos, because all the alternatives i can think of can only make goto-haters happy and readability trash, which is a price i dont intend to pay.
It's when i've an if-else like structure, in which the if is an iteration.
For example if i've a vector of objects, i must perform an action as soon as i find one that has a certain something (the if part), and if none of them does i perform some other actions (the else part)

Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
vector<int> vect;

vect.push_back(0)
vect.push_back(0)
vect.push_back(0)
vect.push_back(5)
vect.push_back(0)

//IF
for(int i : vect)
	{
	if(i > 3)
		{
		//do stuff
		goto END_IF;
		}
	}
//ELSE
	
//do some other stuff

END_IF:


A "break" would be enough if there wasn't that "else" part which mustn't be performed as long as the "if" part succeeded.

Personally i'm 100% fine with using gotos when they heal more than they can harm, but there are massive goto haters around here so if i can get rid of that WITHOUT paying additional variables price i'll be happy.
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template <typename T>
bool any_is_greater_than_3(const T &xs){
    auto beg = xs.begin();
    auto end = xs.end();
    return std::find_if(beg, end, [](const auto &x){ return x > 3; }) != end;
}

void foo(){
    vector<int> vect;

    vect.push_back(0);
    vect.push_back(0);
    vect.push_back(0);
    vect.push_back(5);
    vect.push_back(0);

    if (any_is_greater_than_3(vect)){
        //do stuff
    }else{
        //do some other stuff
    }
}

Although I'm generally more likely to do
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void foo(){
    vector<int> vect;

    vect.push_back(0);
    vect.push_back(0);
    vect.push_back(0);
    vect.push_back(5);
    vect.push_back(0);

    bool found = false;
    for (auto i : vect){
        if (i > 3){
            found = true;
            break;
        }
    }
    if (found){
        //do stuff
    }else{
        //do some other stuff
    }
}

I don't see how either option is less readable than a goto, honestly.
i never thought of the first one, i'll totally switch to that before sharing my code, thanks!

the second is not less readable but it adds a variable when you could pretty much do it my way… i mean you still use a camouflaged goto anyway, why add a variable?


Still thanks for the first one
i mean you still use a camouflaged goto anyway
Is my second snippet very different from this?
1
2
3
4
5
6
7
8
9
10
11
12
bool any_is_greater_than_3(const std::vector<int> &xs){
    for (auto i : xs)
        if (i > 3)
            return true;
}

//...
    if (any_is_greater_than_3(vect)){
        //do stuff
    }else{
        //do some other stuff
    }
Or from this?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool any_is_greater_than_3(const std::vector<int> &xs){
    auto beg = xs.begin();
    auto end = xs.end();
    for (auto i = beg; i != end; ++i)
        if (*i > 3)
            return true;
}

//...
    if (any_is_greater_than_3(vect)){
        //do stuff
    }else{
        //do some other stuff
    }
Or from this?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template <typename It, typename F>
It my_find_if(It beg, It end, const F &f){
    for (auto i = beg; i != end; ++i)
        if (f(*i))
            return i;
    return end;
}

bool any_is_greater_than_3(const std::vector<int> &xs){
    auto beg = xs.begin();
    auto end = xs.end();
    return my_find_if(beg, end, [](const auto &x){ return x > 3; }) != end;
}

//...
    if (any_is_greater_than_3(vect)){
        //do stuff
    }else{
        //do some other stuff
    }
I think you see where I'm going with this.

All control flow constructs in any language are eventually reduced to some form of goto. If, for, return statements, exceptions, polymorphic calls... Everything is some kind of goto.

Every time you find yourself asking "isn't X really just Y written differently?" you really should be asking "what am I getting by writing Y like X?"
99% of the time, replacing a goto with a structured equivalent makes the code a little bit more robust (as in, less likely to break if it changes).
Last edited on
You're basically talking about Python's "else" clause for a "for" loop, the canonical example being:

1
2
3
4
5
6
7
for n in range(2, 10):
    for x in range(2, n):
        if n % x == 0:
            print(n, 'equals', x, '*', n//x)
            break
    else: # only executed if the break didn't occur
        print(n, 'is a prime number')

In C++, I usually avoid a flag by testing if the end condition of the loop was reached (sometimes it's too much trouble or simply won't work).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    for (int n = 2; n < 10; n++)
    {
        int x = 2;
        for ( ; x < n; x++)
        {
            if (n % x == 0)
            {
                std::cout << n << " equals " << x << " * " << n/x << '\n';
                break;
            }
        }
        if (x == n) // if there was no break
            std::cout << n << " is a prime number\n";
    }

I agree that goto's are good in some situations. E.g., if you want to jump out of nested loops.

1
2
3
4
5
6
7
8
9
10
    for (...)
    {
        for (...)
        {
            ...
            if (...)
                goto break_outer;
        }
    }
break_outer:

I forget what language (probably more than one) allows you to name the loops so you can break out of a specific one:

1
2
3
4
5
6
7
8
9
10
    outer:
    for (...)
    {
        for (...)
        {
            ...
            if (...)
                break outer;
        }
    }

That
1
2
3
4
5
6
7
8
9
10
11
12
13
14
for (int n = 2; n < 10; n++)
    {
        int x = 2;
        for ( ; x < n; x++)
        {
            if (n % x == 0)
            {
                std::cout << n << " equals " << x << " * " << n/x << '\n';
                break;
            }
        }
        if (x == n) // if there was no break
            std::cout << n << " is a prime number\n";
    }

seems quite neat too, i might use that as well.

The one of your last example should be Java.
I find Python's naming of the for-else construct odd. It would make sense in a C-like language, by analogy to if-else:
1
2
3
4
5
6
7
8
9
10
11
12
if (condition){
    assert(condition);
}else{
    assert(!condition);
}

for (; condition; ){
    assert(condition);
}else{
    assert(!condition);
    //(the loop ran no times or all the way through)
}
But Python's for is ranged-based, not condition-based.

Having to break out of an outer loop is usually a sign that the code is too complex and needs another function.
Last edited on
even the class version is using another variable. Its a temporary, and hopefully just a register down in the cpu, but so is bool found. Both need at least a register to hold the value.

you are asking for the computer to know something about your data (is there an item in a block that is > 3) without storing that information anywhere. The only way I know to do that is to trap the information when the vector is loaded or modified by your program and swap a function pointer when it is true and reverse them again if it becomes false again in the future. Then you can just call the appropriate version of the function. While i say function pointer you can abstract that, but it is what it is. And, ... a function pointer is a variable (or a constant, but regardless, you still are storing the information about the data, just in a convoluted way). This is probably just as ugly as anything else offered, unless you just insist on goto avoidance.

IMHO bool found is your best answer here.

You can avoid some of the jumping you are doing if performance is what you seek, but you cannot, in any way I can see, avoid storing the fact that you need to know about your data's contents (whether in a temporary or in an explicitly declared function). You can do even more work to avoid storing the fact (by relearning it in each block by iteration), but that is self defeating.



if you're trying to break out of something deeply nested (instead of just returning), it might be a sign the function is too long. Just make more functions

instead of this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bool FuncTooLong()
{
    for (int i=0; i<99; ++i)
    {
        for (int j=0; j<99; ++j)
        {
            if (i+j==130)
                goto found_130;
        }
    }
    return false; // Failure

found_130:
    cout << "yay!\n";
    return true;  // Success
}


maybe this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void FuncYay()
{
    cout << "yay!\n";
}

bool FuncA()
{
    for (int i=0; i<99; ++i)
    {
        for (int j=0; j<99; ++j)
        {
            if (i+j==130)
            {
                FuncYay();
                return true;
            }
        }
    }
    return false;
}

Last edited on
if you are willing to lose the range-based loop and move i outside the loop, you can do this, which has no extra variables:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int i = 0;
 for(i = 0; i < vect.size(); i++) //this can be cleaner, of course, but it can't be range based?
 {
     if(vect[i] > 3)            
     {              
        //do something
       break;
     }
      
 }
      if(vect[i] <= 3)
      {
       //do something else
      }


here, an extra comparison and retention of 'i' saves the data, so its still not avoiding the 'gotta save the info if you want to know it' issue that I said earlier. Its just using what you have already to retain that info, instead of storing it explicitly, and it changes the meaning of i as well.
Last edited on
Jonnin, your last example won't work. If you don't break out of the loop then i will be out of bounds at line 11.

Barnack, I think the non-goto examples are much clearer than your original code. The basic logic is:
1
2
3
4
5
if (an item is greater than 3) {
    do something to it.
} else {
    do something different.
}

The goto inside the loop obscures this logic. I think it's much easier to read if the code is:
1
2
3
4
5
6
search for an item greater than 3
if (found it) {
    do something to it;
} else {
    do something different;
}

And that's exactly what helios suggested.
Good catch, and exactly what I deserve for copy/paste lol. You do need to check i against vect.size() in the second if statement. Sorry about that!

I still prefer the explicit extra boolean. This save a variable concept is just academic...
Last edited on
Topic archived. No new replies allowed.