95

In C++11 you can use a range-based for, which acts as the foreach of other languages. It works even with plain C arrays:

int numbers[] = { 1, 2, 3, 4, 5 };
for (int& n : numbers) {
    n *= 2;
}

How does it know when to stop? Does it only work with static arrays that have been declared in the same scope the for is used in? How would you use this for with dynamic arrays?

Paul Manta
  • 30,618
  • 31
  • 128
  • 208
  • 11
    There are no "dynamic" arrays in C or C++ per se - there are array types and then there are pointers that may or may not point to an array or a dynamically-allocated block of memory that mostly behaves like an array. For any array of type T[n], its size is encoded in the type and can be accessed by `for`. But the moment that array decays to a pointer, the size information is lost. – JohannesD Oct 29 '11 at 16:43
  • 2
    In your example, the number of elements in `numbers` is `sizeof(numbers)/sizeof(int)`, for instance. – JohannesD Oct 29 '11 at 16:49

6 Answers6

63

It works for any expression whose type is an array. For example:

int (*arraypointer)[4] = new int[1][4]{{1, 2, 3, 4}};
for(int &n : *arraypointer)
  n *= 2;
delete [] arraypointer;

For a more detailed explanation, if the type of the expression passed to the right of : is an array type, then the loop iterates from ptr to ptr + size (ptr pointing to the first element of the array, size being the element count of the array).

This is in contrast to user defined types, which work by looking up begin and end as members if you pass a class object or (if there is no members called that way) non-member functions. Those functions will yield the begin and end iterators (pointing to directly after the last element and the begin of the sequence respectively).

This question clears up why that difference exists.

Community
  • 1
  • 1
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • 9
    I think the question was _how_ does it work, not _when_ does it work – sehe Oct 29 '11 at 14:12
  • 1
    @sehe the question contained multiple '?'es. One was "Does it work with ... ?". I explained both of *how* and *when* it works. – Johannes Schaub - litb Oct 29 '11 at 14:18
  • 9
    @JohannesSchaub: I think the "how" issue here is how exactly you get the size of an object of an array type in the first place (because of the pointers vs. arrays confusion, not nearly everyone knows that the size of an array *is* available to the programmer.) – JohannesD Oct 29 '11 at 16:46
  • I believe it *only* looks for non-member `begin`\`end`. It just happens that `std::begin`\`std::end` use the member functions, and will be used if a better match is not available. – Dennis Zickefoose Oct 29 '11 at 17:28
  • 3
    @Dennis no in Madrid it was decided to change that and to favor begin and end members. To not favor begin and end members caused ambiguities that are difficult to avoid. – Johannes Schaub - litb Oct 29 '11 at 18:58
  • Well this topic was just ripe with misinformation then, thanks. – Dennis Zickefoose Oct 29 '11 at 20:46
  • If using something like `clang` which has C99 Support, does this still work? Or not? – Richard J. Ross III Sep 19 '12 at 16:12
57

I think that the most important part of this question is, how C++ knows what the size of an array is (at least I wanted to know it when I found this question).

C++ knows the size of an array, because it's a part of the array's definition - it's the type of the variable. A compiler has to know the type.

Since C++11 std::extent can be used to obtain the size of an array:

int size1{ std::extent< char[5] >::value };
std::cout << "Array size: " << size1 << std::endl;

Of course, this doesn't make much sense, because you have to explicitly provide the size in the first line, which you then obtain in the second line. But you can also use decltype and then it gets more interesting:

char v[] { 'A', 'B', 'C', 'D' };
int size2{ std::extent< decltype(v) >::value };
std::cout << "Array size: " << size2 << std::endl;
honk
  • 9,137
  • 11
  • 75
  • 83
