Reputation: 497
I was stuck in a piece of code in C++ Primer and had thought about it more than 1 hour. The code is
for (auto row : ia)//should use &row here
for (auto col : row)
The explanation for it is
We do so in order to avoid the normal array to pointer conversion. Because row is not a reference, when the compiler initializes row it will convert each array element (like any other object of array type) to a pointer to that array’s first element. As a result, in this loop the type of row is int*. The inner for loop is illegal. Despite our intentions, that loop attempts to iterate over an int*.
I know it has something to do with iterations each time doing for(auto col:row)
. What I can't understand about
We do so in order to avoid the normal array to pointer conversion"
is what's the form of "ia" we passed in for the outer loop? Should it be a pointer that points to the address of its first element rather than a "concrete" array? Then what's wrong for the inner loop? I thought it should be the same mechanism with the outer loop.. I can't understand the posts on Q&A. Someone please enlighten me please...What's wrong with my understanding...A good link is also welcomed! Many thanks in advance!
The declaration for ia is
constexpr size_t rowCnt = 3, colCnt = 4;
int ia[rowCnt][colCnt]; // 12 uninitialized elements
// for each row
for (size_t i = 0; i != rowCnt; ++i) {
// for each column within the row
for (size_t j = 0; j != colCnt; ++j) {
// assign the element's positional index as its value
ia[i][j] = i * colCnt + j;
}
}
Upvotes: 2
Views: 387
Reputation: 9335
Range based for
loop works for either with array type
or user defined type
having member functions begin
and end
.
By array-to-pointer decay rule
when you pass array
to a function which takes a pointer
, then the array decay to pointer
But when you use a template by **reference**
then template type is deduced as a array type
:
template<typename T>
void foo(T&);
int main() {
int ia[2][2] = {{0,1}, {2,3}};
auto row_val = ia[0]; //int*
auto& row_ref = ia[0]; //int [2]
foo(row_val); //void foo<int*>(int*&)
foo(row_ref); //void foo<int [2]>(int (&) [2])
}
Here you can see that (from the error it creates!!) that type deduced for row_val
and row_ref
are not same
row_val ==> int*
row_ref ==> int [2]
You have to use for(auto& row: ia)
as row
will now array type
not a pointer
. hence you can use the inner loop for (auto col : row)
.
Upvotes: 2
Reputation: 965
In general, a range-based for loop declared as:
for ( decl : coll ) {
statement }
is equivalent to the following, if coll provides begin() and end() members:
for (auto _pos=coll.begin(), _end=coll.end(); _pos!=_end; ++_pos )
{
decl = *_pos;
statement
}
or, if that doesn’t match, to the following by using a global begin() and end() taking coll as argument:
for (auto _pos=begin(coll), _end=end(coll); _pos!=_end; ++_pos )
{
decl = *_pos;
statement
}
Now look at the line decl = *_pos;
, here each time compiler will initialize dec , it will convert each array element (like any other object of array type) to a pointer to that array’s first element .
In your case , type of raw will comes out to be int* on which next for loop can't work and hence it becomes illegal .
Upvotes: 5
Reputation: 76240
Range-based for loops are based on the begin
and end
functions which don't work on T*
but work on T[N]
.
The implementation for:
for ( range_declaration : range_expression ) loop_statement
is along the lines of:
{
auto && __range = range_expression ;
for (auto __begin = begin_expr,
__end = end_expr;
__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
Since in the outer for loop the array would decay to a pointer with auto row
, the inner for loop would become illegal.
Upvotes: 4