AlwaysLearning
AlwaysLearning

Reputation: 8011

Using reference to pointer as a sequence in C++11 range for loop

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?

Upvotes: 5

Views: 429

Answers (3)

user3920237
user3920237

Reputation:

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.

Upvotes: 0

Columbo
Columbo

Reputation: 60979

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.

Upvotes: 5

Anthony Williams
Anthony Williams

Reputation: 68581

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.

Upvotes: 4

Related Questions