Say I have a set with a comparator like this:
struct prefix_comparator {
using is_transparent = void;
struct prefix {
std::string_view of;
};
bool operator()(std::string_view l, std::string_view r) const {
return l < r;
}
bool operator()(std::string_view l, prefix r) const {
// Only compare up to the size of the prefix
auto result = l.compare(0, r.of.length(), r.of);
return result < 0;
}
bool operator()(prefix l, std::string_view r) const {
auto result = r.compare(0, l.of.length(), l.of);
return result > 0;
}
};
So prefix_comparator::prefix{ "XYZ" } compares equivalent to any string starting with "XYZ" with this comparator. Would it be UB to use this in a std::set?
It does not form equivalence classes with things of type prefix_comparator::prefix, since prefix objects cannot be compared with each other and stuff like equiv("XYZABC", prefix{ "XYZ" }) && equiv(prefix{ "XYZ" }, "XYZabc") but not equiv("XYZABC", "XYZabc"). But the set doesn't store prefix objects, so I don't know if this applies.
It seems to work fine in practice (with libstdc std::set): count() returns the correct value greater than 1, equal_range can return an iterator range larger than one element, etc. I'm unsure if find is usable since it only returns an iterator to a single element (possibly it only returns an arbitrary element?)
CodePudding user response:
From the draft standard.
[associative.reqmts.general]/24.2.7.1
Each associative container is parameterized on Key and an ordering relation Compare that induces a strict weak ordering ([alg.sorting]) on elements of Key.
there is no "top level" requirement that non-Key type elements are strict weak ordered.
There are restrictions on methods and what the arguments are. Here is the common set used:
(7.20)
klis a value such that a is partitioned ([alg.sorting]) with respect toc(x, kl), withxthe key value ofeandeina;
(7.21)
kuis a value such that a is partitioned with respect to!c(ku, x), withxthe key value ofeandeina;
(7.22)
keis a value such that a is partitioned with respect toc(x, ke)and!c(ke, x), withc(x, ke)implying!c(ke, x)and withxthe key value ofeandeina;
(7.23)
kxis a value such that
(7.23.1)
ais partitioned with respect toc(x, kx)and!c(kx, x), withc(x, kx)implying!c(kx, x)and withxthe key value ofeandeina, and
(7.23.2)
kxis not convertible to eitheriteratororconst_iterator; and [...]
these are used in concert with a_tran, which is the shortcut the standard uses for "an associative container when Compare has is_transparent", to describe the requirements of most (all?) of the extra methods you get when you have is_transparent turned on.
So under this, the important part is that your non-key elements actually used actually partition your container with the actual keys stored in the container:
a_tran.erase(kx)
122 # Result:
size_type
123 # Effects: Erases all elements in the container with key
rsuch that!c(r, kx) && !c(kx, r)istrue.
what this means is you have to check each and every methods requiements for what it expects. Count, for example:
a_tran.count(ke)
150 # Result:
size_type
151 # Returns: The number of elements with key
rsuch that!c(r, ke) && !c(ke, r).
152 # Complexity: log(
a_tran.size())a_tran.count(ke)
So here the argument must be ke, so the specific value passed in has to be a "nice" partition of the current contents of the associative container.
