52

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 <?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Eyzuky
  • 1,843
  • 2
  • 22
  • 45
  • 1
    I don't think c++ infers this automatically and probably (don't shoot me if I'm wrong) will compare the pointer values maybe if the operator is not implemented yet? – Titulum May 23 '17 at 08:14
  • 1
    I feel very cozy here - I thought I am only one who wonder about it. Thank for posting this great question. It would be nice if the answer also elaborates on its whole family e.g. `!=` `<=` `>=` with a reason why it is (not) - in a language designer perspective. – javaLover May 23 '17 at 08:15
  • 5
    @Titulum for user defined type, if the operator is not defined the compiler will raise an error. The operators don't have default behaviour like ``==`` would compare addresses. – nefas May 23 '17 at 08:18
  • 6
    If you have `operator==` and `operator<` then you can have the rest with the standard library: [`std::rel_ops`](http://en.cppreference.com/w/cpp/utility/rel_ops/operator_cmp) – Rakete1111 May 23 '17 at 08:28
  • 67
    Even in mathematics your logic only holds for totally ordered sets and not for [partially ordered sets](https://en.wikipedia.org/wiki/Partially_ordered_set). – CodesInChaos May 23 '17 at 08:45
  • 3
    `operator<` may not even return bool (or a type allowing `operator&&`). – Walter May 23 '17 at 13:23
  • You could write a `bool operator== (const T& x, const T&y) { return !(x – Davislor May 23 '17 at 15:56
  • 13
    Suppose I have a type of sets upon which I define "less than" as "is a proper subset". So {1} is less than {1, 2}. {3} is not less than {1, 2} and {1, 2} is not less than {3}, so your theory is that {3} == {1, 2} ? – Eric Lippert May 23 '17 at 18:04
  • From what I've observed C++ operator overloading is very cumbersome and not intuitive at all so I wouldn't count on the compiler making such or any other complex assumptions (involving the use of overloaded operators). I feel like operator overloading in C++ is implemented on a very basic level. – AnArrayOfFunctions May 24 '17 at 16:36
  • @EricLippert: While it's not what the OP intends I think, I don't know if your example is as strong as it could be (since in number theory, that may very well be a valid comparison depending on the problem -- think modulo arithmetic). – tonysdg May 25 '17 at 15:36
  • 1
    @tonysdg: OK, suppose I have a type of *types* upon which I define "less than" as "is a subtype of". So the theory is that since int is not less than string, and string is not less than int, then int is equal to string? I can do this all day. There are a great many realistic less-than relations that are on a lattice, not a totally-ordered set. – Eric Lippert May 25 '17 at 15:54

13 Answers13

70

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<
  2. 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;
}
  • 2
    error: overloaded 'operator==' must have at least one parameter of class or enumeration type – aschepler May 23 '17 at 11:56
  • Just for the record - I am aware it is an easy task (actually easy as defining < operator) but I am implementing a library with an API that I get objects as input that only defined < operator. – Eyzuky May 23 '17 at 19:18
  • 1
    @MSalters explained elsewhere that "operator== cannot be automatically defined in terms of operator<" is a fallacy, see explanation on other answer: https://stackoverflow.com/questions/44129275/can-i-use-operator-if-i-only-implemented-operator-in-c#comment75284970_44130473 – Blaisorblade May 23 '17 at 23:56
  • 4
    "`operator<` isn't required to compare its arguments" Strictly, no, the compiler can't enforce that. But doing anything else would be throwing Principle of Least Surprise out the window, driving a taxi over it, and flushing the remains down the toilet. **Don't do it.** – jpmc26 May 25 '17 at 00:49
  • 1
    @jpmc26 I completely agree, but some people see an operator and think "Hey, that'll look great in [insert strange situation here]". Operator functions `operator<<` and `operator>>` are overloaded in the standard library for streams, for example. I personally don't like it, but people are used to it and accept it now. –  May 25 '17 at 00:59
48

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

phuclv
  • 37,963
  • 15
  • 156
  • 475
  • 21
    The first statement is logically flawed. **IF** `operator<` is defined, **then** it can be used to automatically define `operator==`. You can't invert that. `complex` can have a non-automatic definition of `operator==`, and that says nothing about its `operator<`. – MSalters May 23 '17 at 11:17
  • 1
    @MSalters AFAIK it can only automatically be defined [when you `#include `](https://en.wikibooks.org/wiki/C%2B%2B_Programming/Operators/Operator_Overloading#Relational_operators). [Are any C++ operator overloads provided automatically based on others?](https://stackoverflow.com/q/32816843/995714) – phuclv May 23 '17 at 11:22
  • 1
    Those are other conditions. I was specifically calling out the logic in the first sentence, which states that `T==T` cannot be derived from `T – MSalters May 23 '17 at 11:29
  • 1
    @MSalters the `complex` is an example for *unorderness*, not that I say it has `operator<` – phuclv May 23 '17 at 14:17
  • @MSalters is right. Not all types are ordered, but that would still allow inferring `==` from `<` when provided, per se. – Blaisorblade May 23 '17 at 23:56
  • 1
    @Blaisorblade There are cases where doing so would lead to unexpected (and therefore dangerous) behavior, such as Eric's set example. This seems reason enough not to do it. – stewbasic May 24 '17 at 00:38
  • @stewbasic Thanks, that's actually a good argument! One particular bad argument was all we were debating. – Blaisorblade May 24 '17 at 00:45
18

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.

Shitao Zhou
  • 181
  • 4
  • 1
    You could also just have a strict weak order and have "equality" mean "is incomparable to." – templatetypedef May 23 '17 at 16:15
  • For what it's worth, if you had the `<=` operator, then you could define equality even for partially ordered sets using `(a <= b) && (b <= a)`, right? – yoniLavi May 23 '17 at 20:22
  • 1
    @yoniLavi, suppose in a complex number class, `operator<=` was defined to just compare the magnitudes of the the two arguments. – The Photon May 24 '17 at 16:10
  • @templatetypedef Yes, you can. If you want to define equality to mean "incomparable to" for your data type, you are welcome to do so. However, it would not be intuitive for the compiler to implicitly make this assumption for you on all classes without being instructed to do so. Most people when they write "equal to" actually mean "equal to" – Cort Ammon May 24 '17 at 17:56
  • @ThePhoton, well I suppose that would be as reasonable an abuse of notation as any, for when only the magnitude matters. I expect that the developer then would either explicitly define equality in the usual way, or indeed prefer to infer the equality operator based on the magnitude. Either might make sense, depending on the use-case. – yoniLavi May 24 '17 at 22:48
12

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.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Jonas
  • 6,915
  • 8
  • 35
  • 53
12

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:

Arne Vogel
  • 6,346
  • 2
  • 18
  • 31
6

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) {
nefas
  • 1,120
  • 7
  • 16
6

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Marek R
  • 32,568
  • 6
  • 55
  • 140
6

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • boost::operators is great for this - and it doesn't depend on lots of the rest of boost. However I think the way this answer is set out makes it look a lot harder to use than it really is - the first block of code is already done for you by boost - but the answer doesn't make that clear - and given the number of answers here one might not take the time to understand that - which is a shame because its a good solution. – ROX May 24 '17 at 11:06
5

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)

Community
  • 1
  • 1
xiaobing
  • 378
  • 4
  • 16
4

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.

Shadi
  • 1,701
  • 2
  • 14
  • 27
  • That `using` will then cause overload ambiguities all over the place. That’s why they fell out of use and never gained traction,and why Boost came up with another approach. – JDługosz May 23 '17 at 20:57
3

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

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Andrew Kashpur
  • 736
  • 5
  • 13
  • 2
    well if these are not the same, does not this mean that compiler developers may not and would not implicitly substitute one with another ? – Andrew Kashpur May 23 '17 at 08:37
2

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.

Foo Is Bar
  • 31
  • 4
1

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;
       }
};
Prithwish Jana
  • 317
  • 2
  • 11