06 - loop control

Apart from break, there is one more statement that can control the loop.

Continue statement

break jumps outside of the loop. continue jumps to the end of current iteration (step statement is still executed).

The example below prints combinations of numbers but only if they are different.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

int main()
{
	for (int j = 0; j < 4; ++j)
	{
		for (int i = 0; i < 4; ++i)
		{
			if (i == j)
				continue;

			std::cout << j << "-" << i << ", ";
		}
	}
}

The same behavior could be achieved with if statements alone, but continue is better because only this keyword is nested inside if, not the whole loop body. Less nesting generally means more readable code.

Exiting nested loops

break can be used to exit a loop (the most enclosing one). But how to exit nested loops? Some languages offer such feature by extending syntax of the break keyword, usually by allowing labels or numbers (how many scopes to exit). There is no such thing in C++.

There are multiple alternatives:

  • Use goto. Heavily discouraged because it breaks structured programming model.

  • throw an exception. Heavily discouraged because exceptions should be used only for error handling, never for control flow.

  • Use a bool variable as a flag.

  • Exit the entire function by a return statement.

Because of the mentioned rationale, only last 2 are used in practice.

Flag variable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool exit = false;
for (int y = 0; y < size_y; ++y)
{
	if (exit)
		break;

	for (int x = 0; x < size_x; ++x)
	{
		if (/* ... */)
		{
			exit = true;
			break;
		}
	}
}

It works, but has multiple disadvantages:

  • An extra variable is needed, which complicates code.

  • Multiple break and if statements might be needed, which is another complication and a good source of potential bugs.

  • The code has so many (unpredictable) relations between loop control variables and the flag that it might be hard to optimize.

In practice this way of dealing with nested loops is rarely used - usually only when due to program logic the loops already have to contain a flag variable and it can be reused.

Return statement

The preferred option, results in very clean code but it exits a lot more - the entire function.

1
2
3
4
5
6
7
8
9
10
for (int y = 0; y < size_y; ++y)
{
	for (int x = 0; x < size_x; ++x)
	{
		if (/* ... */)
		{
			return /* ... */;
		}
	}
}

In practice this is not a problem because loops can be easily moved to a separate function (to alter the scope of return statement), especially local function objects created by lambda expressions. Once you learn lambdas, you will understand how useful they are.