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 regarding mutating objects (as opposed to native data types). I realize that you shouldn't use for_each
this way, but this is for the purpose of learning.
I had thought this code would mutate all the elements in the set, but it doesn't.
My question is: 1. Why doesn't this code mutate the set?
2. How can the code be altered so that it will modify the set? To clarify: is there a way to keep the for_each
and have it manipulate the set, or is this not possible and some other method (such as transform
) has to be used?
#include <iostream>
#include <algorithm>
#include <set>
using namespace std;
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); }
};
int main() {
int mynumbers[] = {8, 9, 7, 6, 4, 1};
set<A> s1(mynumbers, mynumbers+6);
for_each(s1.begin(), s1.end(), doubler()); //<-- Line in question
for_each(s1.begin(), s1.end(), myprinter());
return 0;
}
//Expected output: 2, 8, 12, 14, 16, 18
//Actual output: 1, 4, 6, 7, 8, 9,
At first I thought the problem was that doubler was taking the parameter by value and not by reference, so it wasn't saving the changes to the set. But when I change the signature to be void operator()(A & a)
, I get the error of:
error: no match for call to '(doubler) (const A&)
' __f(*__first);
~~~^~~~~~~~~~
error: binding 'const A' to reference of type 'A&' discards qualifiers
I deducted that that line being pointed out is from the internal implementation of for_each
. I cannot make the parameter a const ref, because I am trying to change the a
value using the setA()
method, so it cannot be const
.
Edit: moooeeeep linked to another question that shows how to edit each element of a set. This is a practical solution to my problem, but my question is more theoretical - why can you not edit sets using for_each
, where you can edit vectors and other stl containers?
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;
}
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
Because the contents of an object in an std::set
determines its position. That's why std::set
iterators are always const
.
Of course, it is often the case that not all members of an object have an effect on its position. A possible workaround is then to declare those members mutable
. Then you can modify them using const
references.
To modify a member that does affect the object's position in a set, remove the object from the set, modify it, and add it back in.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With