Imtiaz Mehedi
Imtiaz Mehedi

Reputation: 343

Set operation in c++(update existing value)

Here is my code:

 while (it!=s.end())  //here 's' is a set of stl and 'it' is iterator of set
    {   
        *it=*it-sub;    //'sub' is an int value
        it++;
    }

I can't update the value of set by iterator. I want to subtract an integer value 'sub' from all the element of set.

Can anyone help me where the actual problem is and what would be the actual solution?

Here is the error message:

error: assignment of read-only location ‘it.std::_Rb_tree_const_iterator<int>::operator*()’
   28 |             *it=*it-sub;
      |             ~~~^~~~~~~~

Upvotes: 20

Views: 3265

Answers (5)

x00
x00

Reputation: 13823

You can't mutate elements of std::set by design. See

https://en.cppreference.com/w/cpp/container/set/begin

Because both iterator and const_iterator are constant iterators (and may in fact be the same type), it is not possible to mutate the elements of the container through an iterator returned by any of these member functions.

That is because set is sorted. If you mutate element in a sorted collection the collection must be sorted again, which is of course possible, but not the C++ way.

Your options are:

  1. Use another type of collection (unsorted).
  2. Create a new set and fill it with modified elements.
  3. Remove an element from std::set, modify it, then insert again. (It is not a good idea if you want to modify every element)

Upvotes: 5

Scheff&#39;s Cat
Scheff&#39;s Cat

Reputation: 20141

Key values of elements in a std::set are const for a good reason. Modifying them may destroy the order which is essential for a std::set.

Hence, the solution is to erase the iterator and insert a new one with key *it - sub. Please, note that std::set::erase() returns a new iterator which has to be used in your case to keep the while loop working properly.

#include<iostream>
#include<set>

template <typename T>
std::ostream& operator<<(std::ostream &out, const std::set<T> &values)
{
  const char *sep = "{ ";
  for (const T &value : values) { out << sep << value; sep = ", "; }
  return out << " }";
}

int main()
{
  std::set<int> test{ 11, 12, 13, 14, 15 };
  std::cout << "test: " << test << '\n';
  const int sub = 10;
  std::set<int>::iterator iter = test.begin();
  while (iter != test.end()) {
    const int value = *iter;
    iter = test.erase(iter);
    test.insert(value - sub);
  }
  std::cout << "test: " << test << '\n';
}

Output:

test: { 11, 12, 13, 14, 15 }
test: { 1, 2, 3, 4, 5 }

Live Demo on coliru


Changes on the std::set while iterating over it are not a problem in general but can cause subtle issues.

The most important fact is that all used iterators have to be kept intact or may not be used anymore. (That's why the current iterator of the erase element is assigned with the return value of std::set::erase() which is either an intact iterator or the end of set.)

Of course, elements can be inserted as well behind the current iterator. While this is not a problem concerning the std::set it may break the loop of my above example.

To demonstrate it, I changed the above sample a bit. Please, note that I added an additional counter to grant the termination of loop:

#include<iostream>
#include<set>

template <typename T>
std::ostream& operator<<(std::ostream &out, const std::set<T> &values)
{
  const char *sep = "{ ";
  for (const T &value : values) { out << sep << value; sep = ", "; }
  return out << " }";
}

int main()
{
  std::set<int> test{ 11, 12, 13, 14, 15 };
  std::cout << "test: " << test << '\n';
  const int add = 10;
  std::set<int>::iterator iter = test.begin();
  int n = 7;
  while (iter != test.end()) {
    if (n-- > 0) {
      const int value = *iter;
      iter = test.erase(iter);
      test.insert(value + add);
    } else ++iter;
  }
  std::cout << "test: " << test << '\n';
}

Output:

test: { 11, 12, 13, 14, 15 }
test: { 23, 24, 25, 31, 32 }

Live Demo on coliru

Upvotes: 22

srt1104
srt1104

Reputation: 959

A std::set is typically implemented as a self-balancing binary tree in STL. *it is the value of the element which is used to order the tree. If it was possible to modify it, the order would become invalid hence it isn't possible to do that.

If you want to update an element, then you have to find that element in the set, remove it and insert the updated value of the element. But since you have to update values of all elements, then you have to erase and insert all elements one by one.

It is possible to do it in one for loop provided sub > 0. S.erase(pos) removes the iterator at position pos and returns the following position. If sub > 0, the updated value which you'll insert will come before the value at the new iterator in the tree but if sub <= 0, then the updated value will come after the value at the new iterator in the tree and hence you'll end up in an infinite loop.

for (auto itr = S.begin(); itr != S.end(); )
{
    int val = *itr;
    itr = S.erase(itr);
    S.insert(val - sub);
}

Upvotes: 4

acraig5075
acraig5075

Reputation: 10756

Simple to just replace it with another set

std::set<int> copy;

for (auto i : s)
    copy.insert(i - sub);

s.swap(copy);

Upvotes: 6

P0W
P0W

Reputation: 47784

The error pretty much explains the problem

Members of std::set container are const. Changing them make their respective ordering invalid.

For changing elements in std::set, you will have to erase the item and re-insert it after it is changed.

Alternatively, you could use std::map to overcome this scenario.

Upvotes: 3

Related Questions