Could I use operator == if I only implemented operator <?

C++Operator Overloading

C++ Problem Overview


I have implemented operator< for a certain object. Logically, if !(a < b) and !(b < a) it means a == b.

Is this inferred automatically? Can I use == if I only implement <?

C++ Solutions


Solution 1 - C++

C++ cannot infer this automatically for a couple of reasons:

  1. It doesn't make sense for every single type to be compared with operator<, so the type may not necessarily define a operator<.
  • This means that operator== cannot be automatically defined in terms of operator<
  1. operator< isn't required to compare its arguments. A programmer can define operators for their types to do almost anything to their arguments.
  • This means that your statement about !(a < b) && !(b < a) being equivalent to a == b may not necessarily be true, assuming those operators are defined.

If you want an operator== function for your types, just define one yourself. It's not that hard :)

// For comparing, something like this is used
bool operator==(const MyType& lhs, const MyType& rhs)
{
    // compare (or do other things!) however you want
}

// ... though it's not the only thing you can do
//  - The return type can be customised
//  - ... as can both of the arguments
const MyType& operator==(int* lhs, const MyType* const rhs)
{
    return lhs;
}

Solution 2 - C++

It cannot infer == from < because not all types are ordered, like std::complex. Is 2 + 3i > 1 + 4i or not?

Moreover even in types that are normally ordered you still can't infer the equality from > or <, for example IEEE-754 NaN

double n = std::numeric_limits<double>::quiet_NaN();

std::cout << "NaN == NaN: " << (n == n) << '\n';
std::cout << "NaN < NaN: " << (n < n) << '\n';
std::cout << "NaN > NaN: " << (n > n) << '\n';
std::cout << "NaN != NaN: " << (n != n) << '\n';

They'll all return false except the last one

Solution 3 - C++

No. This method works well on number-like objects that is called totally ordered. For all kinds of set/class, no one can guarantee this relation. Even no one can guarantee a operator < would compare something.

So == is nothing else than ==. You may implement == by < but this doesn't work for everyone and C++ standards won't do it for you.

Solution 4 - C++

C++ does not infer this automatically. For operator>, operator<= and operator>=, you could use std::rel_ops; this requires only operator<. However, it does not provide operator== in terms of operator<. You can do this yourself like this:

template <class T>
bool operator==(T const& lhs, T const& rhs)
{
    return !((lhs < rhs) or (rhs < lhs));
}

Note that: !((lhs < rhs) or (rhs < lhs)) and !(lhs < rhs) and !(rhs < lhs) are equivalent, mathematically.

Solution 5 - C++

> Logically, if !(a < b) and !(b < a) it means a == b. Does c++ infer > this automatically? Can I use == if I only implemented

To put what others have stated in mathematical terms: Assuming that you have an operator < that returns bool and defines a strict weak order, and you implement operator == as returning !(a < b) && !(b < a), then this operator defines an equivalence relation consistent with the given strict weak order. However, C++ neither requires operator < to define a strict weak order, nor operator == to define an equivalence relation (although many standard algorithms such as sort may implicitly use these operators and require a strict weak order rsp. equivalence relation).

If you want to define all the other relational operators based on and consistent with your operator <'s strict weak order, Boost.Operators may save you some typing.

Because it's so easy to misuse an operator < that does not meet the standard algorithm's requirements, e.g. by accidentally using it via std::sort, std::lower_bound etc., I recommend to define operator < either as a strict weak order or not at all. The example CodesInChaos gave is a partial order, which does not meet the "transitivity of incomparability" requirement of a strict weak order. Therefore, I'd recommend calling such a relation by a different name, e.g. bool setLess(const MySet &, const MySet &).

Sources:

Solution 6 - C++

The compiler doesn't infer == from <.

You can check that with a simple example:

#include <iostream>

struct A {
    A(int r):i{r}{}
    int i;
};

bool operator<(A const & a1, A const& a2) {
    return a1.i < a2.i;
}

