Hi,
Let f be a function (in the mathematical sense) mapping objects of type
Type to a totally ordered set (for simplicity), called keys. By this
ordering, std::set is able to organize data of type Type in an efficient
way to enable fast associative searching, insertion and removal (e.g. by
a red-black tree). An essential requirement for this to be possible is
that the key associated to each object remains unchanged when stored in
std::set. Otherwise invariants of underlying data structures could be
easily broken (e.g. binary search property of a red-black tree).
As a principle of programming, if there is ever an invariant, then that
invariant must be checked automatically by the compiler, or by the
library at runtime (function preconditions, assertions). There are
exceptions, of course, when other priorities conflict, such as
performance or memory use (e.g. checking if iterators are part of the
same container).
As a good example of programming, the std::set enforces the invariant of
unchanging keys, by making all external references to its objects const.
This makes sure that changing a key does not happen by accident. On the
other hand, this decision conflicts with genericity. I often find myself
dealing with objects whose state can, and should, be freely changed
without affecting their associated key. There are several hacks that can
be used to work around this problem:
1) The first hack is to use the mutable keyword in the Type's member
variables. However, this would be incorrect when the member variable is
part of the logical state of the object (compared to just physical
state, which can include additional cache variables).
2) The second hack is to use const_cast for the object to gain access to
modifying its state. This is more correct. However, const_cast exists
only to correct for the mistakes done in the past (and for some tricks
to avoid code replication in const/non-const pair of member functions).
3) The third hack is to change to use std::map instead, by separating
the key to its own object, and then associating it with the rest of the
object. However, this is not always sufficient. The problem is that now
the key is not accessible from the value object. Where this is a
problem, the key must be duplicated to the value type. Interestingly,
the key in the value object is now in danger of being modified different
to the key in the key object.
The best practical approach at the moment, in my opinion, is to use the
second hack of const_cast.
The conflict in genericity is caused by the following problem:
The enforcement of the references to objects of std::set to const
is too strict.
The core of this claim is in that it should only be the associated key
of the object which should be protected from change, and not the whole
object. The latter is usually a sufficient condition for the former (one
can make up situations where this does not hold, such as keys retrieved
by reference from somewhere else). However, it is not a necessary
condition.
I now wonder what other ways there would be to enforce unchanging keys
in std::set, which would allow for changing the other unrelated state of
the object.
An obvious solution would be to remove the constness of the references
completely. However, this would open up a very probable source of bugs,
where the novices change the elements arbitrarily. And not only the
novices; since this goes against the principle of checking invariants,
it would open the door to Murphy's law (it will happen by accident also
to professionals). Thus I don't see this as an option.
Of course, the same problem concerns std::unordered_set also.
I don't know whether there is a better solution to the problem than the
current one + const_cast. However, at least the problem has now been
formulated.
What are your thoughts?
--
http://kaba.hilvi.org
[ See
http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]