Search code examples
c++c++11constantsinitializer-list

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?

Solution

  • 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.