This question already has answers here:
Operator< and strict weak ordering
(7 answers)
Closed 5 years ago.
Here is the code:
struct Payment
{
Payment(time_t time, float money) : mTime(time), mMoney(money) {}
bool operator==(const Payment& p) const // exact comparison
{
return mTime == p.mTime && mMoney == p.mMoney;
}
time_t mTime;
float mMoney;
};
std::vector<Payment> payments;
auto sortP = [](const Payment& p1, const Payment& p2) { return p1.mTime < p2.mTime || p1.mMoney <= p2.mMoney; };
std::sort(payments.begin(), payments.end(), sortP);
std::sort (not always, but sometimes, when mTime of two elements close to each other) raises invalid comparator assert in Visual Studio 2015. What's wrong with the code?
The problem is with the implementation of sortP. It does not satisfy the strictly weak ordering criteria. Read the details at https://www.boost.org/sgi/stl/StrictWeakOrdering.html.
I suggest the following change:
auto sortP = [](const Payment& p1, const Payment& p2)
{
// Order by mTime if they are not equal.
if ( p1.mTime != p2.mTime)
{
return p1.mTime < p2.mTime;
}
// Otherwise, order by pMoney
return ( p1.mMoney < p2.mMoney); // Use < not <=
};
You can use std::tie to make the implementation simpler.
auto sortP = [](const Payment& p1, const Payment& p2)
{
return std::tie(p1.mTime, p1.mMoney) < std::tie(p2.mTime, p2.mMoney);
};
|| p1.mMoney <= p2.mMoney should be || ((p1.mTime == p2.mTime) && (p1.mMoney < p2.mMoney)) Otherwise comparison will be wrong for case when p1.mTime is greater than p2.mTime while p1.mMoney is less than p2.Money. A good practice to ensure that such multi-field comparator satisfy strict weak ordering requirement is to write tests for all possible lt/gt combinations of fields.
Using c++11 your comparator lambda should look like:
#include <tuple>
...
auto sortP = [](const Payment& p1, const Payment& p2)
{
return std::tie(p1.mTime, p1.mMoney) < std::tie(p2.mTime, p2.mMoney);
};
Related
MSVS 16.9.3
Win7-64
I get an invalid comparator error on the second execution of my sort comparator passed to the C++ Sort function provided in <algorithm>. I don't understand why I am getting this error! The sort routine call is:
sort(sorted.begin(), sorted.end(), remsort);
sorted is defined like this:
vector<ADI::RDA_Asset*>& sorted = *(new vector<ADI::RDA_Asset*>);
These are the three versions of remsort that I used:
Version 1: always works:
bool HOAReports::remsort(ADI::RDA_Asset* lhs, ADI::RDA_Asset* rhs) {
return (lhs->getRem() < rhs->getRem());
};
Version 2: works on the first call to remsort by the sort routine, fails on the second:
bool HOAReports::remsort(ADI::RDA_Asset* lhs, ADI::RDA_Asset* rhs) {
return ( (lhs->getRem() < rhs->getRem())
|| ((lhs->getCatName()).compare(rhs->getCatName()) < 0)
|| ((lhs->getRDAName()).compare(rhs->getRDAName()) < 0)
};
Version 3: works on the first call to remsort by the sort routine, fails on the second:
bool HOAReports::remsort(ADI::RDA_Asset* lhs, ADI::RDA_Asset* rhs) {
bool return_value = ( (lhs->getRem() < rhs->getRem())
|| ((lhs->getCatName()).compare(rhs->getCatName()) < 0)
|| ((lhs->getRDAName()).compare(rhs->getRDAName()) < 0)
);
return return_value;
};
Version 2/3 have the same functionality. On the first and second call to remsort only ((lhs->getRem() < rhs->getRem()) is executed and the return_value is true. Looking at the failed assertion, it looks like the assertion is checked on both calls but fails on the second.
The MSVS code which fails is:
// FUNCTION TEMPLATE _Debug_lt_pred
template <class _Pr, class _Ty1, class _Ty2,
enable_if_t<is_same_v<_Remove_cvref_t<_Ty1>, _Remove_cvref_t<_Ty2>>, int> = 0>
constexpr bool _Debug_lt_pred(_Pr&& _Pred, _Ty1&& _Left, _Ty2&& _Right) noexcept(
noexcept(_Pred(_Left, _Right)) && noexcept(_Pred(_Right, _Left))) {
// test if _Pred(_Left, _Right) and _Pred is strict weak ordering, when the arguments are the cv-same-type
const auto _Result = static_cast<bool>(_Pred(_Left, _Right));
if (_Result) {
_STL_VERIFY(!_Pred(_Right, _Left), "invalid comparator");
}
return _Result;
}
Your comparison function does not fulfill the strict weak ordering requirements. See C++ named requirements: Compare.
In your specific case, you could implement it like this:
bool HOAReports::remsort(ADI::RDA_Asset* lhs, ADI::RDA_Asset* rhs) {
if(lhs->getRem() < rhs->getRem()) return true;
if(rhs->getRem() < lhs->getRem()) return false;
// if we get here, lhs.getRem() == rhs.getRem()
auto cmp = lhs->getCatName().compare(rhs->getCatName());
if(cmp) return cmp < 0;
// if we get here, lhs->getCatName() == rhs->getCatName()
return lhs->getRDAName() < rhs->getRDAName();
}
Demo
Or simpler, use std::tuple or std::tie:
#include <tuple>
bool HOAReports::remsort(ADI::RDA_Asset* lhs, ADI::RDA_Asset* rhs) {
return
std::tuple{lhs->getRem(), lhs->getCatName(), lhs->getRDAName()}
<
std::tuple{rhs->getRem(), rhs->getCatName(), rhs->getRDAName()};
}
I'm trying to create an std::set of GLM vectors (glm::vec3 specifically). Since C++ does not know how to perform < operation on the vectors, I must pass in a Compare funciton.
I can write my own by creating a structure like so:
struct compareVec
{
bool operator() (const glm::vec3& lhs, const glm::vec3& rhs) const
{
return lhs.x < rhs.x && lhs.y < rhs.y && lhs.z < rhs.z;
}
};
std::set< glm::vec3, compareVec > myset;
However, I'm sure that GLM includes their own vector comparison functions.
I found the following resource, but I'm unsure of how to use it:
https://glm.g-truc.net/0.9.4/api/a00137.html
How can I pass one of these comparison functions to my set?
Ok, almost there! The glm::lessThan returns a vector type, not a bool. This is why your comparator does not work. You could use glm::all on it to get a bool. From Documentation of glm::all
bool glm::all ( vecType< bool > const & v )
Returns true if all components of x are true.
Template Parameters
vecType Boolean vector types.
If this makes sense for you, you have to decide for yourself, even if I recommend against it as this, as far as I understand, will lead to the following issue:
Consider:
lhs = (1,2,3)
rhs = (0,1,4)
Than:
lhs < rhs ==> false, since lhs.x and lhs.y are larger than the corresponding components of rhs
rhs < lhs ==> false, since rhs.z component is larger than lhs.z
Since neither vector can be ordered to be less, this implies that they are equal. I doubt this is the behavior you have in mind (I was already warning you about this).
If you still decide to use it, here is a minimal working example tested on MSVC2010:
#include <set>
#include <glm/vec3.hpp>
#include <glm/detail/func_vector_relational.hpp>
struct compareVec
{
bool operator() (const glm::vec3& lhs, const glm::vec3& rhs) const
{
return glm::all(glm::lessThan(lhs, rhs));
}
};
int main()
{
std::set<glm::vec3, compareVec> myset;
return 0;
}
Maybe this helps.
By giving priority to early components of the vec, we can avoid (1,2,3) and (0,1,4) from both being less than each other. what we want is something that does the same thing as this:
lh.x != rh.x ?
lh.x < rh.x
: lh.y != rh.y ?
lh.y < rh.y
: lh.z < rh.z
I believe this should work as the compareVec:
struct compareVec {
bool operator() (const glm::vec3& lhs, const glm::vec3& rhs) const
{
glm::vec3 nequ = glm::notEqual(lhs, rhs);
return glm::lessThan(lhs, rhs)[nequ[0] ? 0 : nequ[1] ? : 1 : 2];
}
};
I couldn't find a glm function that returns the first index that is true, so I just used nequ[0] ? 0 : nequ[1]? : 1 : 2
This question already has answers here:
Operator< and strict weak ordering
(7 answers)
Closed 6 years ago.
Point is a struct of the form:
typedef struct Point {
int x;
int y;
bool operator<(const Point &other) const
{
return ((this->x < other.x) && (this->y < other.y));
};
bool operator!=(const Point &other) const
{
return ((this->x != other.x) || (this->y != other.y));
}
bool operator==(const Point &other) const
{
return ((this->x == other.x) && (this->y == other.y));
}
} Point;
and I'm using:
map<Point,int> points;
the map is initialized with {{0,0},1}. and the program used points.count(p) to check whether the point p is a key in the points map.
There's a problem and the program always returns yes! even for points not in the map. I mean if p is not a key in points, I'm getting points.count(p)==1 (and not 0).
Also, when using points.find(p) to get the iterator to check whether the received point is really ==0 (it is not), I'm getting a reference to a totally different point..
Any idea how to fix the problem?
Your operator<() is badly defined. Suppose I have a=Point{0,1} and b=Point{1,1}. Then neither a<b nor b<a nor a==b is true, which makes the operator not an ordering on the set of possible points. In order to correct this you need to make one of the dimensions (say x) the 'major' dimension in your comparison:
bool operator<(const Point &other) const
{
return ((this->x < other.x) ||
((this->x == other.x) && (this->y < other.y));
};
I'm using a c++ STL set and I want to know if it's present in the set an equivalent instance. To retrive the instance I'm using the find set method. The problem is that it doesn't work. I think the problem is in my comparator object:
bool SetComparator::operator ()( const Point* i1, const Point* i2 ) const {
if ( *i1 == *i2 )
return false;
return true;
}
The operator == is redefined for the class Point in a simple way:
bool Point::operator ==( const Point& p ) const {
if (x == p.x && y == p.y)
return true;
return false;
}
After a debugging I can see that the find method calls operator() but it doesn't find the same instance so the find returns end() but I know that there is an equal object. I think the problem is related to the set internal order. How can I do?
std::set uses partial ordering (i.e. the operator<), so when you pass in an operator that can only decide equality, you break the assumption of the implementation of std::set. Your SetComparator has to behave similar to std::less.
For example std::pair (utility) implements relational operators for two items, e.g. for operator<:
template <class T1, class T2>
bool operator< (const std::pair<T1,T2>& lhs, const std::pair<T1,T2>& rhs) {
return lhs.first<rhs.first || (!(rhs.first<lhs.first) && lhs.second<rhs.second);
}
note that (!(rhs.first<lhs.first) && lhs.second<rhs.second) is a workaround for (rhs.first == lhs.first && lhs.second < rhs.second) using only operator<
If you only want to check for equality maybe using std::set is the wrong decision. If you can hash your objects, you could use a std::unordered_set (C++11 and later).
Without going into much details why I´m doing what I´m doing let me describe the issue.
Im using a std::set for storing unique objects of a struct called VertexTypePos3Normal.
The struct is defined as following:
struct VertexTypePos3Normal {
// ctor, dtor ..
friend bool operator==(const VertexTypePos3Normal& v1, const VertexTypePos3Normal& v2);
friend bool operator<(const VertexTypePos3Normal& v1, const VertexTypePos3Normal& v2);
glm::vec3 pos;
glm::vec3 normal;
};
bool operator<(const VertexTypePos3Normal& v1, const VertexTypePos3Normal& v2) {
return (v1.pos.x < v2.pos.x) && (v1.pos.y < v2.pos.y) && (v1.pos.z < v2.pos.z) && (v1.normal.x < v2.normal.x) && (v1.normal.y < v2.normal.y) && (v1.normal.z < v2.normal.z);
}
// operator == ommited
Per default std::set uses std::less as comparison function.
So I first declared my set as std::set<VertexTypePos3Normal> set;
The elements inserted into the set are stored in a std::vector that is not containing unique values (looping over the vector).
Using std::less called my operator< but the result was not correct as the set contained mostly only 1 value although the vector contained about 15 different ones.
Here is the method inserting into the set:
void createUniqueVertices(const std::vector<const VertexTypePos3Normal>& verticesIn,
std::vector<const VertexTypePos3Normal>& verticesOut,
std::vector<unsigned short>& indicesOut)
{
//std::map<VertexTypePos3Normal, int, std::equal_to<VertexTypePos3Normal> > map;
std::set<const VertexTypePos3Normal, std::equal_to<const VertexTypePos3Normal> > set;
int indexCounter = 0;
for (auto c_it = verticesIn.cbegin(); c_it != verticesIn.cend(); ++c_it) {
//bool newlyAdded = map.insert(std::pair<VertexTypePos3Normal, int>(*c_it, indexCounter)).second;
bool newlyAdded = set.insert(*c_it).second;
//if (newlyAdded) {
//verticesOut.push_back(*c_it);
//map.insert(std::pair<VertexTypePos3Normal, int>(*c_it, indexCounter));
//++indexCounter;
//}
//indicesOut.push_back(map[*c_it]);
}
}
So I was about to try out std::equal_to instead of std::less and wrote operator==.
Now the weird stuff started:
Although I´m not calling std::less anymore and therefore also not operator<, there is an assertion error in STL (using VC compiler) _DEBUG_ERROR2("invalid operator<", _File, _Line);
So actually i got two questions:
1.) Why is my operator < not working with std::less as it is supposed to.
2.) How can operator< trigger an assertion when it is not even called.
EDIT: Thanks for all information. Looks like I totally missunderstood strict weak ordering. Using std::tie taking care of it solved my problem. Here is the updated code:
void createUniqueVertices(const std::vector<const VertexTypePos3Normal>& verticesIn,
std::vector<const VertexTypePos3Normal>& verticesOut,
std::vector<unsigned short>& indicesOut)
{
std::map<VertexTypePos3Normal, int> map;
int indexCounter = 0;
for (auto c_it = verticesIn.cbegin(); c_it != verticesIn.cend(); ++c_it) {
bool newlyAdded = map.insert(std::pair<VertexTypePos3Normal, int>(*c_it, indexCounter)).second;
if (newlyAdded) {
verticesOut.push_back(*c_it);
//map.insert(std::pair<VertexTypePos3Normal, int>(*c_it, indexCounter));
++indexCounter;
}
indicesOut.push_back(map[*c_it]);
}
}
Im using a map in the final version as the set is obsolete.
Here is my new operator<
bool operator<(const VertexTypePos3Normal& v1, const VertexTypePos3Normal& v2) {
return (std::tie(v1.pos.x, v1.pos.y, v1.pos.z, v1.normal.x, v1.normal.y, v1.normal.z) < std::tie(v2.pos.x, v2.pos.y, v2.pos.z, v2.normal.x, v2.normal.y, v2.normal.z));
}
Ordered associative containers require a strict weak ordering relation. Among the required properties is antisymmetry, that is, cmp(x,y) implies !cmp(y,x). Your definition of operator< does not satisfy this property.
Also, equality (or equivalence) may be defined as !(cmp(x,y)||cmp(y,x)), and often this is used instead of x==y. That is, operator< may be called even if you don't use it explicitly.
Your operator < is plain wrong.
You might want:
bool operator<(const VertexTypePos3Normal& v1, const VertexTypePos3Normal& v2) {
if(v1.pos.x < v2.pos.x) return true;
else if(v1.pos.x == v2.pos.x) {
if(v1.pos.y < v2.pos.y) return true;
else if(v1.pos.y == v2.pos.y) {
if(v1.pos.z < v2.pos.z) return true;
else if(v1.pos.z < v2.pos.z) {
if(v1.normal.x < v2.normal.x) return true;
else if(v1.normal.x == v2.normal.x) {
if(v1.normal.y < v2.normal.y) return true;
else if(v1.normal.y < v2.normal.y) {
if(v1.normal.z < v2.normal.z) return true;
}
}
}
}
}
return false;
}
Note: That should be split into two less function calls for glm::vec3 (having bool less(const glm::vec3&, const glm::vec3&);)