Reputation: 343
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
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:
std::set
, modify it, then insert again. (It is not a good idea if you want to modify every element) Upvotes: 5
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 }
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 }
Upvotes: 22
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
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
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