overloading operator < for std::set confused me - c++

I know that I have to overload operator < for std::set.
I overload operator < with two classes: "UniqueID" and "UniqueIDWithBug".
The only difference is "UniqueID" added code this->unique_id_a_ == t.unique_id_a_ while comparing.
Then I put same elements into the two sets.
Finally I find one element inside the sets.
One set can find it, another can not.
This problem confused me for a long time.
struct UniqueID {
uint64_t unique_id_a_{0};
uint64_t unique_id_b_{0};
bool operator<(const UniqueID &t) const {
if (this->unique_id_a_ < t.unique_id_a_) {
return true;
}
if (this->unique_id_a_ == t.unique_id_a_ &&
this->unique_id_b_ < t.unique_id_b_) {
return true;
}
return false;
}
};
struct UniqueIDWithBug {
uint64_t unique_id_a_{0};
uint64_t unique_id_b_{0};
bool operator<(const UniqueIDWithBug &t) const {
if (this->unique_id_a_ < t.unique_id_a_) {
return true;
}
return (this->unique_id_b_ < t.unique_id_b_);
}
};
// init data
std::set<UniqueID> _set = {
{17303934402126834534u, 2922971136},
{8520106912500150839u, 3118989312},
{9527597377742531532u, 2171470080},
{10912468396223017462u, 3972792320},
};
std::set<UniqueIDWithBug> _set_with_bug = {
{17303934402126834534u, 2922971136},
{8520106912500150839u, 3118989312},
{9527597377742531532u, 2171470080},
{10912468396223017462u, 3972792320}};
UniqueID _unique_id = {10912468396223017462u, 3972792320};
UniqueIDWithBug _unique_id_with_bug = {10912468396223017462u, 3972792320};
if (_set.find(_unique_id) == _set.end()) {
std::cout << "_set not find" << std::endl;
}
if (_set_with_bug.find(_unique_id_with_bug) == _set_with_bug.end()) {
std::cout << "_set_with_bug not find" << std::endl;
}
The outputs:
_set_with_bug not find

The less-than operation you define for use with std::set (and others) must be a valid strict weak ordering.
Your UniqueIDWithBug ordering is not.
For example, consider:
UniqueIDWithBug a{1, 10};
UniqueIDWithBug b{2, 5};
Now observe that both a < b and b < a are true. This is just a quick demonstration that you do not have a strict weak ordering; indeed, this is not an ordering at all!
So your program has undefined behaviour. The internals of the std::set mechanism assume a valid ordering, but yours is not. In this case the observable result was "element not found". It could have been "make a pizza".
Constructing a good strict weak ordering can be difficult, but you've already done the hard work, because UniqueID's ordering is correct.
Alternatively, abandon the ordering entirely, define a hash function, and switch to unordered_set.

Related

In which case a insert in a std::map can fail?

In my code I have those lines:
if(mymap.count(plan) == 0) {
std::vector<XYZ> v;
v.reserve(10);
mymap.emplace(plan, v);
std::cout << "Plan " << plan.normal << " # " << plan.O << " added";
}
//I inserted this code for debugging
std::map<Plan, std::vector<XYZ>>::const_iterator it = mymap.find(plan);
if(it == this->intersections.end())
std::cout << "not found";
How is it possible that I can read in the console plan added and just after not found ?
My map is declared as such:
std::map<Plan, std::vector<XYZ>, PlanComp> mymap;
At some point I thougt it comes from the comparator, but it respects irreflexivity, antisymmetry, transitivity, transitivity of equivalence (which is enough according to this blog) :
struct PlanComp {
bool operator()(const Plan& l, const Plan& n) const {
return (l.O.x != n.O.x) || (l.O.y != n.O.y) || (l.O.z != n.O.z)
|| (l.normal.x != n.normal.x) || (l.normal.y != n.normal.y) || (l.normal.z != n.normal.z);
}
};
struct XYZ {
double x;
double y;
double z;
};
struct Plan {
XYZ O;
XYZ plan;
};
Your comparator does not define a strict weak ordering (loosely speaking, "less than" semantics which define an order for your elements). Therefore your code exhibits undefined behaviour.
The simplest solution would be to use a lexicographical comparator - compare x first, then compare y only in the event of a tie, and so on. In C++11 that's even simpler; operator < for tuples already does this for you (and you can use std::tie to get the tuples). See the answers to Operator < and strict weak ordering for examples.

