For some reason I am iterating over elements of a class in an std::set
and would like to slightly modify the keys, knowing that the order will be unchanged.
Iterators on std::set
are const_iterators
because if the key is modified, it might result in a bad order and therefore in set corruption. However I know for sure that my operations won't change the order of my elements in the set.
For the moment, here is my solution:
class Foo
{
public:
Foo(int a, int b): a_(a),b_(b) {}
~Foo(){}
bool operator < (const Foo& o) const { return this.a_ < o.a_ ; }
void incrementB() const { ++b_; } // <-- the problem: it is not const!
private:
const int a_;
mutable int b_; // <-- I would like to avoid this
}
void f()
{
std::set<Foo> s;
// loop and insert many (distinct on a_) Foo elements;
std::for_each(s.begin(), c.end(), [](const Foo& s) { s.incrementB(); }); // Foo must be const. iterators are const_iterators
}
How would you modify it (I know I could use an std::map
but I am curious whether you can suggest other options) to remove mutable and const?
Thanks
You can't. Set elements are required to be const for container correctness:
It forces you to realize that the key part needs to be immutable, or the data structure invariants would be broken.
struct element
{
std::string key_part; // const in the set
bool operator<(const element&o) const { return key_part<o.key_part; }
private:
mutable int m_cached; // non-key, *NOT* used in operator<
};
If you wanted to retain the possibility to 'express' const-ness in the non-key part, split it out into pairs and store them in a map:
std::map<std::string /*key_part*/, int /*m_cached*/> mapped;
or, more flexibly:
struct element
{
std::string key_part; // const in the set
bool operator<(const element&o) const { return key_part<o.key_part; }
struct value {
int m_cached;
int m_moredata; //...
} /*not in the element itself*/;
};
std::map<element, element::value> mapped;
Another option is to const_cast
to a reference type :
class Foo
{
public:
void incrementB() const { ++ const_cast< int& >( b_ ); }
private:
int b_;
};
But as sehe already said, you shouldn't modify set's elements.
One possibility might be to factor out the value part of Foo in a pimpl.
class Element
{
public:
Element(int key, int value);
Element( const Element& el );
Element( Element&& el );
~Element();
bool operator < (const Element& o) const;
void incrementValue() const;
int getValue() const;
private:
Element& operator=(const Element& );
Element& operator=( Element&& el );
struct Key
{
Key( const int key ) : m_KeyValue( key )
{
};
const int m_KeyValue;
};
struct Value;
const Key m_Key;
std::unique_ptr<Value> m_Value;
};
struct Element::Value
{
Value( int val ) : value(val)
{
}
int value;
};
Element::Element(int key, int value) :
m_Key(key),
m_Value( new Element::Value(value) )
{
}
Element::~Element()
{
}
Element::Element( const Element& el ) :
m_Key( el.m_Key ),
m_Value( new Element::Value( *el.m_Value ) )
{
}
Element::Element( Element&& el ) :
m_Key(el.m_Key)
{
m_Value = std::move(el.m_Value);
el.m_Value.release();
}
bool Element::operator < (const Element& o) const
{
return m_Key.m_KeyValue < o.m_Key.m_KeyValue;
}
void Element::incrementValue() const
{
m_Value->value++;
}
int
Element::getValue() const
{
return m_Value->value;
}
void f()
{
std::set<Element> s;
s.insert(Element(1,2));
s.insert(Element(2,3));
std::for_each(s.begin(), s.end(), [](const Element& s) { s.incrementValue(); });
std::for_each(s.begin(), s.end(), [](const Element& s)
{
std::cout << s.getValue() << std::endl;
});
}
int
main()
{
f();
return 0;
}
EDIT: To be honest however you must decide if the extra level of indirection makes sense or you would be better off using a map.