1

I am having trouble understanding why I can't set values in a 2D matrix (a vector of vectors). If I use C-style loops or iterators it works, but it doesn't work if I use c++ range based loops. Can someone please explain why?

Thanks.

struct my_str {
   int value;

   my_str(int v) : value(v) {}
};

int main(int argc, char *argv[])
{
   // Initialise with some dummy data (creates a 3 x 2 matrix)
   vector<vector<my_str>> array2D;
   for (int i=0; i<3; ++i) {
      vector<my_str> tmp;
      for (int j=0; j<2; ++j)
         tmp.push_back( my_str(-999) );
      array2D.push_back(tmp);
   }

   // Now try to replace the values
   int ctr=0;

   // Fails
   for (auto array1D: array2D)
      for (auto it: array1D)
         it.value = (float)++ctr;

   // Fails
   for (auto array1D: array2D)
      for (std::vector<my_str>::iterator it=array1D.begin(); it != array1D.end() ;++it)
         (*it).value = (float)++ctr;

   // Fails
   for (int i=0; i<array2D.size(); i++)
      for (auto array1D: array2D[i])
         array1D.value = (float)++ctr;

   // Works
   for (std::vector<std::vector<my_str>>::iterator array1D=array2D.begin(); array1D != array2D.end() ;++array1D)
      for (std::vector<my_str>::iterator it=array1D->begin(); it != array1D->end() ;++it)
         (*it).value = (float)++ctr;

   // Works
   for (int i=0; i<array2D.size(); i++)
      for (std::vector<my_str>::iterator it=(array2D[i]).begin(); it != (array2D[i]).end() ;++it)
         it->value = (float)++ctr;

   // Works
   for (int i=0; i<array2D.size(); i++)
      for (int j=0; j<(array2D[i]).size(); j++)
         array2D[i][j].value = (float)++ctr;
John
  • 451
  • 7
  • 17

2 Answers2

4

Use references, otherwise you are getting copies.

for (auto & array1D: array2D)
          ^
    for (auto & it: array1D)
              ^

auto doesn't keep const or reference in type deduction:

int i{0};
const int ci{0};
int & ri = i;
int const & cri = i;
auto a = i;   // int deduced
auto b = ci;  // int deduced
auto c = ri;  // int deduced
auto d = cri; // int deduced

and in all case, a, b, c and d are copies of i and ci. Any modification of these values won't affect the value of i or ci. Whereas

auto & e = i; // int & deduced
e = 1; // i is modified

Same rule applies here in your for-range loops.

O'Neil
  • 3,790
  • 4
  • 16
  • 30
1

By default auto deduces the value type of an expression. In a range based for loop, this is bad news for class types since each element will be a copy of the original element in the container.

Given

for (auto array1D : array2D)

The compiler fills in the blanks as such -

for (vector<my_str> array1D : array2D)

Why is a copy used? According to the standard, array1D behind the scenes would look something like this.

std::vector<my_str>::iterator it = array2D.begin();

// this for loop advances the iterator
for (...)
{
    // this is a copy of the element!
    vector<my_str> array1D = *it;
}

This "Fails" to work, because when you assign a value to the elements in the second for loop, you will be assigning to a copied vector. This won't modify the original element within array2D.

To avoid using copies, let the compiler know that you want array1D to be a reference to an element within array2D.

for (auto &array1D : array2D)
Austin Brunkhorst
  • 20,704
  • 6
  • 47
  • 61