I\'m taking a self-study course for C++, learning how the Standard Library works, and I want to understand how this code that uses for_each
works, particularly
You can't simply change the key of an element in a key-value store kind of a data structure. (In case of a set, it's only key, no value.)
If you would, the element's position in the underlying container might need to be adjusted, based on the recomputed hash value in case of an unordered hash table, or a different sorting position in a binary search tree / ordered list.
Therefore the API of the std::set
requires you to erase and reinsert the item in this case.
Which the answers to existing questions in this direction already state:
The problem is that you are not allowed to modify elements in a std::set
. If it were possible, then how would it handle something like this:
std::set<int> my_set { 1, 2, 3 };
int& foo = *(my_set.begin());
foo = 2;
Now there is two elements with value 2. That doesn't make sense in a set
due to
std::set is an associative container that contains a sorted set of unique objects of type Key.
(emphasis mine)
http://en.cppreference.com/w/cpp/container/set
The doc for std::set
(http://www.cplusplus.com/reference/set/set/) defines the std::set::iterator
returned by non-const version of begin
and end
as :
a bidirectional iterator to
const value_type
So, even non-const std::set::iterator
returns a const
object.
Because a std::set
manages the order of it's elements, it prohibits the user to change it's elements through it's iterators. Which means it's begin() and end() methods return a const_iterator
. You're only allowed to read the element pointed to by that iterator, not modify it (it's const) which is what doubler()
is trying to do.
A solution would be to just use std::vector
and std::sort
to order it yourself:
#include <iostream>
#include <algorithm>
#include <vector>
class A {
int a;
public:
A(int a) : a(a) {}
int getA() const { return a; }
void setA(int a) { this->a = a; }
bool operator<(const A & b) const { return a<b.a; }
};
struct myprinter {
void operator()(const A & a) { cout << a.getA() << ", "; }
};
struct doubler {
void operator()(A& a) { a.setA(a.getA()*2); } // by reference!
};
int main() {
int mynumbers[] = {8, 9, 7, 6, 4, 1};
std::vector<A> s1(mynumbers, mynumbers+6);
std::sort(s1.begin(), s1.end());
std::for_each(s1.begin(), s1.end(), doubler());
std::for_each(s1.begin(), s1.end(), myprinter());
return 0;
}
This is a piece of cake with a lambda expression.
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main() {
int mynumbers[] = {8, 9, 7, 6, 4, 1};
vector<int> s1(mynumbers, mynumbers+6);
sort(s1.begin(), s1.end());
for_each(s1.begin(), s1.end(), [](int &x) { x*=2; });
for_each(s1.begin(), s1.end(), [](int x) { cout << x << ", "; });
return 0;
}
OUTPUT:
2, 8, 12, 14, 16, 18,
There is in fact nothing wrong with modifying elements in the functor of a for_each:
If InputIt is a mutable iterator,
f
may modify the elements of the range through the dereferenced iterator
So next we should look at the definition of set, note that since c++11 clarified this, both it's iterator
and const_iterator
types are: "Constant Bidirectional Iterators".
So it is undefined behavior to modify a set
with a for_each
.