It's possible to express it as a set of while loops (which someone on Stack Overflow showed [1]), but it's, IMO, less readable than the goto version. Of course, the recursive solution is the most readable anyway.
Edit: Also, both the goto version and while loop version of that algorithm on the Stack Overflow post have an out of bounds index when col and row are both zero. That algorithm was meant for 1-indexed arrays and was not properly translated.
[0]: https://cs.stanford.edu/%7Eknuth/fasc5b.ps.gz
[1]: https://stackoverflow.com/questions/78614303/can-knuths-algo...
void printManyStrsWithCommas(char *s[], int n /* trusted to be positive */)
{
int i = 0;
a: printOneStr(s[i]);
++i;
if (i < n) {
printOneStr(", ");
goto a;
}
}
Here the loop condition is 'i < n', but when it is true, we need to do one more thing (print a comma) before resuming the loop. If we are to stick with non-'goto' syntax we have the following options:- Somehow cram it into the loop expression with the comma operator. This is very limited because it is an expression. For example, what if 'printOneString' can err?
- Somehow use 'while(1)' with 'break'. It may even compile to the same code as with 'goto'. But why, then, we have to use an unbounded loop syntax with a clearly bounded sequence?
- Somehow add additional variables and tests and either lose efficiency or trust the compiler to lead us out.
With 'goto' the code does only what is necessary. What 'goto' does not do is that it does not immediately convey a clear idea that the code repeats certain steps.
1:
while (printOneStr(s[i]), ++i < n)
printOneStr(", ");
2: goto start;
do {
printOneStr(", ");
start:
printOneStr(s[i]);
++i;
} while (i < n);
But for this kind of code in general, I have thought that the "do while" statement should be extended to take an extra statement after the "while (cond)" part. It could still be an empty statement, with just a semicolon, like "do ... while (cond);", exactly like it's already used; but in cases like yours, you would be able to write this: do {
printOneStr(s[i]);
++i;
} while (i < n) {
printOneStr(", ");
}
As a bonus, the current mandatory semicolon after do-while statements would stop being a special case for the grammar of C. for (i = 0; i < n; ++i);
for (i = n; i >= 0; --i);
(Note: not sure if intended that backward should run more often and include i = n) for (i = 0; i < n; i++)
for (i = n; --i >= 0; )
The second one could also be this: for (i = n - 1; i >= 0; i--)
I say this because your backwards loop accesses the inclusive range [0, n] while your forwards range accesses [0, n - 1]. for (i = 0; i <= n-1; i++)
for (i = n-1; i >= 0; i--) for (i = -1; ++i != n; )
for (i = n; --i != -1; )Your reverse iteration would result in an out of bounds access as well as unsigned integer overflow which results in an infinite loop.
'Goto' is, of course, an implementation detail and is not convenient for reasoning. Thing is loops are not necessary for reasoning either. They are an implementation detail mistakenly dressed into reasoning clothes.