std map composite key

I have a problem with the operator<() method which is required for a std::map. I'm using a struct as composite key that looks as follows:
struct MyKey {
std::string string1;
std::string string2;
std::string string3;
unsigned int uint1;
friend bool operator<(const MyKey& mk1, const MyKey& mk2)
{
return mk1.string1 < mk2.string1 && mk1.string2 < mk2.string2 &&
mk1.string3 < mk2.string3 && mk1.uint1 < mk2.uint1;
}
}
As introduced I want to use a composite key with 4 values, but I don't know how to achieve this for the operator< method. I observed that only 1 value is stored at a time!
Can anybody tell me how the right condition looks like?
Thanks in advance!
The Standard library's associative containers such as std::map, std::set, std::multiset, std::multimap, std::bitset require that the ordering of elements must follow Strict Weak Ordering, which means your implementation of operator< must follow strict weak ordering. So one implementation could be this:
friend bool operator<(const MyKey& mk1, const MyKey& mk2)
{
if (mk1.string1 != mk2.string1 )
return mk1.string1 < mk2.string1;
else if ( mk1.string2 != mk2.string2)
return mk1.string2 < mk2.string2;
else if (mk1.string3 != mk2.string3)
return mk1.string3 < mk2.string3;
else
return mk1.uint1 < mk2.uint1;
}
Or you can implement it as:
friend bool operator<(const MyKey& mk1, const MyKey& mk2)
{
auto const & t1 = std::tie(mk1.string1, mk1.string2, mk1.string3, mk1.uint1);
auto const & t2 = std::tie(mk2.string1, mk2.string2, mk2.string3, mk2.uint1);
return t1 < t2;
}
In this solution, std::tie function creates two tuples t1 and t1 of the references of the arguments passed to it, and then compare t1 and t2 using overloaded operator< for std::tuple instead. The operator< for tuple compares the elements lexicographically — strict-weak ordering is achieved..
I think you have a problem in that the operator< doesn't necessarily implement strict weak ordering. There are too many combinations where A<B is false and B<A is also false, where A and B are MyKey objects. This is interpreted as A being equal to B.
The problem with your implementation is that it's not stable, consider...
return mk1.string1 < mk2.string1 && mk1.string2 < mk2.string2 &&
mk1.string3 < mk2.string3 && mk1.uint1 < mk2.uint1;
...evaluating { "a", "a", "a", 1 } < { "a", "b", "a", 1 } = a<a && ... = false && ... = false
...but { "a", "b", "a", 1 } < { "a", "a", "a", 1 } = a<a && ... = false && ... = false
So, neither is reported as less than the other, despite them not being equal keys in the map.
A working solution: it's concise and efficient to do each necessary string comparisons only once...
friend bool operator<(const MyKey& mk1, const MyKey& mk2)
{
int x;
return (x = mk1.string1.compare(mk2.string1)) ? x < 0 :
(x = mk1.string2.compare(mk2.string2)) ? x < 0 :
(x = mk1.string3.compare(mk2.string3)) ? x < 0 :
mk1.uint1 < mk2.uint1;
}

C++: std::sort using already destroyed object with custom predicate?

