Ternary comparison operator overloading - c++

How would one implement a ternary comparison operator to determine, for example, the boolean value of a < b < c?

Solution:
When coding a comparison, have the return type be a comparison object that can chain additional comparisons, but is implicitly convertible to a bool. This can even (kind of) work with types that weren't coded with this intent, simply by casting them to the comparison type manually.
Implementation:
template<class T>
class comparison {
const bool result;
const T& last;
public:
comparison(const T& l, bool r=true) :result(r), last(l) {}
operator bool() const {return result;}
comparison operator<(const T& rhs) const {return comparison(rhs, (result && last<rhs));}
comparison operator<=(const T& rhs) const {return comparison(rhs, (result && last<=rhs));}
comparison operator>(const T& rhs) const {return comparison(rhs, (result && last>rhs));}
comparison operator>=(const T& rhs) const {return comparison(rhs, (result && last>=rhs));}
};
A useful example:
#include <iostream>
int main() {
//testing of chained comparisons with int
std::cout << (comparison<int>(0) < 1 < 2) << '\n';
std::cout << (comparison<int>(0) < 1 > 2) << '\n';
std::cout << (comparison<int>(0) > 1 < 2) << '\n';
std::cout << (comparison<int>(0) > 1 > 2) << '\n';
}
Output:
1
0
0
0
Note: This was created by Mooing Duck, and a compiled, more robust example can be found on http://ideone.com/awrmK

Why do you need an operator?
inline bool RangeCheck(int a, int b, int c)
{
return a < b && b < c;
}
or:
#define RANGE_CHECK(a, b, c) (((a) < (b)) && ((b) < (c)))

Related

incorrect function call on overload operator

