Whether this has defined behaviour is unclear, but I believe it does.
There appears to be no specific prohibition in the description of std::set
on modifying the values, other than the restriction you already hinted at that the comparer must return the same result when passed the same inputs ([associative.reqmts]p3).
However, the general rule on modifying objects defined as const
does apply. Whether set
defines its elements as const
is not spelt out. If it does, then modifying the elements is not allowed ([dcl.type.cv]p4).
But [container.requirements.general]p3 reads:
For the components affected by this subclause that declare an allocator_type
, objects stored in these components shall be constructed using the allocator_traits<allocator_type>::construct
function and
destroyed using the allocator_traits<allocator_type>::destroy
function (20.7.8.2).
std::set<T>
declares an allocator_type
, which defaults to std::allocator<T>
. std::allocator_traits<allocator_type>::construct
passes it a T *
, causing a T
to be constructed, not a const T
.
I believe this means std::set<T>
is not permitted to define its elements as const
, and for lack of any other prohibition, means modifying the elements through const_cast
is allowed.
Personally, if I couldn't find any better alternative, I would consider avoiding the issue by putting the whole thing in a wrapper struct, which defines a member mutable X x;
. This would allow modifications without const_cast
, so long as you take care to avoid changing the relative order of elements already in the set. It would additionally serve as documentation to other readers of your code that the elements of your set can and will be modified.