Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Range based for implicitly adds `const` qualifier?

Let's look at the following simple range based for loop:

  int a = 5, b = 6;
  for (auto & i : {a, b})
  {
      std::cout << i << std::endl; // Works as expected.
      i = 3;                       // Error!
  }

gcc complains about assignment of read-only reference 'i', implying that the range based for loop used with an initializer list implicitly adds a const qualifier to the reference, totally unprovoked.

  1. Why does this happen?
  2. Is there a work around to allow modifying variables in a range based for loop?
like image 298
nbubis Avatar asked Jul 16 '15 13:07

nbubis


1 Answers

In

int a = 5, b = 6;
for (auto & i : {a, b})

You have that {a, b} is an std::initialiser_list of two elements, a and b, in which the values of a and b are copied. Now, std::initializer_list only provides constant iterators to its elements, because initializer_lists are immutable, so you cannot bind the value to non-const lvalue references.

One option would be to pass pointers instead, which would make the pointers themselves constant, but not the value they point to:

for (auto& i : {&a, &b}) 
    *i = 0;

Live demo

Another alternative would be to use an std::reference_wrapper, but that would still required a call to .get() or an explicit cast static_cast<int&> in this case:

for (auto& i : {std::ref(a), std::ref(b)}) 
    i.get() = 0;

Live demo

Considering that std::reference_wrapper has an implicit conversion operator to T&, I wouldn't be surprised if in some other context you would be able to automatically trigger an implicit conversion (as opposed to calling .get()).


Also note that {a, b} is not a range of numbers from a to b, it's really just those two numbers. So with int a = 0, b = 10 you would not have [0, 10] but the list of 0 followed by 10.

If you want to have "proper" ranges, I recommend you take a look at Boost.Range.

like image 51
Shoe Avatar answered Oct 06 '22 01:10

Shoe