Systematic approach of how to think about operator overloading - c++

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

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.

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

How to declare operator/ overload function to operate on a const variable and a non-const variable?

I have the following code
#pragma once
#include "material.h"
#include "../Math/vector.h"
#include <cmath>
class LambertianMaterial : public Material
{
public:
LambertianMaterial(Vector rho);
Vector brdf(Vector wi) const
{
return mRho / M_PI; // Error here
}
private:
Vector mRho;
};
In the line corresponding to the return statement of brdf I am getting the following error
Invalid operands to binary expression ('const Vector' and 'double')
In the class vector I have declared the operator/ like
Vector operator/(const float a);
I was thinking of redefining the method to
friend Vector operator/(const Vector& v, const float& a);
Is this a good way of doing it or is there a way so that the current definition of the operator accounts for the const Vector case?
You could make it a const member function, which could be applied for const and non-const object, if it won't (and it shouldn't) modify any non-static member variables.
Vector operator/(const float a) const;
As you thought, making it non-member function (and declared as friend if necessary) could do the work too. IMO I prefer to it for operator/. See Operator overloading : member function vs. non-member function? for more informations.

Non-friend single-line operator + overload based on operator +=

Let's say I have a class:
class A
{
//...
};
With well defined operator +=:
A& operator +=(const A&) {return *this;} //class without members
So let's try to overload operator+ also (as non-friend). I don't want to use class name to call constructor of temporary object (kinda want this make generic code):
A operator +(const A& other) const
{
return auto(*this)(*this) += other; //error: invalid use of auto
// /\/\/\/\/\ /\
// type of *this ||
// constructor call
}
auto is no good here. Let's try decltype.
A operator +(const A& other) const
{
return decltype(*this)(*this) += other; //error: 'A& A::operator+=(const A&)' discards
// /\/\/\/\/\/\/\ /\ qualifiers [-fpermissive] return
// type of *this || decltype(*this)(*this) += other;
// constructor call ^
}
This managed to get the type out of *this, but operator+ is declared const, so we got const A deduced (that's what I thought). So let's go on:
A operator +(const A& other) const
{
return typename std::remove_const<decltype(*this)>::type(*this) += amount;
//same error as previous
}
Now I got mindblown. Even thought I removed constness, it still discards
qualifier. Well, maybe that's because all I was doing was just CASTING. So stupid. To call a constructor I would have to generate code that (besides type) has ::Constructor (so I even tried to make an alias for constructor, but at this point I failed so hard). My broken brain gave up, but rest of my consciousness gave me an solution (which is generic in writing, so that's good):
// outside of class
template<class A>
inline A&& make_rvalue(A copy)
{
return std::move(copy);
}
// inside of class
A operator +(const A& other) const
{
return make_rvalue(*this) += other; // such generic code
}
That's what I ended with. But is there some trick that doesn't involve any other function call?
EDIT: I know classic methods of doing this, but what I search is described below:
operator is reduced to {return /*...*/;}
doesn't involve names of class methods or global functions
takes to account overloads with other types - it cannot take argument(s) by value, since class A != class B, argument int over const int& doesn't help much with Matrix class (but proxy operator that calls target operator with exchanged arguments is OK)
takes to account (possible) order of operation (x#y != y#x), where both should should have same return statement
return statement should be exacly the same for given operator# is every class that has overloaded operator +=
If you create a function like this:
template <typename T>
T add(T temp,const T &b)
{
temp += b;
return temp;
}
You can use it like this:
A operator+(const A& other) const { return add(*this,other); }
I'm going to go on record as saying this whole line of thinking/coding is going the wrong way. You already know that (in this case) the return type is A. Your code seems to be putting a lot of work into saying A in the most complex, indirect way possible.
A operator +(const A& other) const {
A ret {*this};
ret += other;
return ret;
}
Since you seem to really want to use C++11 features (even though in this case almost none of them provides any real help) I used a braced initializer, so it uses something from C++11. And no, this isn't one line, but it is readable.
Note that the same style is fine in generic code as well. For example, in a non-member overload of operator+, we might have something like this:
template <class T>
T operator+(T const &a, T const &b) {
T ret {a};
ret += b;
return ret;
}
This can be simplified a bit as well though:
template <class T>
T operator+(T ret, t const &b) {
ret += b;
return ret;
}
Again, we lose precisely nothing from the fact that older compilers can and will accept the code without problem.

My understanding of friend functions

Sometimes a non-member function may need access to the private members it's taking in as a agrument.
A friend function is a non-member function that gives private access to the classes it's friends with.
Class X{
int number;
public:
X& operator+=(const X& rhs);//a += b is a.operator(b)
friend int& operator+=(int& addthis, const X& rhs);//add this += b is I don't know what lol
}
X& operator+=(const X& rhs){
this->number = this->number + rhs.number;
}
int& operator+=(int& addthis, const X& rhs){
addthis = addthis + rhs.number;
return *this;
}
What I read is that if I wanted to do += to an object, object += int, just overload the += operator, but what if the int came before the object. Say int += object? Than I would have to write it as a friend function, and that's where I get a little lost. Why can't I just write int& operator+=(int& addthis, const X& hrs); as a member function? I assume a friend function can work with other classes since it's a non-member function, so it's not assigned to any specific class?
I'm sorry I just don't understand why I would use friend function over making it a member function. Really would appreciate any feedback, thank you.
You can enable the operator+= for int and other integral types via a user-defined conversion in your class X:
struct X{
int number;
operator int() const
{
return number;
}
//...
}
Of course, this doesn't give you exactly the same behaviour as your friend-approach, as other functions could also use the conversion. Nevertheless, I guess this is what you are looking for, as otherwise you would need to define as well operator-=, operator*= and so on.
As far as I understand, you can't have operator*, operator+ or operator+= or any of operator# where # is some math operator that returns something other than an instance of your class right in the definition of your class.
In real world, it would be quite odd to add an integer to an integer and get an apple as a result. The same should apply to classes in C++. A member function is, well, a property of the class, a function that represents whatever can be done to the object of this class. Operators are special. They represent operations that sort of can't be made easier (what will you use to code an addition operator using the + sign instead of operator overloading?). They represent the very basic operations and thus should return an instance of the class they're members of.
Consider you implemented operator overloading as member function and performed something like x += y where both x and y are objects of the class which has the operator += overloaded.
Compiler parses x += y as x.operator+=(y) which is fine as both operands x and y are of the same class which has the operator += overloaded.
Now consider an expression like 2 += y (this may be insane, but I am trying explain why operator overloading with friend functions may be at time beneficial over operator overloading with member functions)
Since, the expression 2 += y will be parsed by compiler as 2.operator+=(y), an error is raised. Maybe 2 may get converted to an object of the class using constructor for conversion; (Single argument constructors can be used for conversion) but that is not the point here.
Using friend function to overload the operator calls the above expression 2 += y as operator+=(2, y) which works fine.
The reason why the operator int += X has to be a free function is that the compiler only searches for applicable member operators in the lhs type, and not in the rhs type. Since int is not a type that can be extended, you can't define this operator as a member function.
You were almost there, just a few errors:
class X;
int operator+=(int addthis, const X& rhs);
class X{
int number;
public:
X& operator+=(const X& rhs);//a += b is a.operator(b)
friend int operator+=(int addthis, const X& rhs);//add this += b is I don't know what lol
};
X& X::operator+=(const X& rhs){
this->number = this->number + rhs.number;
return *this;
}
int operator+=(int addthis, const X& rhs){
addthis = addthis + rhs.number;
return addthis;
}
some forward declarations added
int passed by value instead of by reference
missing semicolon after class declaration
class instead of Class
return corrected
Note that X += int is a member function, while int += X is a free function, declared a friend of the class X.
Using a free function for int += X is the only solution that allows to use the operator as usual, like:
X x;
int i = 2;
i += x;
The friend modifier is just meant to expose private data. Normally, you wouldn't define a friend function as a non-member if it only depends on the private data of a class it can be defined as a member function for.
A better example would be ostream::operator<< or istream::operator>>.

Non-friend operator+ with two parameters in Wandevoorde&Jossutis' book

Learning Expression templates.
In Wandevoode and Jossutis's book Templates, the complete guide, section 18.2.3 The Operators, they define an operator+ with two arguments but not as a friend method.
template <typename T, typename R1, typename R2>
Array<T,A_Add<T,R1,R2> >
operator+ (Array<T,R1> const& a, Array<T,R2> const& b) {
return Array<T,A_Add<T,R1,R2> >
(A_Add<T,R1,R2>(a.rep(),b.rep()));
}
I am a beginner and hence insecure about what I know about C++.
Shouldn't this operator+ be a friend?
I tried
class A{
public:
explicit A(int x) : a(x){};
A& operator+(const A& x, const A& y){
A temp{x.a + y.a};
return temp;
};
private:
int a;
};
I am getting a binary 'operator+' has too many parameters.
Your "binary" operator + member function gives you the "too many parameters" error because it has three parameters: the x and y you specified, plus the implicit this parameter. This means it actually takes three arguments, which of course is impossible.
You can make a member function taking one explicit parameter and the implicit this, but in the opinion of many people, these binary operators such as add and subtract are better implemented outside the class, in the same namespace as the class, as free functions taking two explicit parameters. This works especially well if the data needed to fulfill the operation can be publicly accessed (otherwise you must use friend, which is OK if truly necessary).
To summarize for the next reader.
There seem to be three forms of overloading binary arithmetic operators.
First: A member method taking one parameter by const reference. The const tothe return value is used (according to this reference) to avoid (x+y)=z.
class A{
const A operator+(const A& other){
// ...
};
};
Second: A friend method, and therefore included in the definition of the class, taking two parameters.
class A{
friend A& operator+(const A& operand1, const A& operand2){
// ...
};
};
Third: A non-member method taking two parameters.
class A{
// ...
};
A operator+(const A& operand1, const A& operand2){
// ...
};

operator << must take exactly one argument

a.h
#include "logic.h"
...
class A
{
friend ostream& operator<<(ostream&, A&);
...
};
logic.cpp
#include "a.h"
...
ostream& logic::operator<<(ostream& os, A& a)
{
...
}
...
When i compile, it says:
std::ostream& logic::operator<<(std::ostream&, A&)' must take exactly one argument.
What is the problem?
The problem is that you define it inside the class, which
a) means the second argument is implicit (this) and
b) it will not do what you want it do, namely extend std::ostream.
You have to define it as a free function:
class A { /* ... */ };
std::ostream& operator<<(std::ostream&, const A& a);
A friend function is not a member function, so the problem is that you declare operator<< as a friend of A:
friend ostream& operator<<(ostream&, A&);
then try to define it as a member function of the class logic
ostream& logic::operator<<(ostream& os, A& a)
^^^^^^^
Are you confused about whether logic is a class or a namespace?
The error is because you've tried to define a member operator<< taking two arguments, which means it takes three arguments including the implicit this parameter. The operator can only take two arguments, so that when you write a << b the two arguments are a and b.
You want to define ostream& operator<<(ostream&, const A&) as a non-member function, definitely not as a member of logic since it has nothing to do with that class!
std::ostream& operator<<(std::ostream& os, const A& a)
{
return os << a.number;
}
I ran into this problem with templated classes.
Here's a more general solution I had to use:
template class <T>
class myClass
{
int myField;
// Helper function accessing my fields
void toString(std::ostream&) const;
// Friend means operator<< can use private variables
// It needs to be declared as a template, but T is taken
template <class U>
friend std::ostream& operator<<(std::ostream&, const myClass<U> &);
}
// Operator is a non-member and global, so it's not myClass<U>::operator<<()
// Because of how C++ implements templates the function must be
// fully declared in the header for the linker to resolve it :(
template <class U>
std::ostream& operator<<(std::ostream& os, const myClass<U> & obj)
{
obj.toString(os);
return os;
}
Now:
* My toString() function can't be inline if it is going to be tucked away in cpp.
* You're stuck with some code in the header, I couldn't get rid of it.
* The operator will call the toString() method, it's not inlined.
The body of operator<< can be declared in the friend clause or outside the class. Both options are ugly. :(
Maybe I'm misunderstanding or missing something, but just forward-declaring the operator template doesn't link in gcc.
This works too:
template class <T>
class myClass
{
int myField;
// Helper function accessing my fields
void toString(std::ostream&) const;
// For some reason this requires using T, and not U as above
friend std::ostream& operator<<(std::ostream&, const myClass<T> &)
{
obj.toString(os);
return os;
}
}
I think you can also avoid the templating issues forcing declarations in headers, if you use a parent class that is not templated to implement operator<<, and use a virtual toString() method.
Operator overloading includes member function overloading and non-member function overloading, which cannot be mixed. https://condor.depaul.edu/ntomuro/courses/262/notes/lecture3.html
If you define operator<< as a member function it will have a different decomposed syntax than if you used a non-member operator<<. A non-member operator<< is a binary operator, where a member operator<< is a unary operator.
// Declarations
struct MyObj;
std::ostream& operator<<(std::ostream& os, const MyObj& myObj);
struct MyObj
{
// This is a member unary-operator, hence one argument
MyObj& operator<<(std::ostream& os) { os << *this; return *this; }
int value = 8;
};
// This is a non-member binary-operator, 2 arguments
std::ostream& operator<<(std::ostream& os, const MyObj& myObj)
{
return os << myObj.value;
}
So.... how do you really call them? Operators are odd in some ways, I'll challenge you to write the operator<<(...) syntax in your head to make things make sense.
MyObj mo;
// Calling the unary operator
mo << std::cout;
// which decomposes to...
mo.operator<<(std::cout);
Or you could attempt to call the non-member binary operator:
MyObj mo;
// Calling the binary operator
std::cout << mo;
// which decomposes to...
operator<<(std::cout, mo);
You have no obligation to make these operators behave intuitively when you make them into member functions, you could define operator<<(int) to left shift some member variable if you wanted to, understand that people may be a bit caught off guard, no matter how many comments you may write.
Almost lastly, there may be times where both decompositions for an operator call are valid, you may get into trouble here and we'll defer that conversation.
Lastly, note how odd it might be to write a unary member operator that is supposed to look like a binary operator (as you can make member operators virtual..... also attempting to not devolve and run down this path....)
struct MyObj
{
// Note that we now return the ostream
std::ostream& operator<<(std::ostream& os) { os << *this; return os; }
int value = 8;
};
This syntax will irritate many coders now....
MyObj mo;
mo << std::cout << "Words words words";
// this decomposes to...
mo.operator<<(std::cout) << "Words words words";
// ... or even further ...
operator<<(mo.operator<<(std::cout), "Words words words");
Note how the cout is the second argument in the chain here.... odd right?
The key point is the logic:: before operator<< which is defined as a friend function.
logic:: is only added before the member function. I understand that this is similar to telling the compiler that this function is a member function and granting it corresponding permissions (such as accessing private functions).
In other words, just as #asaelr and #Morteza mentioned, "when defining a friend function you do not use the name of the class to scope the name of the friend function".
Hence, we should remove logic:: before operator<<.