psur
  • 4,400
  • 26
  • 36
  • 8
    This is indeed what I was originally asking about. :) – Paul Manta Oct 11 '14 at 10:13
  • The array size, it is exactly the answer I was looking for. Since when you convert your old array for loops into foreach loops, you wonder how can it work now on a primitive array without any added information about the array size... Thank you so much for your great answer. – SomethingSomething Mar 30 '22 at 09:43
20

According to the latest C++ Working Draft (n3376) the ranged for statement is equivalent to the following:

{
    auto && __range = range-init;
    for (auto __begin = begin-expr,
              __end = end-expr;
            __begin != __end;
            ++__begin) {
        for-range-declaration = *__begin;
        statement
    }
}

So it knows how to stop the same way a regular for loop using iterators does.

I think you may be looking for something like the following to provide a way to use the above syntax with arrays which consist of only a pointer and size (dynamic arrays):

template <typename T>
class Range
{
public:
    Range(T* collection, size_t size) :
        mCollection(collection), mSize(size)
    {
    }

    T* begin() { return &mCollection[0]; }
    T* end () { return &mCollection[mSize]; }

private:
    T* mCollection;
    size_t mSize;
};

This class template can then be used to create a range, over which you can iterate using the new ranged for syntax. I am using this to run through all animation objects in a scene which is imported using a library that only returns a pointer to an array and a size as separate values.

for ( auto pAnimation : Range<aiAnimation*>(pScene->mAnimations, pScene->mNumAnimations) )
{
    // Do something with each pAnimation instance here
}

This syntax is, in my opinion, much clearer than what you would get using std::for_each or a plain for loop.

honk
  • 9,137
  • 11
  • 75
  • 83
Grant
  • 219
  • 2
  • 4
3

How does the range-based for work for plain arrays?

Is that to read as, "Tell me what a ranged-for does (with arrays)?"

I'll answer assuming that - Take the following example using nested arrays:

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};

for (auto &pl : ia)

Text version:

ia is an array of arrays ("nested array"), containing [3] arrays, with each containing [4] values. The above example loops through ia by it's primary 'range' ([3]), and therefore loops [3] times. Each loop produces one of ia's [3] primary values starting from the first and ending with the last - An array containing [4] values.

  • First loop: pl equals {1,2,3,4} - An array
  • Second loop: pl equals {5,6,7,8} - An array
  • Third loop: pl equals {9,10,11,12} - An array

