1

An std::set is a sorted associative container that offers fast lookup of it's elements. Keys are inserted in a sorted fashion and keys can't be modified once inserted to preserve that order.

Consider the following demonstration that constructs an std::set of int* and then tries to break it's elements' sorting :

#include <iostream>
#include <set>

int values[] = { 50, 40, 30, 20, 10 };

// Comparator that sorts by pointed value
struct comparator {
    bool operator()(const int* left, const int* right) const {
        return *left < *right;
    }
};

using my_set_t = std::set<int*, comparator>;

// Checks if each of the elements of `values` are in the set
void output(const my_set_t & foo)
{
    for (auto & x : values) {
        std::cout << x << ' ' << foo.count(&x) << '\n';
    }
    std::cout << std::endl;
}

int main()
{
    // Insert the address of each element of `values` into the set
    my_set_t foo;
    for (auto & x : values) {
        foo.emplace(&x);
    }

    output(foo);
    // Changing `values[1]` to zero should break the sorting of the set
    values[1] = 0;
    output(foo);
}

The output I got was :

50 1
40 1
30 1
20 1
10 1

50 1
0 0
30 0
20 0
10 0

The expression values[1] = 0; effectively alters one of the set's keys indirectly. This breaks the ordering and consequently seems to also break count. I assume this would also break most of the set's other functionalities.

There is obviously something wrong with the code. As far as I can tell it follows all language rules and I doesn't seem to violate any requirement that I could find for the compare function or set. But the guaranties provided by set are nonetheless broken, meaning I must have missed something.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
François Andrieux
  • 28,148
  • 6
  • 56
  • 87
  • It all boils down to what the standard means by *modify* in [\[dcl.type.cv\]/4](https://timsong-cpp.github.io/cppwp/dcl.type.cv#4) *Any attempt to modify ([expr.ass], [expr.post.incr], [expr.pre.incr]) a const object ([basic.type.qualifier]) during its lifetime ([basic.life]) results in undefined behavior.*. To me, this says it's legal. Ideally though, I would like it to not be. – NathanOliver Oct 16 '19 at 20:13
  • 1
    @NathanOliver Which `const` object is being modified? Edit : But I agree that the definition seems weak to me. – François Andrieux Oct 16 '19 at 20:13
  • 1
    @HTNW Note that the container is not `const`, my title was referring to `const` elements of a container. But now I see that it's not clear. – François Andrieux Oct 16 '19 at 20:16
  • @FrançoisAndrieux None, which is the problem. The thing you point to is not considered as part of the state of the pointer. – NathanOliver Oct 16 '19 at 20:17
  • Looks like this isn't a great example as Brian's answer points out the associative containers explicitly cover this scenario. – NathanOliver Oct 16 '19 at 20:18
  • @NathanOliver Well, I was hoping that the answer to this question would help answer that other question from earlier, but I guess not. Still, I was specially interested in this specific case because it seems like something that could accidentally happen and doesn't seem obvious to notice. Its similar to the previous question but without any obvious `const_cast` and might be more generally useful. I've changed the question title to be more specific. – François Andrieux Oct 16 '19 at 20:19

2 Answers2

5

In C++17, there is [associative.reqmts]/3:

... For any two keys k1 and k2 in the same container, calling comp(k1, k2) shall always return the same value.

Thus, your code has UB because it violates this requirement.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
-1

[Not very politically correct remark: Many things are unwritten in the standard, and even written things are sometimes incorrect. You have to make up for the missing parts, it's more reliable than parsing exact words.]

All associative containers (sorted or not sorted) are based on axiomatic requirements.

The implicit basis is that all the predicates/functors on which there are axioms are mathematical relations/functions, or the axioms wouldn't possibly mean anything. So obviously these functions, like comparators, must be well defined; it means that elements currently in a container are ordered.

But it doesn't matter if your container is empty and you change its ordering function. You can also have a polar ordering where you order elements according to the angle, with an arbitrary cutoff (minimal angle by definition), and rotate that minimum angle line, if the rotation doesn't pass over elements in the container.

curiousguy
  • 8,038
  • 2
  • 40
  • 58