1

To use a comparison operator for std::set (and many other data structures of course), it has to fulfil the Compare requirement, which is expressed as certain properties for keys a,b,c. (Crucially, in this case, equiv(a,b) and equiv(b,c)equiv(a,c).)

Now presumably this only needs to hold for keys that I will actually give to any std::set methods (otherwise I could theoretically add assert calls to my comparison operator to allow only a subset of possible keys). But does this subset of keys have to remain constant over the lifetime of my object or even at compile-time?
Or would the following be valid for elements a,b,c with a < c but no other relations?

std::set<CuriousKey, std::less<>> set = {b};
set.find(a);
set.find(c);

For context, my keys are integer intervals (with some extra data), and my code looks something like this:

#include <set>
#include <cassert>

struct IntervalWithTag {
        using Tag = char;

    int min;
    int max;
    Tag tag; // we don't want overlapping intervals with different tags.
};

constexpr bool operator<(IntervalWithTag const& lhs, IntervalWithTag const& rhs)
{
    return lhs.max < rhs.min;
}
constexpr bool operator<(int const lhs, IntervalWithTag const& rhs)
{
    return lhs < rhs.min;
}
constexpr bool operator<(IntervalWithTag const& lhs, int const rhs)
{
    return lhs.max < rhs;
}

struct IntervalWithTagSet {
    void add_interval(IntervalWithTag interval);

    std::set<IntervalWithTag, std::less<>> intervals;
};

void IntervalWithTagSet::add_interval(IntervalWithTag interval)
{
    // find all intersecting intervals
    auto const lb = intervals.lower_bound(interval.min);
    auto const ub = intervals.upper_bound(interval.max);

    // gobble up intersecting intervals
    for (auto it = lb; it != ub; ++it) {
        assert(it->tag == interval.tag);
        interval.min = std::min(interval.min, it->min);
        interval.max = std::max(interval.max, it->max);
    }

    intervals.insert(intervals.erase(lb, ub), interval);
}

int main(int argc, char *argv[])
{
        IntervalWithTagSet set;

        // Add three disjoint intervals; nothing interesting happening yet
        set.add_interval({0, 1, 'a'});
        set.add_interval({2, 3, 'a'});
        set.add_interval({4, 5, 'b'});

        // Add interval [1,2], which should leave us with intervals [0,3] and [4,5]
        // Is this undefined behaviour?
        set.add_interval({1, 2, 'a'});
}

Using auto const [lb, ub] = intervals.equal_range(interval) in IntervalWithNetSet::add_interval would definitely technically violate the Compare requirement as soon as interval intersects more than one existing interval, but I'm unsure about my code as it stands above.

MadTux
  • 139
  • 1
  • 10
  • @RichardCritten It compiles now, thanks :) – MadTux Jun 20 '22 at 19:15
  • 1
    I suspect that it's formally undefined behavior, but in practice you are likely to get away with it. **[associative.reqmts]/2** says that the comparator "induces a strict weak ordering on elements of `Key`." It's not immediately clear what "elements of `Key`" means; I can't think of any other reading than "all possible values of `Key`" (as in, a type is a set of possible values; so elements of type are its possible values). In practice, in any given member function, `set` would only have a chance to compare keys already held and keys passed as parameters, so wouldn't notice anything funny. – Igor Tandetnik Jun 20 '22 at 22:59
  • Looks like a variant of https://stackoverflow.com/questions/4816156/are-ieee-floats-valid-key-types-for-stdmap-and-stdset, but you'd have to know that `NaN` floats violate the same `Compare` requirement. Also, that question predates `Compare`. – MSalters Jun 20 '22 at 23:46

0 Answers0