This question already has an answer here:
Efficient operator< with multiple members
(1 answer)
Closed 1 year ago.
I would like to use a struct with three integer members as a key. How can I overload the < operator. I understand that for two members it could be overloaded as:
bool operator < (const CacheKey& a, const CacheKey& b) {
return a.x < b.x || (a.x == b.x && a.y < b.y);
}
The generic solution is:
if (a.x != b.x) return a.x < b.x;
if (a.y != b.y) return a.y < b.y;
// ...
return false;
Or:
return std::tie(a.x, a.y) < std::tie(b.x, b.y);
(In this case, you might want to create a member function that returns the tied members, to be able to do something like a.tie() < b.tie() for all needed operators.)
Or, in C++20, you would add following to your class to automatically get all comparison operators including <:
auto operator<=>(const CacheKey &) const = default;
The most direct approach would be:
class Foo {
friend bool operator<(const Foo&, const Foo&);
int a, b, c;
}
bool operator<(const Foo& lhs, const Foo& rhs) {
return (lhs.a < rhs.a) ||
(lhs.a == rhs.a && lhs.b < rhs.b) ||
(lhs.a == rhs.a && lhs.b == rhs.b && lhs.c < rhs.c);
}
Related
Hello to everyone who will find this post helpful. I had this custom class Position and I wanted to use it in std::map
class Position
{
public:
int x;
int y;
Position(const int &pos_x, const int &pos_y)
: x(pos_x), y(pos_y) {}
};
But i had error because it couldn't compare the classes to each other. So I created this comparison class:
class PosComparator
{
public:
bool operator()(const ConsoleRenderer::Position &A, const ConsoleRenderer::Position &B)
{
if (A.x < B.x)
return true;
else if (A.x == B.x && A.y < B.y)
return true;
return false;
}
};
But I got this this error:
static assertion failed: comparison object must be invocable as const
As the error implied, adding const at the end solves the problem:
class PosComparator
{
public:
bool operator()(const ConsoleRenderer::Position &A, const ConsoleRenderer::Position &B) const
{
if (A.x < B.x)
return true;
else if (A.x == B.x && A.y < B.y)
return true;
return false;
}
};
Or make it shorter by using std::tie, creating tuples which can be use to compare:
class PosComparator
{
public:
bool operator()(const ConsoleRenderer::Position &A, const ConsoleRenderer::Position &B) const
{
return std::tie(A.x, A.y) < std::tie(B.x, B.y);
}
};
I solved it with simply adding const at the end of the line.
class PosComparator
{
public:
bool operator()(const ConsoleRenderer::Position &A, const ConsoleRenderer::Position &B) const
{
if (A.x < B.x)
return true;
else if (A.x == B.x && A.y < B.y)
return true;
return false;
}
};
Hopefully this helped. :)
In the following simple structs, p3 inherits from p2.
struct p2 {
double x, y;
p2(double x, double y) : x(x),y(y){}
bool operator ==(const p2& b) const{ return x == b.x && y == b.y; }
bool operator !=(const p2& b) const{ return !(*this == b); }
};
struct p3 : p2 {
double z;
p3(double x, double y, double z) : p2(x,y),z(z){}
bool operator ==(const p3& b) const{ return x == b.x && y == b.y && z == b.z; }
bool operator !=(const p3& b) const{ return !(*this == b); }
};
In the overloaded comparison operators for p3, how can I replace the x == b.x && y == b.y part with a call to the overloaded operator from the parent class?
Just use the scoping operator :: in the derived struct
So in your operator==() in struct p3 looks like this:
bool operator==(const p3& b) const {
return p2::operator==(b) && z == b.z;
}
bool operator==(const p3& b) const
{
return p2::operator==(b) && z == b.z;
// ~~~~~~~~~~~~~~~~^
}
I have a struct of following type which I am planning use as a key in a map.
Hence I write a comparator like below. I would like to know if there is a more elegant yet efficient way of doing this.
May be using std::pair or something.
struct T
{
int a, b, c, d;
bool operator< (const T& r) {
if (a < r.a)
return true
else if (a == r.a)
if (b < r.b)
return true;
else if (b == r.b)
if (c < r.c)
return true;
else if (c == r.c)
if (d < r.d)
return true;
return false;
}
}
Can you use C++11? If so:
struct T {
int a, b, c, d;
bool operator<(const T& rhs) const {
return tied() < rhs.tied();
}
private:
std::tuple<int, int, int, int> tied() const {
return std::make_tuple(a, b, c, d);
}
};
Alternatively, I would prefer returning at each possible opportunity to avoid the error-prone awkward nesting approach:
bool operator<(const T& rhs) const {
if (a != rhs.a) return a < rhs.a;
if (b != rhs.b) return b < rhs.b;
if (c != rhs.c) return c < rhs.c;
return d < rhs.d;
}
You can use...
bool operator<(const T& r)
{
return a < r.a ||
a == r.a && (b < r.b ||
b == r.b && (c < r.c ||
c == r.c && d < r.d));
}
Or...
return a != r.a ? a < r.a :
b != r.b ? b < r.b :
c != r.c ? c < r.c :
d < r.d;
You've said you're not using C++11, and Barry has a good illustration of a tuple approach, but for future reference and other interested parties, with a little reusable support code...
bool less_by_pairs()
{
return false;
}
template <typename T, typename U, typename ...Args>
bool less_by_pairs(const T& v1, const U& v2, Args... args)
{
return v1 != v2 ? v1 < v2 : less_by_pairs(args...);
}
...you can implement such operators more easily...
bool operator<(const T& r)
{
return less_by_pairs(a, r.a, b, r.b, c, r.c, d, r.d);
}
You could do something similar providing a list of members to compare, but the notation for that's actually a little verbose anyway.
You could use another field to store a key. This key value could be generated through a formula, that takes as input (a,b,c,d)
Like so:
void hash()
{
key = (a ^ b ^ c ^ d);
}
As result, you would need to only compare this key to know if the contents are the same.
I have a set of tuples of 3 integers and I don't want any duplicates. That is, I don't want 2 entries with the same 3 values.
And here is my code.
struct Key{
unsigned a;
unsigned b;
unsigned c;
public:
Key(unsigned _a, unsigned _b, unsigned _c) :
a(_a),
b(_b),
c(_c) {}
bool operator<(const Key& rhs) const
{
if (a < rhs.a) {
return true;
}
if (b < rhs.b) {
return true;
}
if (c < rhs.c) {
return true;
}
return false;
};
};
std::set<Key> myset;
But I see duplicates in myset sometimes. I can't catch exactly what sequence causes the duplicate entry to be added. It doesn't always happen.
My question is this, is there something intrinsically wrong with my operator< function?
It's nearly right! But you are cascading too soon.
bool operator<(const Key& rhs) const
{
if (a < rhs.a)
return true;
if (a > rhs.a)
return false;
if (b < rhs.b)
return true;
if (b > rhs.b)
return false;
return (c < rhs.c);
};
Otherwise the following, for example, gives the wrong result:
Key lhs{3,1,0};
Key rhs{2,2,0};
assert(lhs < rhs); // passes, wrongly, because !(3 < 2) but then (1 < 2).
// you need an immediate `return false` when !(3 < 2)
It is safer to do something like this:
bool operator<(const Key& rhs) const
{
return std::tie(a, b, c) < std::tie(rhs.a, rhs.b, rhs.c);
}
C++'s standard library already knows what to do with that, so you don't have to.
Now, how can your bug lead to duplicate keys in a set?
Set's internal algorithm relies on the ordering being a strict weak ordering — when you break that precondition, you break the algorithms managing the internal tree, which is constructed and arranged using this ordering as its bible.
All hell breaks loose, basically. You could get a crash from this. In your case the symptoms were somewhat more benign (at least for now), with a deformed/mangled data tree resulting in the appearance of duplicated data.
It's folly to try to reason about the specific chain of events that led to a specific outcome, if you started off by breaking the preconditions and causing UB.
https://stackoverflow.com/a/979768/560648
Implementing comparison operators via 'tuple' and 'tie', a good idea?
Your operator<() is not consistent, as key1<key2 and key2<key1 could both be true (example: key1={1,3,0}, key2={3,1,0}). You should give the member variables a precedence in comparison:
if (a < rhs.a) {
return true;
} else if (a == rhs.a) {
if (b < rhs.b) {
return true;
} else if (b == rhs.b) {
if (c < rhs.c) {
return true;
}
}
}
return false;
You could indeed use standard class std::tuple as the key.
Nevertheless the operator can be defined the following way
bool operator <( const Key &rhs ) const
{
return
( a < rhs.a ) ||
( !( rhs.a < a ) && ( b < rhs.b ) ) ||
( !( rhs.a < a ) && !( rhs.b < b ) && ( c < rhs.c ) );
};
That this operator would work all you need is that for the type of objects a, b, and c there would be defined operator < Of course for arithmetic types it is already defined.
In fact it is the same as
#include <tuple>
//...
bool operator <( const Key &rhs ) const
{
return std::tie( a, b, c ) < std::tie( rhs.a, rhs.b, rhs.c );
}
i have a class named Point (in an external library, i can't modify the code) that is used to represent a point in a 3d space:
int x = 0; int y = 0; int z = 0;
Point my_point p(x,y,z);
It overloads the == and != operators but not < or > ones. I need to store them in an efficient way (no double element, no repetition). I thought my data structure is set, but if i try to use, i get this error:
error: no match for ‘operator<’ in ‘__x < __y’
some advice?
Write a comparison operator, and instantiate the set with that:
struct ComparePoints
{
bool operator()( Point const& lhs, Point const& rhs ) const
{
if ( lhs.x != rhs.x ) {
return lhs.x < rhs.x;
} else if ( lhs.y != rhs.y ) {
return lhs.y < rhs.y;
} else {
return lhs.z < rhs.z;
}
}
};
std::set <Point, ComparePoints> mySet;
You can define a comparison functor and pass it as second template argument to std::set. See here, look at Compare. You can also define bool operator<(const Point& lhs, const Point& rhs), but if you cannot touch the class, this requires that the comparison can be implemented via the public interface of Point.
You can define operator < yourself. It doesn't have to be inside the Point class if x, y and z are available from Point's public interface.
bool operator<(const Point& lhs, const Point& rhs)
{
if( lhs.x != rhs.x ) return lhs.x < rhs.x;
if( lhs.y != rhs.y ) return lhs.y < rhs.y;
return lhs.z < rhs.z;
}