c++ less operator overload, which way to use?

37,167

Solution 1

They are essentially the same, other than the first being non-const and allowing you to modify itself.

I prefer the second for 2 reasons:

  1. It doesn't have to be a friend.
  2. lhs does not have to be a Record

Solution 2

The best way to define the less operator is:

struct Record{
    (...)
    const bool operator < ( const Record &r ) const{
        return ( num < r.num );
    }
};

Solution 3

Welcome to where we have even more options.

  //version 1
  bool operator <(const Record& rhs)
  {
     return this->num>rhs.num;
  }

this one is wrong, it should read:

  //version 1
  bool operator <(const Record& rhs)const
  {
     return this->num>rhs.num;
  }

as you want the left hand side to be const-qualified as well.

  //version 2
  friend bool operator <(const Record& lhs, const Record& rhs) //friend claim has to be here
  {
     return lhs->num>rhs->num;
  }

this one is symmetric. So suppose you have a struct Bar with an operator Record.

Then

Record rhs;
Bar lhs;
assert( lhs < bar );

the above works with a symmetric case, but not with a member function version.

The friend in class version is an operator that can only be found via Koenig lookup (Argument Dependent Lookup). This makes it very useful for when you want a symmetric operator (or one where the type is on the right, like ostream&<<*this) bound to a specific template class instance.

If it is outside of the class, it has to be template function, and a template function does overloading differently than a non-template function does; non-template functions permit conversion.

template<class T>
struct point {
  T x ,y;
  point operator-(point const& rhs)const{
    return {x-rhs.x,y-rhs.y};
  }
  friend point operator+(point const& lhs, point const& rhs) {
    return {lhs.x+rhs.x, lhs.y+rhs.y};
  }
};
template<class T>
point<T> operator*( point<T> const& lhs, point<T> const& rhs ) {
  return {lhs.x*rhs.x, lhs.y*rhs.y};
}

here - is asymmetric, so if we have a type that converts to a point<int> on the left, - won't be found.

+ is symmetric and a "Koenig operator", so it is a non-template operator.

* is symmetric, but is a template operator. If you have something that converts-to-point, it won't find the * overload, because deduction will fail.

  //version 3
  inline bool operator <(const Record& lhs, const Record& rhs)
  {
     return lhs->num>rhs->num;
  }

this is similar to the template above, but here that problem doesn't occur. The difference here is that you can get the address of this function outside of the class, while the "koenig operator<" you wrote can only be found via ADL. Oh, and this isn't a friend.

adds in

 auto operator<=>(const Record&)=default;

where we use the spaceship operator <=> to define ordering automatically.

This will use the ordering of both c and num to produce the required result.

Much like the rule of 5, you should seek to make =default here work correctly. Having state that < ignores is a bad smell, and so is entangling different parts of your state.

Share:
37,167

Related videos on Youtube

user268451
Author by

user268451

Updated on April 27, 2021

Comments

  • user268451
    user268451 about 3 years

    For example: in a C++ header file, if I defined a struct Record and I would like to use it for possible sorting so that I want to overload the less operator. Here are three ways I noticed in various code. I roughly noticed that: if I'm going to put Record into a std::set, map, priority_queue, … containers, the version 2 works (probably version 3 as well); if I'm going to save Record into a vector<Record> v and then call make_heap(v.begin(), v.end()) etc.. then only version 1 works.

      struct Record
      {
          char c;
          int num;
    
          //version 1
          bool operator <(const Record& rhs)
          {
             return this->num>rhs.num;
          }
    
          //version 2
          friend bool operator <(const Record& lhs, const Record& rhs) //friend claim has to be here
          {
             return lhs->num>rhs->num;
          }
      };
    

    in the same header file for example:

          //version 3
          inline bool operator <(const Record& lhs, const Record& rhs)
          {
             return lhs->num>rhs->num;
          }
    

    Basically, I would like to throw the questions here to see if someone could come up with some summary what's the differences among these three methods and what are the right places for each version?

    • Cheers and hth. - Alf
      Cheers and hth. - Alf over 12 years
      could you please post complete example programs for each case that Does Not Work
    • Mooing Duck
      Mooing Duck over 12 years
      ideone.com/issBj all scenarios work on this compiler
    • user268451
      user268451 over 12 years
      sorry, I didn't finish editing the question first. Now,it should be complete
  • K-ballo
    K-ballo over 12 years
    Actually one should always favour out-of-class, so that conversions on both left and right types apply (and not just the right one). If possible to make it a non-friend then even better yet, since it decouples the operator from the class implementing it just in terms of the public interface.
  • user268451
    user268451 over 12 years
    I think the 2nd version, if you claim the operator < inside the class definition, you have to claim it as a friend of the class
  • Pubby
    Pubby over 12 years
    @user268451 It doesn't have to be a friend. It actually is best if it isn't one, as it leads to better encapsulation.
  • vpalmu
    vpalmu over 12 years
    You don't want conversions on comparison operators unless you're some kind of number!
  • Sjoerd
    Sjoerd over 12 years
    This does not allow conversions on the left hand side, so definitely is not the best way.
  • Sjoerd
    Sjoerd over 12 years
    @Pubby8 If you want to define a free function from inside a class, you'll have to add friend. This has nothing to do with accessing the private members.
  • Kashyap
    Kashyap over 12 years
    @Joshua that's not always true, if some container (map, sorted-set, ...) needs it you might implement it for "non-number" classes.
  • vpalmu
    vpalmu over 12 years
    Really, convert one type of collection to another implicitly?
  • g24l
    g24l over 9 years
    The path of ipmlicit conversions is dangerous and thy shall not walk