Reputation: 695
Consider the simple program below, which attempts to iterate through the values of a set using NON-const references to the elements in it:
#include <set>
#include <iostream>
class Int
{
public:
Int(int value) : value_(value) {}
int value() const { return value_; }
bool operator<(const Int& other) const { return value_ < other.value(); }
private:
int value_;
};
int
main(int argc, char** argv) {
std::set<Int> ints;
ints.insert(10);
for (Int& i : ints) {
std::cout << i.value() << std::endl;
}
return 0;
}
When compiling this, I get an error from gcc:
test.c: In function ‘int main(int, char**)’:
test.c:18:18: error: invalid initialization of reference of type ‘Int&’ from expression of type ‘const Int’
for (Int& i : ints) {
^
Yes, I know I'm not actually trying to modify the elements in the for loop. But the point is that I should be able to get a non-const reference to use inside the loop, since the set itself is not const qualified. I get the same error if I create a setter function and use that in the loop.
Upvotes: 47
Views: 9259
Reputation: 18902
Adding on nate's answer:
A set is like a map with no values, only keys. Since those keys are used for a tree that accelerates operations on the set, they cannot change. Thus all elements must be
const
to keep the constraints of the underlying tree from being broken.
With C++17 there is the new extract
member function, so an alternative to const_cast
could be:
#include <iostream>
#include <string_view>
#include <set>
struct S
{
int used_for_sorting;
bool not_used_for_sorting;
bool operator<(const S &rhs) const
{ return used_for_sorting < rhs.used_for_sorting; }
};
void print(std::string_view comment, const std::set<S> &data)
{
std::cout << comment;
for (auto datum : data)
std::cout << " {" << datum.used_for_sorting
<< ',' << datum.not_used_for_sorting
<< '}';
std::cout << '\n';
}
int main()
{
std::set<S> cont = {{1, false}, {2, true}, {3, false}};
print("Start:", cont);
// Extract node handle and change key
auto nh = cont.extract({1, false});
nh.value().not_used_for_sorting = true;
print("After extract and before insert:", cont);
// Insert node handle back
cont.insert(std::move(nh));
print("End:", cont);
}
Probably useful as hot-fix. In general it's hard to see any advantage over a std::map
.
Upvotes: 0
Reputation: 8464
From the cpp reference:
In a set, the value of an element also identifies it (the value is itself the key, of type T), and each value must be unique. The value of the elements in a set cannot be modified once in the container (the elements are always const), but they can be inserted or removed from the container.
Upvotes: 12
Reputation: 15522
std::set
uses the contained values to form a fast data structure (usually, a red-black tree). Changing a value means the whole structure needs to be altered. So, forcing const
ness, std::set
prevents you from pushing it into a non-usable state.
Upvotes: 13
Reputation: 1342
The behaviour is by design.
Giving you a non-const iterator could inspire you to change the element in the set; the subsequent iterating behaviour would then be undefined.
Note that the C++ standard says that set<T>::iterator
is const
so the old-fashioned pre C++11 way still wouldn't work.
Upvotes: 6
Reputation: 1871
A set is like a map with no values, only keys. Since those keys are used for a tree that accelerates operations on the set, they cannot change. Thus all elements must be const to keep the constraints of the underlying tree from being broken.
Upvotes: 59