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.