How do I fix an Invalid Comparator error? - c++

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()};
}

Related

C++: “invalid comparator” assert [duplicate]

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);
};

'operator<' error while creating an std::pair<A,B>

I already have a workaround for this but would still like to understand the problem.
I have a Multimap
multimap<QPoint,Figure*> mymap;
QPoint is a class from Qt 5.4. Figure* is a pointer to a
class I have concocted myself.
Now I want to add elements to that map.
This sample works fine:
multimap<int,int> test;
test.insert(pair<int,int>(41,43));
As does this one (being said workaround)
std::pair<QPoint,Figure*> p;
p.first = pos;
p.second = sub_fig;
mymap.insert(p);
However, the plain first reflex of
std::pair<QPoint,Figure*> p(pos, sub_fig);
has the compiler at that line state something like:
[..]
scanner.cpp:264:17: required from here
/usr/include/c++/4.9/bits/stl_function.h:371:20: error: no match for
‘operator<’ (operand types are ‘const QPoint’ and ‘const QPoint’)
{ return __x < __y; }
[..]
followed by the usual five kilometers of stacked STL error messages.
First: The 'types' are not QPoint and QPoint. They are, as
stated above, QPoint and Figure*.
Anyone who can riddle this?
CORRECTION
My work-around does not work either after all. I had forgotten to
de-comment
res.insert(p);
Here is the complete pertinent code:
multimap<QPoint,Figure*> res;
// ...
vector<Figure*> stack = figure->get_above_figure_stack();
for (vector<Figure*>::const_iterator CI2=stack.begin();
CI2!=stack.end();CI2++)
{
// ..
Figure* sub_fig = *CI2;
std::pair<QPoint,Figure*> p;
p.first = pos;
p.second = sub_fig;
res.insert(p); // <- The bad line.
}
multimap needs an ordering relation for the keys, and its default is to use < (in the guise of std::less).
Since QPoint doesn't have an overload of operator<, the compiler is complaining that it doesn't exist.
It's not difficult to provide one:
bool operator< (const QPoint& lhs, const QPoint& rhs)
{
return lhs.x() < rhs.x() || (lhs.x() == rhs.x() && lhs.y() < rhs.y());
}
or
bool lessQPoints (const QPoint& lhs, const QPoint& rhs)
{
return lhs.x() < rhs.x() || (lhs.x() == rhs.x() && lhs.y() < rhs.y());
}
multimap<QPoint, Figure*, lessQPoints> mymap;
The keys in a multimap are ordered by default with std::less which invokes operator< on the key type.
Your key object (QPoint) has no operator< to do the comparison.
You will need to provide your own comparison function using the approprate multimap constructor.

qsort of a structure in c++

I try to sort a structure but I have this error :
error: cannot convert ‘std::vector<Node>’ to ‘void*’ for argument ‘1’ to ‘void qsort(void*, size_t, size_t, __compar_fn_t)’
qsort(nodes,nodes.size(), sizeof(Node), dataClustering::compare);
This is my code :
compare function :
int compare(const void * node1, const void * node2){
string name1 = ((const struct Node*)node1)->name;
string name2 = ((const struct Node*)node2)->name;
int start1 = ((const struct Node*)node1)->start;
int start2 = ((const struct Node*)node2)->start;
if(name1 <= name2 && start1 <= start2){
return -1;
}
else if(name1 > name2 && start1 > start2){
return 1;
}
else{
return 0;
}
}
Call of compare in another function :
qsort(allNodes,allNodes.size(), sizeof(Node), compare);
And the header in .hpp file (I try to put it in static but this don't solve the error):
int compare(struct Node *, struct Node *);
You can pass vector to C-style sort, for example, like this:
qsort(&allNodes[0], allNodes.size(), sizeof(Node), compare);
You would be better off using std::sort, which works with vectors natively:
bool compareNodes(const Node& lhs, const Node& rhs) {
return (lhs.name < rhs.name)
|| (lhs.name == rhs.name && lhs.start < rhs.start);
}
...
std::sort(allNodes.begin(), allNodes.end(), compareNodes);
in C++11/C++14 you can define comparison as a lambda, too:
std::sort(allNodes.begin(), allNodes.end(), [] (const auto& lhs, const auto& rhs) {
return (lhs.name < rhs.name)
|| (lhs.name == rhs.name && lhs.start < rhs.start);
});
(the code above uses auto for lambda arguments, a C++14 feature).
The error is that you
cannot convert ‘std::vector<Node>’ to ‘void*’
and thus, a std::vector<Node> cannot possibly be a suitable first argument to a function like qsort with signature
void (void*, size_t, size_t, __compar_fn_t)
While there are things you could do to use qsort here, you really shouldn't: you should use std::sort from the C++ standard library:
using std::sort;
sort(begin(allNodes), end(allNodes), compare_function);
Here, compare_function should be a function that acts like < and takes two arguments of the appropriate type (e.g. the actual type of the objects you're sorting or const references to them, not pointers to them or void pointers) and returns bool if the first argument is less than the second. (compare_function doesn't actually have to be a function: it can also be any object that has an appropriate operator() or a lambda)
If you want to just sort, use
#include <algorithm>
...
bool myfunction (const Node &lhs, const Node &lhs) {
if(lhs.name < rhs.name) return true;
return (lhs.name == rhs.name && lhs.start < rhs.start));
}
...
std::sort(allNodes.begin(), allNodes.end(), compare_func);
Further read: std::sort
If you want to use cstyle qsort(which I suggest you should not), add a wrapper function to do the conversion of void * to Node * and change the first argument of qsort to static_cast<void *>(&allNodes[0])

Find equal instance in a c++ set

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).

Invalid operator< using std::equal_to

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&);)