c++referencesetconst-iteratorrange-based-loop

Why is iterating over the set and modifying element not allowed here


In the following code, we cannot iterate over set s1 using non-const reference. Why?

#include <set>

struct st {
  unsigned int f;
  std::set<int> s2;
};

struct comp {
  bool operator()(const st &lhs, const st &rhs) const {
    return lhs.f < rhs.f;
  }
};

int main() {
  std::set<st, comp> s1;
  st x;

  for (st &m : s1) // compiler error here. requires const st&
    m.s2.insert(begin(x.s2), end(x.s2));

  return 0;
}

thanks to @super I found that it is logical for compiler to not let us use non-const reference to std::set members. Now how and where is this policy declared?


Solution

  • From the C++17 Standard (26.2.6 Associative containers)

    5 For set and multiset the value type is the same as the key type. For map and multimap it is equal to pair<const Key, T>.

    6 iterator of an associative container is of the bidirectional iterator category. For associative containers where the value type is the same as the key type, both iterator and const_iterator are constant iterators.

    Thus in this range-based for loop

    for (st &m : s1)
    

    you may not use a non-constant reference because constant iterators return constant references to objects pointed to by the iterators.

    Actually a range-based for loop is defined the following way (the C++17 Standard, 9.5.4 The range-based for statement)

    1 The range-based for statement

    for ( for-range-declaration : for-range-initializer ) statement
    

    is equivalent to

    {
        auto &&__range = for-range-initializer ;
        auto __begin = begin-expr ;
        auto __end = end-expr ;
        for ( ; __begin != __end; ++__begin ) {
            for-range-declaration = *__begin;
            statement
        }
    }
    

    So relative to your range-based for loop in this line

    for-range-declaration = *__begin;
    

    there is an attempt to assign a constant reference returned by the constant iterator to a non-constant reference.

    Pay attention to that you created an empty set.

    std::set<st, comp> s1;
    

    So the loop in any case does not make sense.

    Consider using a map instead of the set.