3

Given the following code:

#include <set>

struct X {
    int a, b;
    friend bool operator<(X const& lhs, X const& rhs) {
        return lhs.a < rhs.a;
    }
};

int main() {
    std::set<X> xs;
    // some insertion...
    auto it = xs.find({0, 0}); // assume it != xs.end()

    const_cast<X&>(*it).b = 4; // (1)
}

Is (1) defined behavior? I.e., am I allowed to const_cast a reference to an element obtain from a const_iterator of a std::set and modify it if the modification does not alter the ordering?

I have read some posts here and there proposing this kind of const_cast, but I was wondering if this was actually defined behavior.

Holt
  • 36,600
  • 7
  • 92
  • 139

1 Answers1

2

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.