In the code, why is (10 != i) calling == instead of !=? The other two call !=
#include <iostream>
class Integer
{
int x;
public:
bool
operator== (const Integer &i)
{
std::cout << "==";
return x == i.x;
}
bool
operator!= (const Integer &i)
{
std::cout << "!=";
return x != i.x;
}
Integer (int t = 0) { x = t; }
};
int
main ()
{
Integer i;
std::cout << (i != i) << '\n'; // calls !=
std::cout << (i != 100) << '\n'; // calls !=
std::cout << (10 != i) << '\n'; // calls ==
}
Prior to C++20, you'd need to add two free functions for the comparison where the int is on the left-hand side:
bool operator==(int lhs, const Integer& rhs) {
return rhs == lhs;
}
bool operator!=(int lhs, const Integer& rhs) {
return rhs != lhs;
}
You should also make the member comparison operators const qualified:
class Integer {
public:
//...
bool operator==(const Integer &i) const { // note const
std::cout << "==";
return x == i.x;
}
bool operator!=(const Integer &i) const { // note const
std::cout << "!=";
return x != i.x;
}
//...
};
You could also remove the member operators to simplify things. Now the left-hand side int will be implicitly converted to Integer and then compared with the right-hand side:
class Integer {
int x;
public:
Integer(int t = 0) : x{t} {}
friend bool operator==(const Integer& lhs, const Integer& rhs) {
return rhs.x == lhs.x;
}
friend bool operator!=(const Integer& lhs, const Integer& rhs) {
return !(rhs == lhs);
}
};
Since you've tagged this C++20, you can let operator== do all the work. See Default comparisons. It'll be used for operator!= too.
class Integer {
int x;
public:
bool operator==(const Integer &i) const {
std::cout << "==";
return x == i.x;
}
Integer(int t = 0) : x{t} {}
};
... and it'll correctly show that it used operator== for all your != comparisons (and negated it).
More from Defaulted equality comparison:
A class can define operator== as defaulted, with a return value of bool. This will generate an equality comparison of each base class and member subobject, in their declaration order. Two objects are equal if the values of their base classes and members are equal. The test will short-circuit if an inequality is found in members or base classes earlier in declaration order.
Per the rules for operator==, this will also allow inequality testing
This means that you will in fact get away with the below only since C++20:
class Integer {
int x;
public:
bool operator==(const Integer &i) const = default;
Integer(int t = 0) : x{t} {}
};
or even better, get all the comparison operators for free by defaulting the spaceship operator <=>:
class Integer {
int x;
public:
auto operator<=>(const Integer &i) const = default;
Integer(int t = 0) : x{t} {}
};
There are two new additions to C++20 that made this possible (note that your code doesn't compile in earlier standard versions).
Compiler will attempt to replace a != b with !(a == b) if there is no suitable != for these arguments.
If there is no suitable a == b, compiler will attempt b == a as well.
So, what happens - compiler first notices that it doesn't know how to compare 10 != i, so it tries !(10 == i). There is still no suitable comparison, so it tries !(i == 10) and it can finally be done using your implicit constructor to convert 10 to Integer.
It can be easily verified by adding more info to debug print:
bool
operator== (const Integer &i) const
{
std::cout << x << "==" << i.x << ' ';
return x == i.x;
}
will print 0==10 1 in the last line (see it online).
As noticed in comments, you don't even need operator !=, due to aforementioned behaviour C++20 compiler will automatically convert any such call to operator ==.

compare 3 or more objects

Currently, to compare 3 or more integers, We do it this way. (a < b) && (b < c). I know that, a < b < c translates to (a < b) < c and compares boolean with integer. Is there any way such that, I can overload some operators on a custom Class to achieve continuous comparison? How does languages like python does this?
Update: According to accepted answer, I managed to write a piece of code. Have a look.
#include <iostream>
template <typename T>
class Comparator {
bool result;
T last;
public:
Comparator(bool _result, T _last) : result(_result), last(_last) {}
operator bool() const {
return result;
}
Comparator operator<(const T &rhs) const {
return Comparator(result && (last < rhs), rhs);
}
Comparator operator>(const T &rhs) const {
return Comparator(result && (last > rhs), rhs);
}
};
class Int {
int val;
public:
Int(int _val) : val(_val) {}
operator int() const {
return val;
}
Comparator<Int> operator<(const Int &rhs) {
return Comparator<Int>(val < int(rhs), rhs);
}
Comparator<Int> operator>(const Int &rhs) {
return Comparator<Int>(val > int(rhs), rhs);
}
};
int main() {
Int a(2), b(3), c(1), d(4), e(6), f(5);
std::cout << (a < b > c < d < e) << '\n';
// 2 < 3 > 1 < 4 < 6 > 5
return 0;
}
a < b < c is grouped as (a < b) < c.
If a or b are a type that you define, you could overload < for that type to return a proxy object, for which an overloaded < is also defined. That proxy object would contain the value of b along with the result of a < b.
It's some hassle, and will not make your code readable either since all C++ programmers know what a < b < c should do.
Python has its own syntax and interpreter.

Operator() crashing program on MSVC C++17 (2019)

The following code doesn't work with MSVC++ 2019, but it works on GCC compiler.
#include <set>
#include <string>
#include <iostream>
struct MyData {
MyData() {}
MyData(std::string keyA, std::string keyB) :keyA(keyA), keyB(keyB) {}
std::string keyA;
std::string keyB;
};
struct Compare {
bool operator() (const MyData& lhs, const MyData& rhs) const
{
if (lhs.keyA < rhs.keyA)
return true;
if (lhs.keyB < rhs.keyB)
return true;
else
// All else conditions would be false
return false;
}
};
int main()
{
std::set<MyData, Compare> s;
s.insert(MyData("Clark", "Alice"));
s.insert(MyData("Bob", "Alice"));
s.insert(MyData("Alice", "Bob"));
s.insert(MyData("Derek", "Clark"));
for (auto& i : s)
{
std::cout << i.keyA << ", " << i.keyB << std::endl;
}
}
Here's the error on MSVC:
While on GCC, it shows this output:
Alice, Bob
Bob, Alice
Clark, Alice
Derek, Clark
Process returned 0 (0x0) execution time : 0.971 s
Press any key to continue.
What's causing this error, and how to define the operator correctly?
Compare::operator() is not correct. It does not meet the criteria of strict weak ordering.
You can change it to:
bool operator() (const MyData& lhs, const MyData& rhs) const
{
if (lhs.keyA != rhs.keyA)
return (lhs.keyA < rhs.keyA)
return (lhs.keyB < rhs.keyB);
}
It can be further simplified using std::tie.
bool operator() (const MyData& lhs, const MyData& rhs) const
{
return std::tie(lhs.keyA, lhs.keyB) < std::tie(rhs.keyA, rhs.keyB);
}
Your comparator fails to adhere to the rules for strict weak ordering, that is to say it is possible to have the following situation:
a < b
b < c
a >= c
MSVC has evidently detected this and raised an assertion failure.
To fix this, you can change your comparator as follows:
bool operator() (const MyData& lhs, const MyData& rhs) const
{
if (lhs.keyA < rhs.keyA)
return true;
if (lhs.keyA == rhs.keyA && lhs.keyB < rhs.keyB)
return true;
return false;
}
Or, more compactly, use std::tie:
bool operator() (const MyData& lhs, const MyData& rhs) const
{
return std::tie (lhs.keyA, lhs.keyB) < std::tie (rhs.keyA, rhs.keyB);
}
I also got rid of the redundant else.

Do I have to overload every operator for a class to behave like one of its member variables?

Given a user defined type such as the following:
struct Word{
std::string word;
Widget widget;
};
Is there a way to make every overloaded operator of the class behave exactly the same as if it was just a string? Or do I have to implement the class the following way:
struct Word{
bool operator < (Word const& lhs) const;
bool operator > (Word const& lhs) const;
bool operator <= (Word const& lhs) const;
bool operator => (Word const& lhs) const;
bool operator == (Word const& lhs) const;
bool operator != (Word const& lhs) const;
//etc...
std::string word;
Widget widget;
};
making sure I account for every overloaded operation a string contains, and applying the behaviour to just the string value.
I would say your best option is to use std::rel_ops that way you only have to implement == and < and you get the functionality of all of them. Here's a simple example from cppreference.
#include <iostream>
#include <utility>
struct Foo {
int n;
};
bool operator==(const Foo& lhs, const Foo& rhs)
{
return lhs.n == rhs.n;
}
bool operator<(const Foo& lhs, const Foo& rhs)
{
return lhs.n < rhs.n;
}
int main()
{
Foo f1 = {1};
Foo f2 = {2};
using namespace std::rel_ops;
std::cout << std::boolalpha;
std::cout << "not equal? : " << (f1 != f2) << '\n';
std::cout << "greater? : " << (f1 > f2) << '\n';
std::cout << "less equal? : " << (f1 <= f2) << '\n';
std::cout << "greater equal? : " << (f1 >= f2) << '\n';
}
If you need a more complete version of this type of thing use <boost/operators.hpp>

Why should operator< be non-member function?

I remeber C++ Primer tells us operator< should be non-member function, and I always obey the rule. But now I want to know the reason.
I wrote following code:
#include <iostream>
using std::cout;
using std::endl;
struct Point1
{
int x, y;
Point1(const int a, const int b): x(a), y(b) { }
};
inline bool operator<(const Point1& lhs, const Point1& rhs)
{
return lhs.x < rhs.x || (lhs.x == rhs.x && lhs.y < rhs.y);
}
struct Point2
{
int x, y;
Point2(const int a, const int b): x(a), y(b) { }
bool operator<(const Point2& rhs)
{
return x < rhs.x || (x == rhs.x && y < rhs.y);
}
};
int main()
{
Point1 a(1, 2), b(1, 3);
cout << (a < b) << " " << (b < a) << endl;
Point2 c(2, 3), d(2, 4);
cout << (c < d) << " " << (d < c) << endl;
}
In this case, It seems they don't make difference and member function seems much simpler.
But in this case:
#include <iostream>
using std::cout;
using std::endl;
// Usually I write it for comparing floats
class Float1
{
long double _value;
public:
static const long double EPS = 1e-8;
Float1(const long double value): _value(value) { }
const long double Get() const { return _value; }
};
inline bool operator<(const Float1& lhs, const Float1& rhs)
{
return rhs.Get() - lhs.Get() > Float1::EPS;
}
inline bool operator<(const Float1& lhs, const long double rhs)
{
return rhs - lhs.Get() > Float1::EPS;
}
class Float2
{
long double _value;
public:
static const long double EPS = 1e-8;
Float2(const long double value): _value(value) { }
const long double Get() const { return _value; }
bool operator<(const Float2& rhs)
{
return rhs._value - _value > Float2::EPS;
}
bool operator<(const long double rhs)
{
return rhs - _value > Float2::EPS;
}
};
int main()
{
Float1 x(3.14);
Float2 y(2.17);
long double zero = .0;
cout << (x < zero) << " " << (zero < x) << endl;
//cout << (y < zero) << " " << (zero < y) << endl; Compile Error!
}
Both (x < zero) and (zero < x) work! (is long double converted to Float?)
But (zero < y) don't, because zero is not a Float.
You see, in first case, member function costs less code length, and in second case, non-member function makes comparing easier. So I want to know
In first case, should I use member function instead of non-member function?
Why C++ Primer suggests binary operators be non-member function?
Is there any other case that member function and non-member function make difference?
Thanks for helping!
I think the basic answer is that non-member functions play better with implicit conversion. So if you can write your binary operator as a non-member function, you should.
The question can be answered in different levels. In the highest level, from a design standpoint, operator< is a binary operator. It is no more of an operation on the left hand side that it is on the right. Member functions, on the other hand are bound to the first argument, they are an operation on the first type.
From a technical point of view, and going right to the C++ language, this comes down to what you already noticed: member functions are not symmetric with respect to the types. That lack of symmetry means that an operator< declared as a member function can only be applied when the left hand side is of the type that contains the member. No conversions can be applied before a member operator is called. On the other hand, because free functions are not more bound to the first argument than they are for the second, the free function operator< will be picked up by ADL whenever any of the two arguments is of the appropriate type, allowing the same conversions to the first and second arguments.
As a non-member the comparison operator works also for derived class (left hand side) arguments.
EDIT: and as #jonathan points out in a comment, it also allows value conversions, such as e.g. an int left hand side argument.
If constructors and conversion operators are not explicit, such conversions can allow meaningless and probably unintended code, e.g. comparing a T instance to 5.