int main(int argc, char* argv[]) {
    A a1{2};
    A a2{3};
    if(a1 == a2) {
        std::cout << "equals\n";
    }
    return 0;
}

GCC gives you this error:

main.cpp:20:11: error: no match for 'operator==' (operand types are 'A' and 'A')

     if(a1 == a2) {

Solution 7 - C++

There are templates defined in the std::rel_ops namespace which are auto-defining missing operators.

It doesn't define an equality operator based on the less operator as you wish.

Still this is quite useful; if you define the less operator and equality operator you will have the other comparison operators for free.

Solution 8 - C++

As many have stated, no you cannot, and no the compiler should not.

This doesn't mean it shouldn't be easy to go from a < to == and the whole myriad.

boost::operators attempts to make it easy. Use it and done.

If you want to do it yourself, it also only takes a little bit of code to reimplement what boost provides you:

namespace utility {
  namespace details {
    template<class...>using void_t=void;
    template<template<class...>class Z, class, class...Ts>
    struct can_apply:std::false_type{};
    template<template<class...>class Z, class...Ts>
    struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:std::true_type{};
  }
  template<template<class...>class Z, class...Ts>
  using can_apply = ::utility::details::can_apply<Z,void,Ts...>;
}

namespace auto_operators {
  template<class T, class U>
  using less_r = decltype( std::declval<T const&>() < std::declval<U const&>() );
  template<class T, class U>
  using can_less = ::utility::can_apply<less_r, T, U>;

  struct order_from_less {
    template<class T, class U>
    using enabled = std::enable_if_t<
      std::is_base_of<order_from_less, T>{}
      && std::is_base_of<order_from_less, U>{}
      && can_less<T, U>{},
      bool
    >;
    template<class T, class U>
    friend enabled<U,T>
    operator>(T const& lhs, U const& rhs) {
      return rhs < lhs;
    }
    template<class T, class U>
    friend enabled<U,T>
    operator<=(T const& lhs, U const& rhs) {
      return !(lhs > rhs);
    }
    template<class T, class U>
    friend enabled<T,U>
    operator>=(T const& lhs, U const& rhs) {
      return !(lhs < rhs);
    }
  };
  struct equal_from_less:order_from_less {
    template<class T, class U>
    using enabled = std::enable_if_t<
      std::is_base_of<order_from_less, T>{}
      && std::is_base_of<order_from_less, U>{}
      && can_less<T, U>{} && can_less<U,T>{},
      bool
    >;
    template<class T, class U>
    friend enabled<U,T>
    operator==(T const& lhs, U const& rhs) {
      return !(lhs < rhs) && !(rhs < lhs);
    }
    template<class T, class U>
    friend enabled<U,T>
    operator!=(T const& lhs, U const& rhs) {
      return !(lhs==rhs);
    }
  };
}

The above only has to be written once, or equivalent cose gotten from #include boost.

Once you have boost, or the above, it is as simple as something like:

struct foo : auto_operators::equal_from_less {
  int x;
  foo( int in ):x(in) {}
  friend bool operator<( foo const& lhs, foo const& rhs ) {
    return lhs.x < rhs.x;
  }
};

and foo now has all the ordering and comparison operators defined on it.

int main() {
  foo one{1}, two{2};
  std::cout << (one < two) << "\n";
  std::cout << (one > two) << "\n";
  std::cout << (one == two) << "\n";
  std::cout << (one != two) << "\n";
  std::cout << (one <= two) << "\n";
  std::cout << (one >= two) << "\n";
  std::cout << (one == one) << "\n";
  std::cout << (one != one) << "\n";
  std::cout << (one <= one) << "\n";
  std::cout << (one >= one) << "\n";
}

Live example.

The point of all of this is that C++ doesn't, as a language, assume that < means > and >= and == all make sense. But you can write a library that lets you take a type with < defined, and adding a trivial base class suddenly make all of those other operations defined with zero runtime cost.

Solution 9 - C++

The answer is NO, you just need a simple test

struct MyType{
    int value;
};

bool operator < (MyType& a, MyType& b)
{
    return a.value < b.value;
}

int main(int argc, char* argv[])
{
    MyType a = {3};
    MyType b = {4};
    if (a == b)
        std::cout << "a==b" << std::endl;
    if (a < b)
        std::cout << "a < b" << std::endl;
}

g++ 4.8.2 complains:

> main.cpp: In function ‘int main(int, char**)’: > > main.cpp:16:11: error: no match for ‘operator==’ (operand types are ‘MyType’ and ‘MyType’)

But there is something similar that works in C++, check this c++ concepts:Compare

it says: > equiv(a, b), an expression equivalent to !comp(a, b) && !comp(b, a)

Solution 10 - C++

In addition to other answers,

The compiler cannot even infer != from ==

struct MyType
{
    int value;
};

bool operator == (const MyType& a, const MyType& b)
{
    return a.value == b.value;
}

int main()
{
    MyType a = {3};
    MyType b = {4};
    if (a != b)    // (* compilation Error *) 
        std::cout << "a does not equal b" << std::endl;
}

It would be nice though if there is an option to tell the compiler that the rest of the rational operators apply to your class.

There is, as explained in some answers in the <utility> header something that can provide such functionality. you will need to add the following line at the beginning of the main:

using namespace std::rel_ops; 

However, using this approach is costly and will cause overload ambiguities all over the place as noted by JDługosz.

Solution 11 - C++

Consider the following example:

class point{

  unsigned int x;
  unsigned int y;

  public:
    bool operator <(const point& other){
      return (x+y) < (other.x+other.y);
    }

    bool operator == (const point& other){
      return (x==other.x) && (y==other.y);
    }
}

And then we have:

point a{1, 2};
point b{2, 1};

!(a < b), !(b < a) , but also !(a == b).

Solution 12 - C++

Kind of.
But you would need boost::operators

> Overloaded operators for class types typically occur in groups. If you > can write x + y, you probably also want to be able to write x += y. If > you can write x < y, you also want x > y, x >= y, and x <= y. > Moreover, unless your class has really surprising behavior, some of > these related operators can be defined in terms of others (e.g. x >= y > <=> !(x < y)). Replicating this boilerplate for multiple classes is > both tedious and error-prone. The boost/operators.hpp templates help > by generating operators for you at namespace scope based on other > operators you've defined in your class.

Solution 13 - C++

The answer is clear NO. There is no implicit way. C++ classes allow operators to be overloaded. So, your idea that logically, if !(a < b) and !(b < a) it means a == b. is correct. And, you can overload operators as below. For example, a Fraction class:

class Fraction {
    int num;
    int denom;
    . . .
    public:
    . . .
    bool operator < (const Fraction &other) {
        if ((this->num * other.denom) < (this->denom * other.num))
            return false;
        else
            return true;
       }
       bool operator == (const Fraction &other) (
           if (!(*this < other) && !(other < *this)) {
               return true;
           else
               return false;
       }
};

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEyzukyView Question on Stackoverflow
Solution 1 - C++user7881131View Answer on Stackoverflow
Solution 2 - C++phuclvView Answer on Stackoverflow
Solution 3 - C++Shitao ZhouView Answer on Stackoverflow
Solution 4 - C++JonasView Answer on Stackoverflow
Solution 5 - C++Arne VogelView Answer on Stackoverflow
Solution 6 - C++nefasView Answer on Stackoverflow
Solution 7 - C++Marek RView Answer on Stackoverflow
Solution 8 - C++Yakk - Adam NevraumontView Answer on Stackoverflow
Solution 9 - C++xiaobingView Answer on Stackoverflow
Solution 10 - C++ShadiView Answer on Stackoverflow
Solution 11 - C++Andrew KashpurView Answer on Stackoverflow
Solution 12 - C++Foo Is BarView Answer on Stackoverflow
Solution 13 - C++Prithwish JanaView Answer on Stackoverflow