65

From C++ Primer 5th Edition by Lippman, page 182, consider:

int ia[3][4];
for (auto row : ia)
        for (auto col : row)

The first for iterates through ia, whose elements are arrays of size 4. 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*.

I am not really sure that I understand how this auto works, but if I can assume it automatically gives a type to a row based on ia array members type, but I don't understand why this kind of for, where row is not a reference, is not valid. Why is this going to happen? "pointer to that array’s first element", because of what?

Gaurang Tandon
  • 6,504
  • 11
  • 47
  • 84

2 Answers2

73

The problem is that row is an int * and not a int[4] as one would expect because arrays decay to pointers and there is no automatic way to know how many elements a pointer points to.

To get around that problem std::array has been added where everything works as expected:

#include <array>

int main() {
    std::array<std::array<int, 4>, 3> ia;
    for (auto &row : ia){
        for (auto &col : row){
            col = 0;
        }
    }
}

Note the & before row and col which indicate that you want a reference and not a copy of the rows and columns, otherwise setting col to 0 would have no effect on ia.

nwp
  • 9,623
  • 5
  • 38
  • 68
  • 50
    Note that the equivalent of `int[3][4]` is _not_ `std::array, 4>`, but `std::array, 3>`. This is a common mistake and worth pointing out, doubly so since you made it as well. ;-] – ildjarn Jan 25 '16 at 09:32
  • @Deduplicator what temporary copies ? – Quentin Jan 25 '16 at 14:15
  • @Quentin: Ah, misread. He change the type *and* went for references. So no extra-copies. Still, the type-change is cumbersome, and is certainly API and perhaps also ABI-breaking, if calling other functions with that as an argument. – Deduplicator Jan 25 '16 at 14:20
  • As an aside, traversing loops row major vs column major can have a significant performance implication for large-ish array sizes, so it is important to know what your rows vs columns really are. – Eric J. Jan 26 '16 at 20:28
  • Note, that you are now changing the values inside `ia`. The original code did not use references, implying that you work on copies and thus not propagates changes of `col` to `ia`. – Raoul Steffen Jan 27 '16 at 08:24
38

To prevent the decay of the int[] to int* you can use &&

int main() {
    int ia[3][4];
    for (auto && row : ia)
        for (auto && col : row)
            ;
}
Thomas
  • 4,980
  • 2
  • 15
  • 30
  • 9
    Or just plain reference would be enough. – Revolver_Ocelot Jan 24 '16 at 20:42
  • 1
    Actually, you should use rvalue references (`&&`) only in move constructors, move assignment operators or when plain references don't work and you exactly know what you are doing. – Michael Karcher Jan 24 '16 at 21:23
  • 6
    I used `&&` here specifically because that is what is being proposed in [N3853](http://open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3853.htm) to support the even more reduced `( elem : collection )` syntax – Thomas Jan 24 '16 at 21:34
  • 4
    @MichaelKarcher But that's not a (plain) rvalue reference - it's a [forwarding reference](http://en.cppreference.com/w/cpp/utility/forward). – Angew is no longer proud of SO Jan 25 '16 at 11:15