I'm having a very odd problem with some code using std::sort. If I replace std::sort by stable_sort the problem goes away.
class Entry
{
public:
Entry() : _date(0), _time(0), _size(0) {}
Entry(unsigned int d, unsigned int t, unsigned int s) : _date(d), _time(t), _size(s) {}
~Entry() {_size=0xfffffffe;}
unsigned int _date, _time, _size;
};
void initialise(std::vector<Entry> &vec)
vec.push_back(Entry(0x3f92, 0x9326, 0x1ae));
vec.push_back(Entry(0x3f92, 0x9326, 0x8a54));
//.... + a large number of other entries
}
static bool predicate(const Entry &e1, const Entry &e2)
{
// Sort by date and time, then size
if (e1._date < e2._date )
return true;
if (e1._time < e2._time )
return true;
return e1._size < e2._size;
}
int main (int argc, char * const argv[]) {
using namespace std;
vector<Entry> vec;
initialise(vec);
sort(vec.begin(), vec.end(), predicate);
vector<Entry>::iterator iter;
for (iter=vec.begin(); iter!=vec.end(); ++iter)
cout << iter->_date << ", " << iter->_time <<
", 0x" << hex << iter->_size << endl;
return 0;
}
The idea is that I sort the data first by date and time then by size. However, depending on the data in the vector, I will end up with 0xfffffffe in the size printed out at the end for the first object, indicating that a destroyed object has been accessed, or a seg fault during the sort.
(Xcode 3.2.4 - 64 bit intel target)
Any ideas anyone??
I suspect it has something to do with my predicate, but I can't see for the life of me what it is....!!
This page seems to refer to the same problem:
http://schneide.wordpress.com/2010/11/01/bug-hunting-fun-with-stdsort/
but the reason he gives (that the predicate needs to define a strict weak ordering) seems to be satisfied here...
Your predicate does not satisfy strict weak ordering criteria. Look at your function and ask yourself, what happens if e1's date comes after e2, but e1's time comes before e2?
I think what your predicate really should be is something like this:
static bool predicate(const Entry &e1, const Entry &e2)
{
// Sort by date and time, then size
return e1._date < e2._date ||
(e1._date == e2._date &&
(e1._time < e2._time ||
(e1._time == e2._time && e1._size < e2._size)));
}
What you wrote - if e1._date>e2._date, the first condition will be false, but the second may still be true and the function will still claim that e1<e2 which is probably not what you want.
Your code needs to be:
static bool predicate(const Entry &e1, const Entry &e2)
{
// Sort by date and time, then size
if (e1._date != e2._date )
return e1._data < e2._date;
if (e1._time != e2._time )
return e1._time < e2._time;
return e1._size < e2._size;
}
If e2's date is after e1, then your version treats goes on to compare the time and size. This is not what you want. This eventually confuses std::sort because if you swap e1 and e2 you will not get a consistent answer.

Using comparator for STL set

Check the following code:
string toLowerCase(const string& str) {
string res(str);
int i;
for (i = 0; i < (int) res.size(); i++)
res[i] = (char) tolower(res[i]);
return res;
}
class LeagueComparator
{
public:
bool operator()(const string& s1, const string& s2)
{
return toLowerCase(s1) < toLowerCase(s2);
}
};
int main()
{
set<string, LeagueComparator> leagues;
set<string, LeagueComparator>::iterator iter;
leagues.insert("BLeague");
leagues.insert("aLeague"); // leagues = {"aLeague", "BLeague"}
leagues.insert("ALeague");
for (iter = leagues.begin(); iter != leagues.end(); iter++)
cout << *iter << endl;
return 0;
}
The output is:
aLeague
BLeague
which is shocking to me. I thought (and expecting) the output would be:
aLeague
ALeague
BLeague
Before the execution of leagues.insert("ALeague");, the leagues contains "aLeague" and "BLeague". My question is, while executing leagues.insert("ALeague"); why the machine treats "ALeague" == "aleague"? According to my understanding, there is no element "ALeague" in leagues. So "ALeague" should be inserted into leagues. The comparator should determine where to put "ALeague".
Thanks in advance.
PS: Please don't hit me for using C style cast. :P I'm too lazy to type static_cast.
Your comparator, thanks to the toLowerCase, says that "aLeague" == "ALeague". Since (according to your comparator) "aLeague" < "ALeague" == false and "ALeague" < "aLeague" == false, they must be equivalent. And inserting an equivalent element into a set doesn't do anything.
When you insert any value to a set, the object checks to see whether it already contains that value. Your LeagueComparator object compares ALeague with the other two values already in the set. It determines that the existing value aLeague is neither greater than nor less than the proposed new entry (ALeague), so they must be equal, and so it doesn't proceed with the insert. The set remains with just two elements. That's the whole point of providing a customer comparison object, so you can control how the set determines whether two elements match.
Given the comparator you provided, "ALeague" is indeed equivalent "aLeague".
Given two values, x and y, and a less-than comparator z:
If z(x, y) is true, then x is less than y
If z(y, x) is true, then y is less than x
If neither is true, then x is equivalent to y
If both are true, then you have a broken comparator.
Replace your LeagueComparator with
class LeagueComparator
{
public:
bool operator()(const string& s1, const string& s2)
{
return toLowerCase(s1) < toLowerCase(s2) ||
!(toLowerCase(s2) < toLowerCase(s1)) && s1 < s2;
}
};

