简体   繁体   中英

Why referencing in a nested ranged-based for-loops

I'm a newbie in C++. Why can't we iterate through int* , what is the use of & here, and how does this nested range-based for perform, deeply?

int arr[10][3];

for (auto &i : arr)
{
    for (auto j : i)
    {
        //sth
    }
}

First of all, we need to know the exact data type of int arr[10][3]; . It is an array of 10 arrays of 3 int .

A loop typically iterates over one dimension of a multidimensional container, eg

for(int i = 0; i < 10; ++i)
{
    for(int j = 0; j < 3; ++j)
    {
        arr[i][j] = 0;
    }
}

The first loop iterates over the array of 10 X , the second loop then iterates over X , which here is an array of 3 int .

The next step is to explicitly use this X in code:

for(int i = 0; i < 10; ++i)
{
    int (&x)[3] = arr[i];  // you won't see this syntax often

    for(int j = 0; j < 3; ++j)
    {
        int &elem = x[j];
        elem = 0;
    }
}

The line int (&x)[3] declares a reference to an array of 3 int , which is the result of accessing the first level of the multidimensional array arr .

We can also write this example using iterators:

for(int (*px)[3] = arr; px != arr+10; ++px)
{
    // `px` is a _pointer to an array of 3 `int`_
    // `*px` then is an _array of 3 `int`_

    for(int *pelem = *px; pelem != (*px)+3; ++pelem)
    {
        *pelem = 0;
    }
}

Note I'm using a feature here that converts an array to a pointer to its first element. This is called decaying : an array is/can be decayed to a pointer (to the first element of that array), eg

int my_arr[3];
int *p = my_arr;  // `p` now points to the first element of `my_arr`
     p = &my_arr[0]; // equivalent

For multidimensional arrays, this becomes

int arr[10][3];
int (*p)[3];    // a pointer to an _array of 3 `int`_
p = arr;        // `p` now points to the first element of `arr`, i.e.
                // the first _array of 3 `int`_

Last but not least, for multidimensional arrays, it is also possible to write:

for(int *pelem = arr[0]; pelem != arr[0]+10*3; ++pelem)
{
    *pelem = 0;
}

But this is only possible for multidimensional arrays, as they're laid out contiguously in memory, and the memory layout of multidimensional arrays is specified.

This is not possible for a container like vector<vector<int>> , even though

vector<int> v = {1,2,3,4,5};
for(int* i = &v[0]; i != &v[0] + 5; ++i)
{
    *i = 0;
}

is well-formed and has no undefined behaviour.


The same logic now applies to range-based for loops:

for(int (&x)[3] : arr)
{
    for(int &elem : x)
    {
        elem = 0;
    }
}

The whole point of having range-based for loops is to get rid of explicit iterators. int* is such an iterator, so there's no point in having a range-based for loop that iterates over int* IMO.


how does this nested range-based for perform, deeply?

The C++ language Standard defines the range-based for statement in [stmt.ranged] as follows (note I've simplified it a bit):

for ( for-range-declaration : expression ) statement

is resolved to:

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

Where for-range-declaration and statement are essentially copy-pasted from the unresolved range-based for loop. The rest ( begin-expr , end-expr ) has some intricacies, here's a simplified version:

{
    using std::begin;
    using std::end;

    for ( auto __begin = begin(/*expression*/),
               __end = end(/*expression*/);
          __begin != __end;
          ++__begin )
    {
        /*for-range-declaration*/ = *__begin;
        /*statement*/
    }
}

My example of a range-based for loop is resolved from

for(int (&x)[3] : arr)
{
    /*statements*/
}

to

{
    using std::begin;
    using std::end;

    for ( auto __begin = begin(arr),
               __end = end(arr);
          __begin != __end;
          ++__begin )
    {
        int (&x)[3] = *__begin;
        /*statements*/
    }
}

Or, by resolving the begin / end calls:

{
    for ( int (*__begin)[3] = arr,
               __end = arr + 10;
          __begin != __end;
          ++__begin )
    {
        int (&x)[3] = *__begin;           // (A)
        /*statements*/
    }
}

The line marked with an (A) also shows why the & in an example for (int x[3] : arr) is necessary:

int arr[10][3];
int (&x)[3] = arr[0];   // well-formed
int   x [3] = arr[0];   // ill-formed for arrays

It is not allowed to directly assign a raw/C-style array, as you may know from examples like

int my_arr[10];
int my_sec_arr[10] = my_arr;  // not legal, ill-formed

This is why you have to use a reference.

With other containers like the Standard Library's std::array , it is possible to avoid the reference:

std::array<int, 10> my_arr;
std::array<int, 10> my_sec_arr = my_arr;  // well-formed

But assigning means a copy, so the whole array had to be copied; whereas a reference here doesn't require copying.


As Yakk pointed out in the comments , this isn't quite the reason why the & is necessary in your example for (auto &i : arr) , as auto &i = arr[0]; is resolved to int (*i)[3] = arr[0]; . But as you can see, auto decays the array into a pointer, so your second iteration fails:

for(auto i : arr)
{
    // type of `i` now is _pointer to an array of 3 `int`_
    for(auto j : i) // can't iterate over a pointer: what are the boundaries?
    {
        /* ... */
    }
}

To be a bit more precise: you can iterate over an array, as the compiler knows how many elements there are in the array; it's part of the type, eg array of 3 int , and the type is known to the compiler.

For pointers, the compiler doesn't know if the pointer refers to a single element or to an array of elements, and in the latter case it doesn't know how large that array is. The type in any case is just, eg pointer to int :

int my_arr[10];
int my_int;

int *p;
p = my_arr;
p = &my_int;
p = new int[25];

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM