5

The only difference between the following two snippets of code is the usage of reference. I understand why the first snippet does not compile and am seeking help in understanding why the second one compiles.

The first snippet:

int a[2][3] = {0,1,2,3,4,5};
for (auto row : a)
  for (auto column : row)
    cout << column << endl;

The above code does not compile because the type of 'row' is pointer to int, which is not a sequence.

The second snippet:

int a[2][3] = {0,1,2,3,4,5};
for (auto &row : a)
  for (auto column : row)
    cout << column << endl;

This code compiles. If I understand correctly how auto works, 'row' is a reference to pointer to int. But why can this reference be viewed as a sequence more than a regular pointer?

AlwaysLearning
  • 7,257
  • 4
  • 33
  • 68
  • 4
    `row` is not a reference to pointer to int. It's a reference to an array of 3 ints - a `int (&)[3]`. – T.C. Dec 15 '14 at 10:43
  • Thank you. But I am still confused. I understand that in C++ an array in an expression is always viewed as a pointer to the first element (that is why the first example did not compile). So how come 'row' is not converted to a pointer in the second example? – AlwaysLearning Dec 15 '14 at 10:50
  • 2
    "an array in an expression is always viewed as a pointer to the first element" - no, an array *decays* to a pointer if used in a context that requires a pointer. The top answer in [this question](http://stackoverflow.com/questions/4810664/how-do-i-use-arrays-in-c) has a fuller explanation. – T.C. Dec 15 '14 at 10:55

3 Answers3

5

Deduction of the type is done via template argument deduction.

template <typename U>
void foo(U&); // Equivalent to auto&

foo(row);

This will always deduce U to be the exact type of row (if it's an lvalue as in this case), which gives us the array type we desired.
Only for non-reference parameters is the array-to-pointer decay performed.

Columbo
  • 60,038
  • 8
  • 155
  • 203
4

Each element of the outer iteration is an array. In the first case auto takes the element by value so array to pointer decay happens and you can't then loop over a single pointer.

In the second case you get a reference to the array, which you can of course iterate over.

Anthony Williams
  • 66,628
  • 14
  • 133
  • 155
0

std::begin and std::end don't have overloads for pointer types. Ranged-based for loop are defined to use these functions. If you want to prevent the array-to-pointer conversion from happening, you can bind a reference to the array.

§8.5.3

5 A reference to type "cv1 T1" is initialized by an expression of type "cv2 T2" as follows:

— If the reference is an lvalue reference and the initializer expression

— is an lvalue (but is not a bit-field), and "cv1 T1" is reference-compatible with "cv2 T2," or

— has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an lvalue of type "cv3 T3," where "cv1 T1" is reference-compatible with "cv3 T3"106 (this conversion is selected by enumerating the applicable conversion functions (13.3.1.6) and choosing the best one through overload resolution (13.3)),

then the reference is bound to the initializer expression lvalue in the first case and to the lvalue result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object). [ Note: The usual lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are not needed, and therefore are suppressed, when such direct bindings to lvalues are done. — end note ]

Since the second bullet point doesn't apply, then the reference binds directly to the array and no conversion happens.