C++ can I have operator<=> and operator== in a class with different logic?

130 Views Asked by At

I created a class in C++ with an implementation for operator<=> and operator==. Their logic is different: operator<=> compares based on one field, and operator== compares based on another field. I added a Student to a set, then called find on that set with a Student that wasn't in the set.

#include <iostream>
#include <set>
#include <string>

using namespace std;

class Student {
  private:
    string name;
    int age;

  public:
    Student (string p_name, int p_age) : name {p_name}, age {p_age} {};
    // Students are ordered in respect to their age
    auto operator<=>(const Student& rhs) const {
        return age <=> rhs.age;
    }
    // A student is equal to another if they have the same name
    bool operator==(const Student& rhs) const {
        return name == rhs.name;
    }
};


int main() {
  Student john {"john", 10};
  Student lucas {"lucas", 10};
  set<Student> my_set;
  my_set.insert(john);
  std::cout << std::boolalpha;
  std::cout << "Does my_set contain lucas? " << my_set.contains(lucas) << std::endl;
}

I want my_set.contains(lucas) to return false, instead, it returns true.

1

There are 1 best solutions below

0
Nicol Bolas On

Operators are just function calls; you can have them do anything you want, within the limitations of the language. It is within your rights to make your code as incoherent as you like.

However, once you give such incoherent code to some other code (like, say, the standard library), you are now at the mercy of that other code's expectations. And those expectations tend to be "things do what they say they do".

It is therefore always best to make operators do what they are supposed to do. If for some values a and b such that operator<=>(a, b) != 0, then operator==(a, b) == false too. That will be the expectation of pretty much any code not under your control which intends to use either of these operators.

std::set<T> is an associative, ordered container; this means that it puts the T items in an order and maintains this order automatically as items are added or removed. The ordering is defined by a comparison object given to the template, which defaults to std::less<T> (aka: use operator<). Since operator< is based on operator<=>, that is what is used to define comparisons.

All comparisons. Two values a and b are equal when comp(a, b) == comp(b, a). Since comp here is std::less<T>, it will always use operator< to determine equality. That is, two objects are equal if neither is less than the other.

You cannot make std::set use operator== for this (it only takes one comparison function). Nor would set work if you did, since the internal structure of the type presumes that equivalence is based on the comparison function's ordering. That is, the reason why set::find is O(log(n)) is because all of the objects are put into an order based on the comparison function.

So not only is what you want just bad code, it is code that could never work with any associative container. If you want to write incoherent code, you can, but the consequences of writing incoherent code are that the rest of the world expands and demands coherence.