1. C style loop allows joining multiple arrays by index:
for (size_t i = 0; i < a.size(); i++) { do_something_together(a[i], b[i]); }
The use cases are the same as for the join statement in SQL:
select a.x, b.y from a join b on (a.p == b.p)
Here also one may argue, "keep all fields you would ever need together in one table". Unfortunately this does not always work and it may be more important things influencing the clarity of the code. There is often no obvious and easy construct to iterate over multiple arrays, having values at the same position of each array together. See this question for the possible approaches, many of them look complex. This is difficult to add later and should be built into the language, envisioning something like
for (ax: a join bx: b) { do_something_together(ax, bx); }
Of course, to be efficient this should not work just by copying. It should be an assertion that the arrays have the same dimensions or otherwise just "outer join" (loop terminates after reaching the boundary of the shorter array).
I am mostly using the "for each" style loops, but some need to be reworked into "old style" loops if they use two or more arrays this way in the iteration.
2. There are also cases when adjacent elements are needed:
for (size_t i = 1; i < a.size(); i++) { double difference = a[i] - a[i-1]; ... }
This needs precautions for the first or last member of the array, anyway you also need another member that is not available in the "modern" ranged loop. Algorithms like filtering, smoothing, differentiating and the like often need access to the values that are spread around the current index. Here is the access as required, for the 2D array, by Crank-Nicolson method, the golden classic of numeric computation (image credit):

3. There may be cases when just the number of the iteration is needed, like printing the numbered list. It is very stupid when you cannot easily get it and need to define and maintain a separate counter. The question has been asked here, and, again, most of solutions are just too complex to be practical.
4. Least, not the last: identifying the first and the last iteration. The most obvious case is when all you need is to produce the comma delimited list of items: it must be no comma after the last item. See this question for all fun the people attempted. Surely the classic C++ look also looks ugly, but still:
for (int k = 0; k < a.size(); k++) { if (k + 1 == a.size()) { // Last iteration } ... }
something like
for (int x: a) { ... if (last x) { ... } }
may look nicer, but how can you take value and return something that depends on the position inside the loop. Due that last and first must likely be the keywords built into the language, or some system functions that are processed in a very specific way with the help of compiler.
Some of these problems are also solvable with C++ iterators but the syntax also looks quite cluttered.
P.S. This answer advocates that it is important to have a loop where the loop variable is just an integer, supporting the usual arithmetic and usable with any array. There is a comment that it does not need to be a typical C loop, a simpler syntax like for int x in a .. b would also suffice. But even if loops providing just a reference to the member of collection being iterated look very simple and clean in most cases, there are other cases where they cannot be easily used.