key comparisons in c++ map not working

I have created a map with ClassExpression as the key and std::string as the value. The key comparator is given below
class ClassExpressionComparator {
public:
bool operator()(const ClassExpression& lhs,
const ClassExpression& rhs) const {
return ((lhs.quantifier == rhs.quantifier) &&
(lhs.property.compare(rhs.property) == 0) &&
(lhs.concept.compare(rhs.concept) == 0));
}
};
ClassExpression contains the 3 fields mentioned in the comparator. I compare all the 3 fields. When I use find() of map, even if the key is not present in the map, it says that it found the key and gives an existing key as the result (getting first key as the result).
I tried the following
boost::shared_ptr< std::map<ClassExpression, std::string,
ClassExpressionComparator> > cemap(
new std::map<ClassExpression,
std::string, ClassExpressionComparator>());
ClassExpression ce1;
ce1.quantifier = com::xxxx::kb::SOME;
ce1.property = "<http://www.w3.org/2002/07/acts-on>";
ce1.concept = "<http://www.w3.org/2002/07/Tissue>";
populateMap(cemap, ce1);
ClassExpression ce2;
ce2.quantifier = com::xxxx::kb::SOME;
ce2.property = "<http://www.w3.org/2002/07/contained-in>";
ce2.concept = "<http://www.w3.org/2002/07/HeartValve>";
populateMap(cemap, ce2);
ClassExpression ce3;
ce3.quantifier = com::xxxx::kb::SOME;
ce3.property = "<http://www.w3.org/2002/07/has-location>";
ce3.concept = "<http://www.w3.org/2002/07/Endocardium>";
std::map<ClassExpression, std::string, ClassExpressionComparator>::iterator
ceIterator = cemap->find(ce3);
if (ceIterator == cemap->end()) {
std::cout << "Not found" << std::endl;
}
else {
std::cout << "Found; concept = " << ceIterator->second << std::endl;
}
ClassExpressionComparator cmp;
std::cout << "compare: " << cmp(ce1, ce3) << std::endl;
populateMap() just does an insert, in my actual code, I do a few more things in it, I wanted to keep the same flow, so left it that way.
The output of cmp(ce1, ce3) is 0 but when I do a find(ce3), the result is that it found it at the first key position instead of returning end(). Where am I going wrong here?
Thank you.
Raghava.
You wrote an equality comparison. map requires a less-than comparison. (Or greater-than if you want the keys in decreasing order.)
My usual idiom for doing this:
bool operator()(const ClassExpression& lhs,
const ClassExpression& rhs) const {
return lhs.quantifier < rhs.quantifier? true
: rhs.quantifier < lhs.quantifier? false
: lhs.property.compare(rhs.property) < 0? true
: lhs.property.compare(rhs.property) > 0? false
: lhs.concept.compare(rhs.concept) < 0;
}
The map is a sorted container, the comparator operator it's looking for is one that implements a strict weak ordering, like <. You're giving it an equals operator, which will screw up the ordering.