Home > Enterprise >  Systematic approach of how to think about operator overloading
Systematic approach of how to think about operator overloading

Time:08-16

I'm learning how to do operator overloading and trying to come up with a systematic approach of how to know number of arguments an overloaded operator should take in and if the function should be constant.

I know the system is not perfect and doesn't catch all the edge cases, but I'm thinking that will work itself out with some logical thinking in the end.

What I'm looking for is if this systematic approach is catching the majority of cases, or am I missing something important?

Systematic approach

Important: Think logically through the entire process regarding if the input paramters can be const, * or &.

  1. Is the operator overloader a member function and NOT a stream?* It should take 1 argument which is the value/object to the right of the operator, UNLESS it is a unary operator then it should take 0 arguments. Does it alter the class object? If it does, add const to the end of it.
  2. Is the operator overloader a member function and ALSO a stream?* It should take 2 arguments, first a reference to the stream object and secondly (most likely) a reference to the class object. Does it alter the class object? If it does, add const to the end of it.
  3. Is the operator overloader NOT a member function?* In that case it should take 2 arguments which is the value/object to the left and right of the operator, UNLESS it is a unary operator then it should take 1 argument. No const at the end needed.

Below is the header file that I based the system on.

Vector.h

#pragma once

#include <iostream>

class Vector
{
public:

  Vector(double x = 0.0, double y = 0.0);
  
  Vector& operator =(Vector const& other);
  Vector& operator-=(Vector const& other);
  Vector& operator*=(double other);
  Vector& operator/=(double other);

  Vector operator-() const;
  
  bool operator==(Vector const& other) const;
  bool operator!=(Vector const& other) const;

  double operator*(Vector const& rhs) const;
  
  double length() const;

  friend std::ostream& operator<<(std::ostream& os, Vector const& other);
  friend std::istream& operator>>(std::istream& is, Vector& other);
  
private:
  
  double x;
  double y;
};

Vector operator (Vector const& lhs, Vector const& rhs);
Vector operator-(Vector const& lhs, Vector const& rhs);
Vector operator*(Vector const& lhs, double rhs);
Vector operator*(double lhs, Vector const& rhs);
Vector operator/(Vector const& lhs, double rhs);

CodePudding user response:

You got one thing wrong:

friend std::ostream& operator<<(std::ostream& os, Vector const& other);

You say this is written with two explicit arguments "because one of them is a stream." But actually it takes two explicit arguments because the friend keyword on the front implies this is a free function, not a member function. So it's covered by your third rule, and your second rule should be deleted entirely.

CodePudding user response:

Its both less and more diverse than you sketched.

More diverse because your systematic approach misses some operators. operator() and, since C 23, operator[] can have arbitrary number of arguments. and -- have an argument merely to distinguish between post and pre de/increment.

Less diverse, because your confusion seems to be mainly related to operators declared as friends defined inside the class definition. And friend / free function or member and number of arguments isn't really specific to operator overloading.

Whether a free function is a friend and defined in the class definition has some impact on ADL. Though, operators as friends are not much different than other free functions as friends. For example:

struct foo {
    friend void do_something(foo) {}
};

struct bar {};
void do_something(bar) {}

int main() {
    do_something(foo{});
    do_something(bar{});
}

Two classes, foo and bar, and for both there is a free function called do_something. It is important to note that the do_something defined in foo is not a member of foo. I may look rather similar to a member function definition but it is none. ADL aside (which is the reason we can actually call do_something(foo{})), the only reason we would prefer making do_something a friend is when it needs access to private members. And thats the same for operator overloads.

Now consider the difference between a free function and a member function:

struct moo {
    int x;
    void print_x() const { std::cout << x; }
};

void print_x(const moo& m) { std::cout << m.x; }

They both do exactly the same thing. Both need an object to be called. One is called like this m.print_x(); the other is called like this print_x(m);.

Ergo, the number of parameters does not depend on whether it is a friend of the class. A member function needs one argument less than a free function that does the same.

For the rest I refer you to https://en.cppreference.com/w/cpp/language/operators. Most importantly it has a list that tells you (among other things) which operators can be overloaded as free function and which operators can only be overloaded as members. For those that can be free functions you need to decide: Free function or member. If you go for free function and it needs access to private members, you must make it a friend.

Overloads for std::ostreams << are somewhat special. Or actually not that special, you just need to consider that you are overloading an operator of std::ostream not one of your class. Because you cannot add a member to std::ostream such overloads can only be free functions.

Regarding const the usual applies. If it can be const, make it const.

  • Related