Before we explain the process, here are some friendly reminders about arrays:

  • Arrays are interpreted as pointers to their first value - Using an array without any iteration returns the address of the first value
  • pl must be a reference because we cannot copy arrays
  • With arrays, when you add a number to the array object itself, it advances forward that many times and 'points' to the equivalent entry - If n is the number in question, then ia[n] is the same as *(ia+n) (We're dereferencing the address that's n entries forward), and ia+n is the same as &ia[n] (We're getting the address of the that entry in the array).

Here's what's going on:

  • On each loop, pl is set as a reference to ia[n], with n equaling the current loop count starting from 0. So, pl is ia[0] on the first round, on the second it's ia[1], and so on. It retrieves the value via iteration.
  • The loop goes on so long as ia+n is less than end(ia).

...And that's about it.

It's really just a simplified way to write this:

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
for (int n = 0; n != 3; ++n)
  auto &pl = ia[n];

If your array isn't nested, then this process becomes a bit simpler in that a reference is not needed, because the iterated value isn't an array but rather a 'normal' value:

 int ib[3] = {1,2,3};

 // short
 for (auto pl : ib)
   cout << pl;

 // long
 for (int n = 0; n != 3; ++n)
   cout << ib[n];

Some additional information

What if we didn't want to use the auto keyword when creating pl? What would that look like?

In the following example, pl refers to an array of four integers. On each loop pl is given the value ia[n]:

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
for (int (&pl)[4] : ia)

And... That's how it works, with additional information to brush away any confusion. It's just a 'shorthand' for loop that automatically counts for you, but lacks a way to retrieve the current loop without doing it manually.

Super Cat
  • 1,547
  • 1
  • 18
  • 23
  • @Andy 9 out of 10 times the *title* is what matches in Google/whatever searches - The title asks *how do these work?*, not *when does it know when to stop?*. Even so, the underlying question implied *is* covered in this answer to some extent, and goes on to answer for anyone else looking for the *other* answer. Syntax questions such as these should have titles phrased so that an answer can be written using that alone because that's all the information the searcher needs to find the question. You're certainly not wrong - The question isn't titled as it should be. – Super Cat Sep 10 '15 at 19:56
3

It knows when to stop because it knows the bounds of static arrays.

I'm not sure what do you mean by "dynamic arrays", in any case, if not iterating over static arrays, informally, the compiler looks up the names begin and end in the scope of the class of the object you iterate over, or looks up for begin(range) and end(range) using argument-dependent lookup and uses them as iterators.

For more information, in the C++11 standard (or public draft thereof), "6.5.4 The range-based for statement", pg.145

chill
  • 16,470
  • 2
  • 40
  • 44
  • 5
    A "dynamic array" would be one created with `new[]`. In that case, you've only got a pointer with no indication of the size, so there's no way for the range-based `for` to work with it. – Mike Seymour Oct 29 '11 at 15:50
  • My answer includes a dynamic array whose size (4) is known at compile time, but I don't know whether that interpretation of "dynamic array" is what the questioner intended. – Johannes Schaub - litb Oct 29 '11 at 16:09
1

Some sample code to demonstrate the difference between arrays on Stack vs arrays on Heap


/**
 * Question: Can we use range based for built-in arrays
 * Answer: Maybe
 * 1) Yes, when array is on the Stack
 * 2) No, when array is the Heap
 * 3) Yes, When the array is on the Stack,
 *    but the array elements are on the HEAP
 */
void testStackHeapArrays() {
  int Size = 5;
  Square StackSquares[Size];  // 5 Square's on Stack
  int StackInts[Size];        // 5 int's on Stack
  // auto is Square, passed as constant reference
  for (const auto &Sq : StackSquares)
    cout << "StackSquare has length " << Sq.getLength() << endl;
  // auto is int, passed as constant reference
  // the int values are whatever is in memory!!!
  for (const auto &I : StackInts)
    cout << "StackInts value is " << I << endl;

  // Better version would be: auto HeapSquares = new Square[Size];
  Square *HeapSquares = new Square[Size];   // 5 Square's on Heap
  int *HeapInts = new int[Size];            // 5 int's on Heap

  // does not compile,
  // *HeapSquares is a pointer to the start of a memory location,
  // compiler cannot know how many Square's it has
  // for (auto &Sq : HeapSquares)
  //    cout << "HeapSquare has length " << Sq.getLength() << endl;

  // does not compile, same reason as above
  // for (const auto &I : HeapInts)
  //  cout << "HeapInts value is " << I << endl;

  // Create 3 Square objects on the Heap
  // Create an array of size-3 on the Stack with Square pointers
  // size of array is known to compiler
  Square *HeapSquares2[]{new Square(23), new Square(57), new Square(99)};
  // auto is Square*, passed as constant reference
  for (const auto &Sq : HeapSquares2)
    cout << "HeapSquare2 has length " << Sq->getLength() << endl;

  // Create 3 int objects on the Heap
  // Create an array of size-3 on the Stack with int pointers
  // size of array is known to compiler
  int *HeapInts2[]{new int(23), new int(57), new int(99)};
  // auto is int*, passed as constant reference
  for (const auto &I : HeapInts2)
    cout << "HeapInts2 has value " << *I << endl;

  delete[] HeapSquares;
  delete[] HeapInts;
  for (const auto &Sq : HeapSquares2) delete Sq;
  for (const auto &I : HeapInts2) delete I;
  // cannot delete HeapSquares2 or HeapInts2 since those arrays are on Stack
}
Yip Cubed
  • 56
  • 3