Operator overloading is useful for defining user-defined functionality for built-in operators. The familiarity of the symbols and their judicious new definition adds to the convenience of reading and writing expressions. The following programming languages are included in the list of providers of this facility: Ada, C++, C#, Python, Ruby, Swift, and Kotlin. We use the C++ language for coding examples in this blog.
Let’s dive right in!
We’ll cover:
Operator overloading is the power of a programming language that allows the built-in operators (, , , etc.) to be overloaded for user-defined data types. The familiarity of the symbols for an expected behavior facilitates reading and writing. The cascading of multiple function calls is easy to understand in expressions.
Operator overloading is a powerful tool for expressing complicated invocations concisely.
One of the arguments against the facility of operator overloading is the possibility of irrelevant or inconsistent definitions. It is important that the developers defining the new behavior for the operators should keep in mind the general expectation of their usage. The overloaded version of the operator should not do any additional or irrelevant operations under the hood.
The new definition of familiar symbols should be judiciously created to avoid confusion.
For example, we perform on the objects/variables of the polynomial class. The overloaded operator, for this class, should sum up two polynomials without changing the operands, as expected by the definition of this operation in mathematics.
In all the coding examples, the term is output as , where is the coefficient, and is the exponent of the variable .
Zero to Hero in C++
C++ is a robust and flexible language commonly used for games, desktop, and embedded applications development. This Skill Path is perfect for beginners eager to learn C++ and embark on a programming journey. You’ll explore C++ fundamentals, starting with basic syntax and functionality to create programs, and then dive into more complex concepts like dynamic memory allocation in C++. The latter half focuses on C++ programming with a detailed overview of object-oriented programming (OOP), which includes classes in C++, data hiding in C++, encapsulation in C++, abstraction in C++, inheritance in C++, and polymorphism in C++. Hands-on practice with algorithms and data structures will empower you to write real-world programs confidently, paving your way as a C++ developer.
An object has attributes (also called the data members of the class) and methods (also called the member functions of the class). Functions and methods can be overloaded for the following reasons.
However, the operators cannot be overloaded for the third reason mentioned above. The number of parameters for the operators cannot be changed. It remains the same as defined by the language for primitive data types. A binary operator cannot be defined for less than or greater than two operands. Similarly, a unary operator cannot be defined for less than or greater than one operand. When we define a binary operator as a class method, we can use only one explicit parameter (neither less nor more) for the second operand. The first operand implicitly comes as the calling object.
The operators are overloaded as methods. In C++, the operator
keyword is used to overload an operator, and the symbol has to be from the permissible ones. Many operators in C++ can be overloaded.
The method can have any name following the rules of the language. Calling an operator is also a bit different from the method.
A method of an object is called using the member selection operator, .
, between the object name and the method name. The method parameters are passed inside the parentheses after the method name. Calling an operator does not require the use of a member selection operator and parentheses. The following simple implementation of class Polynomial
is used to demonstrate this difference.
// header file for cout and endl#include <iostream>// header file for abs()#include <cstdlib>// header file for vector<int>#include <vector>using namespace std;class Polynomial {vector<int> coef;public:Polynomial(const vector<int> & coefficients) : coef(coefficients) {}// Function to display a term of the polynomialvoid showTerm(int val) const {if (val != 0) {if (val > 0) {cout << " + ";} else {cout << " - ";}cout << abs(val);}}// Function to display the polynomialvoid show() const {int lim = coef.size() - 1;for (int i = lim; i >= 0; --i) {// Showing the coefficient with the signshowTerm(coef[i]);// Showing the variable and exponentcout << "x" << i;}cout << endl;}// Overloading the + operator to add two polynomials and return the resultPolynomial operator+ (const Polynomial &other) const {Polynomial result = *this;for (size_t i = 0; i < other.coef.size(); ++i) {if (i < result.coef.size()) {result.coef[i] += other.coef[i];} else {result.coef.push_back(other.coef[i]);}}return result;}// Function to add two polynomials and return the resultPolynomial plus(const Polynomial &other) const {Polynomial result = *this;for (size_t i = 0; i < other.coef.size(); ++i) {if (i < result.coef.size()) {result.coef[i] += other.coef[i];} else {result.coef.push_back(other.coef[i]);}}return result;}};int main() {vector<int> va = {-1, -2, -3};vector<int> vb = {10, 20, 30};vector<int> vc = {3, -4, 5};Polynomial a = va;Polynomial b = vb;Polynomial c = vc;// Notice the two cascaded calls in the RHS of both of the following lines:// a is the caller and b is the parameter in the first call// The result of the first call is caller and c is the parameter in the second callPolynomial r = a + b + c; // No use of dot and parentheses for +Polynomial s = a.plus(b).plus(c); // Use of dot and parentheses for plus// Showing the values of all objects by calling the show method using dot and parentheses.a.show();b.show();c.show();r.show();s.show();return 0;}
In the above code:
Lines 10–63: We define class Polynomial
as having one data member, one type-cast constructor, and four methods. The plus()
method and the overloaded operator +
have the same behavior. They are included to demonstrate the difference between the calling of these two methods.
Lines 16–25: We define the showTerm()
method. It is a public member function that takes a coefficient as a parameter and displays it along with the appropriate sign. It ensures that positive coefficients are displayed with a "+"
sign and negative coefficients are displayed with a "-"
sign. This separation of concerns makes the code more modular and easier to understand.
Lines 27–36: We define the show()
method that displays the polynomial by iterating through the coefficients. It calls showTerm()
to display the coefficient of each term.
Lines 39–49: We overload the addition operator using the function operator+
. The function takes a constant reference to another Polynomial
object as a parameter and returns a new Polynomial
object representing the sum of the two polynomials. Inside the function, the +
operator is applied element-wise to the coefficients of the polynomials. Notice the use of *this
in line 40 to initialize the result
with the values of the calling object (the first operand). Returning the object of class type (line 48) enables the cascaded calls.
Lines 52–62: We define the plus()
method, which performs the same addition operation as the operator+
function. This method allows the addition of two polynomials and returns the result as a new Polynomial
object. We copy the values of the calling object to result
in line 53. Returning the object of class type (line 61) enables the cascaded calls.
In main()
:
Polynomial
: a
, b
, and c
, using vectors va
, vb
, and vc
, respectively.The following two lines demonstrate the difference between calling an overloaded operator vs. calling a method.
Lines 76: We demonstrate the call to operator+
without using dot and parenthesis. We also show two cascaded calls. The first call is a + b
, and the second is result + c
. The caller of the first call is a
, and the second is the result of a + b
.
Line 77: We demonstrate the call to plus()
with the help of dot and parenthesis. We also show the cascaded calls of this method. The caller of the first call is a
, and the second is the resulting object returned by a.plus(b)
.
At the end:
show()
method for objects a
, b
, c
, r
, and s
using dot notation and parentheses, demonstrating the use of methods to display polynomial values. Notice that the values of r
and s
are the same.
The following are various types of operators that can be overloaded in C++.
Arithmetic operators: All arithmetic operators can be overloaded: +
, -
, *
, /
and %
. A popular example is the overloaded operator +
for strings.
Bitwise operators: All bitwise operators can be overloaded: ^
, |
, &
, ~
, <<
, and >>
. The popular examples are the use of operators <<
and >>
for output and input.
Assignment and compound assignment operators: All assignment operators can be overloaded: =
, +=
, -=
, *=
, /=
, %=
, &=
, |=
, ^=
, <<=
, and >>=
. It is important to note that the assignment operator (=
) must be overloaded as a member function of the class. This operator is as necessary as providing a copy-constructor in the class. Usually, when we use pointer data members in a class, we provide these two functionalities to avoid buggy results due to the default member-wise copy and assignment.
Relational operators: All relational operators can be overloaded: ==
, !=
, <
, >
, <=
, and >=
. Usually, only <
is overloaded in detail, and all others are defined in terms of this operator. It saves the maintenance effort in the future.
Logical operators: All logical operators can be overloaded: !
, &&
, and ||
. A popular example is the overloaded operator !
to test the validity of i/o streams.
Increment and decrement operators: All increment and decrement operators can be overloaded: ++
and --
. The pre-increment and pre-decrement operators are implemented as unary operators, while post-increment and post-decrement are implemented as binary operators. The second operand in the case of post-increment/post-decrement is the value 1
sent by the language as an int
type.
Subscript operator: The subscript operator []
can be overloaded as a member of the class. It has two possible implementations for a class: RValue and LValue. In the presence of the LValue version, the RValue can be provided only for const
objects. The LValue version returns a reference type. Both versions can have only one parameter of any type. A popular example is the overloaded subscript operator for maps/dictionaries.
Function call operator: The function operator ()
can be overloaded as a member of the class. It can have any number of parameters and have several overloaded versions in a class.
Miscellaneous operators: The following operators can also be overloaded in C++: address (&
), reference (&
), pointer indirection (*
), member reference operator through pointer (->
), memory management operators (new
and delete
), type conversion operators (()
), and the comma operator (,
).
Data Structures Preliminaries (Refresher of Fundamentals in C++)
Dive into a comprehensive review of the fundamental elements of C++ through this course. Experience concise yet detailed explanations, coupled with hands-on exercises that delve into the core concepts of C++ fundamentals. Your journey begins with the exploration of fundamental programming concepts, such as the versatility of variables and their applications in strengthening control structures like conditions, loops, arrays and pointers. Next, you’ll cover structured programming, understanding its advantages and disadvantages, which seamlessly transitions you into object-oriented programming (OOP). Your focus will expand to encompass subjects like classes, object relationships and the implementation of operator overloading to equip user-defined data types. By the end of this course, you'll possess a solid grasp of the essential fundamentals, providing a robust foundation for success in subsequent courses, including the study of data structures and algorithms in C++.
Most of the operators in C++ can be overloaded, except the following:
Operator Name | Operator Token |
---|---|
Conditional | ?: |
Member selection | . |
Member pointer indirection | .* |
Scope resolution | :: |
Object size information | sizeof |
Object type information | typeid |
Static casting operator | static_cast |
Const casting operator | const_cast |
Raw casting operator | reinterpret_cast |
Polymorphic casting operator | dynamic_cast |
A binary operator has two operands. The above example illustrates the overloading of a binary operator +
. The language definition specifies which operators are binary in that language. Most of the operators that can be overloaded in C++ are binary, including arithmetic, relational, logical, bit-wise, and assignment.
If the first operand of a binary operator is an object of our class, then we can overload it as a member function of the class. The second operand of a binary operator is received as a parameter in the overloaded version when the operator is overloaded as a member of the class. Every non-static member function (method or operator) implicitly receives the caller object as its first argument. It is accessible through the keyword this
, which is a pointer of the class type. In some cases, we need to use the this
pointer, but usually we don’t need to explicitly use it.
The following code demonstrates the use of the first operand as an object of the class. We declare the operator const
because we don’t want to change any data in the first operand. It adds to the safety and is followed as a best practice.
We explicitly write a call to show()
using this
inside the overloaded operator only to demonstrate its values.
#include <iostream>#include <cstdlib>#include <vector>using namespace std;class Polynomial {vector<int> coef;public:Polynomial(const vector<int> & coefficients) : coef(coefficients) {}void showTerm(int val) const {if (val != 0) {if (val > 0) {cout << " + ";} else {cout << " - ";}cout << abs(val);}}void show() const {int lim = coef.size() - 1;for (int i = lim; i >= 0; --i) {// Showing the coefficient with the signshowTerm(coef[i]);// Showing the variable and exponentcout << "x" << i;}cout << endl;}// Overloading the + operator to add two polynomials and return the resultPolynomial operator+ (const Polynomial &other) const {cout << "The code of operator+ starts here.\n";// Showing the first operand being the caller objectcout << " The first operand is the caller:\n "; this->show();// Showing the second operandcout << " The second operand is the parameter:\n "; other.show();Polynomial result = *this;for (size_t i = 0; i < other.coef.size(); ++i) {if (i < result.coef.size()) {result.coef[i] += other.coef[i];} else {result.coef.push_back(other.coef[i]);}}cout << "The code of operator+ ends here.\n";return result;}};int main() {vector<int> va = {-1, -2, -3};vector<int> vb = {10, 20, 30};Polynomial a = va;Polynomial b = vb;Polynomial c = a + b; // a is the caller and b is the parameter// Showing the resulting object.cout << "\nThe resulting object is:\n"; c.show();return 0;}
Let’s focus on the overloaded operator in the above code:
Lines 35–51: The operator+()
is the overloaded operator that adds two Polynomial
objects. It displays a message indicating the start of the operator overloading process in line 36. Then, it shows the operands (the caller object *this
and the parameter other
) in lines 38 and 40. It then performs the addition of coefficients. Then, it displays a message indicating the end of the operator overloading process in line 49.
Line 62: Inside the main()
function, we display the resulting Polynomial
object c
using the show()
method.
We can also overload the same operator without making it the member function of the class. In this case, we need to declare such non-member functions as a friend of the class so that they can access the private
data of that class.
If the first operand of a binary operator is not an object of the class, then the operator must be overloaded as a non-member.
The first operand should be received by value or as a constant reference when we don’t want to change it (as in the case of the +
operator). It adds to the safety of accidental writes inside the function definition. The following code demonstrates the same.
#include <iostream>#include <cstdlib>#include <vector>using namespace std;class Polynomial {vector<int> coef;public:Polynomial(const vector<int> & coefficients) : coef(coefficients) {}void showTerm(int val) const {if (val != 0) {if (val > 0) {cout << " + ";} else {cout << " - ";}cout << abs(val);}}void show() const {int lim = coef.size() - 1;for (int i = lim; i >= 0; --i) {// Showing the coefficient with the signshowTerm(coef[i]);// Showing the variable and exponentcout << "x" << i;}cout << endl;}friend Polynomial operator+ (const Polynomial &first, const Polynomial &other);};// Overloading the + operator to add two polynomials and return the resultPolynomial operator+ (const Polynomial &first, const Polynomial &other) {cout << "The code of FRIEND operator+ starts here.\n";// Showing the first operand being the caller objectcout << " The first operand is the first parameter:\n "; first.show();// Showing the second operandcout << " The second operand is the second parameter:\n "; other.show();Polynomial result = first;for (size_t i = 0; i < other.coef.size(); ++i) {if (i < result.coef.size()) {result.coef[i] += other.coef[i];} else {result.coef.push_back(other.coef[i]);}}cout << "The code of FRIEND operator+ ends here.\n";return result;}int main() {vector<int> va = {-1, -2, -3};vector<int> vb = {10, 20, 30};Polynomial a = va;Polynomial b = vb;Polynomial c = a + b; // a is the caller and b is the parameter// Showing the resulting object.cout << "\nThe resulting object is:\n"; c.show();return 0;}
In the above code, let’s focus only on the friend function and its details:
operator+()
function as a friend
of the Polynomial
class. This allows the operator+()
function to access the private members of the Polynomial
class.operator+()
function outside of the class. It takes two Polynomial
objects as parameters and overloads the +
operator. Inside the function:
friend
operator overloading process in line 38.first
and other
) in lines 40 and 42.friend
operator overloading process in line 51.Using the power of friend
, we can also overload a binary operator whose first operand is not an object of our class.
In C++, the
private
members of a class can be accessed from the member functions (of the same class) or thefriend
functions.
Become a C++ Programmer
C++ is a popular language used to develop browsers, games, and operating systems. It's also an essential tool in the development of modern technology like IoT and self-driving cars. This Path will expand your knowledge of C++ with lessons built for professional developers. This Path will take you from basic to advanced concepts with hands-on practice. By the end, you'll have enough C++ experience to confidently solve real-world problems.
We don’t need to declare such non-member functions as friends if they do not access the private members of the class directly. The use of friend
provides a way to intrude into the private area of the class without validity and stability testing.
The use of
friend
is a breach of encapsulation.
If we have properly encapsulated the private members of the class through a public interface, then we don’t need to access the private members directly. The following code demonstrates the same.
#include <iostream>#include <cstdlib>#include <vector>using namespace std;class Polynomial {vector<int> coef;public:Polynomial(const vector<int> & coefficients) : coef(coefficients) {}size_t degree() const {return coef.size();}int getTerm(int ind) const {if (ind >= 0 && ind < coef.size()) {return coef[ind];}return 0;}void setTerm(int ind, int val) {if (ind >= 0 && ind < coef.size()) {coef[ind] = val;}}void newTerm(int val) {coef.push_back(val);}void showTerm(int val) const {if (val != 0) {if (val > 0) {cout << " + ";} else {cout << " - ";}cout << abs(val);}}void show() const {int lim = coef.size() - 1;for (int i = lim; i >= 0; --i) {// Showing the coefficient with the signshowTerm(coef[i]);// Showing the variable and exponentcout << "x" << i;}cout << endl;}};// Overloading + operator to add two polynomials and return the result// as a non-member and non-friend, through public methods in the classPolynomial operator+ (const Polynomial &first, const Polynomial &other) {Polynomial result = first;// Using public method degree() instead of accessing private coef.size()for (size_t i = 0; i < other.degree(); ++i) {if (i < result.degree()) {// Using public method getTerm(i) instead of accessing private coef[i]int newVal = result.getTerm(i) + other.getTerm(i);// Using public method setTerm(i,newVal) instead of coef[i] = newValresult.setTerm(i,newVal);} else {// Using public method newTerm(i) instead of coef.push_back()result.newTerm(other.getTerm(i));}}return result;}int main() {vector<int> va = {-1, -2, -3};vector<int> vb = {10, 20, 30};Polynomial a = va;Polynomial b = vb;Polynomial c = a + b; // a is the caller and b is the parameter// Showing the resulting object.cout << "\nThe resulting object is:\n";c.show();return 0;}
In the above code, let’s focus on the operator overloaded as non-member and non-friend:
degree()
method. It is a public member function of the Polynomial
class that returns the size of the coefficient vector as the degree of the polynomial.getTerm()
takes an index ind
as a parameter and returns the coefficient at that index. If the index is out of range, it returns 0
to indicate the non-existence of the term in the polynomial.setTerm()
method. It is a public member function that takes an index ind
and a value val
as parameters and sets the coefficient at the given index to the provided value. If the index is out of range, it does nothing.newTerm()
public method. It adds a new term (coefficient) to the polynomial by pushing the val
value to the end of the coefficient vector.operator+()
as a function outside the class. It is an overloaded addition operator that takes two Polynomial
objects (first
and other
) as parameters. Inside the function, it utilizes the public methods degree()
, getTerm()
, setTerm()
, and newTerm()
to perform the addition of two polynomials. It creates a new Polynomial
object result
and returns it.
The second operand of a binary operator is received as a parameter in the overloaded version when the operator is overloaded as a member of the class. When a binary operator is overloaded as a non-member function, then the second operand is received as the second parameter of the class. The second operand should be received by value or as a constant reference. It adds to the safety of accidental writes inside the function definition.
C++ Programming for Experienced Engineers
If you’re looking to sharpen up your C++ programming skills, then you’re in the right place. You will start with learning about dynamic memory allocation, how to create classes, and constructors. In the latter half of the course, inheritance, functions, I/O, exception handling, and more. Throughout the course, you will study over 90 carefully designed examples aimed to enhance your understanding of the C++ language. By the time you finish this course, you will be able to use more advanced functionality in your C++ programs.
A unary operator is one that has only one operand, which is considered the caller of the operator. The language definition specifies which operators are unary in that language. The following unary operators can be overloaded in C++: -
, ~
, !
, ++
, and --
. The following operators (listed under the miscellaneous operators above) are also unary: address of (&
), reference (&
), pointer indirection (*
), memory management (new
and delete
), and type conversion operators.
The name of the type conversion operators works as their return type. There should be no return type before the
operator
keyword in this case.
We can overload unary operators as member functions of the class. The operand is the caller object accessible through the this
keyword. The following code demonstrates the overloading of a unary operator as a member function. We declare it as a const
member function because we don’t want to change the caller object in it.
We explicitly write a call to show
using this
inside the overloaded operator just to show the value of the caller object.
#include <iostream>#include <cstdlib>#include <vector>using namespace std;class Polynomial {vector<int> coef;public:Polynomial(const vector<int> & coefficients) : coef(coefficients) {}void showTerm(int val) const {if (val != 0) {if (val > 0) {cout << " + ";} else {cout << " - ";}cout << abs(val);}}void show() const {int lim = coef.size() - 1;for (int i = lim; i >= 0; --i) {// Showing the coefficient with the signshowTerm(coef[i]);// Showing the variable and exponentcout << "x" << i;}cout << endl;}// Overloading the - operator to return a polynomial with inverted signs of the coefficients of the originalPolynomial operator- () const {Polynomial result = *this;for (size_t i = 0; i < result.coef.size(); ++i) {result.coef[i] = -result.coef[i];}return result;}};int main() {vector<int> va = {1, -2, -3};Polynomial a = va;Polynomial b = -a; // Use of unary negation// Showing the values of all objects by calling the method showa.show();b.show();return 0;}
In the above code, let’s focus on the definition and use of the overloaded unary operator:
operator-()
for the Polynomial
class. The operator-()
returns a new Polynomial object with the coefficients with inverted signs. The *this
pointer is used to access the operand (caller object), and a new Polynomial
object result
is initialized with the caller object. The coefficients of result
are then inverted by negating each coefficient in the loop.main()
function, a vector va
is used to create a Polynomial
object a
. The unary negation operator is applied to a
in line 47, and a new Polynomial
object b
is created with inverted coefficients resulting from the operator call. The show()
method is called for objects a
and b
, demonstrating the use of the overloaded unary negation operator.Note that there is no parameter when we overload a unary operator as a member function of the class. The only operand of a unary operator is received in *this
being the caller object of this operator.
We can overload the same operator without making it the member function of the class, declaring it a friend of the class to access the private
data directly. The operand should be received by value or as a constant reference when we don’t want to change it. It adds to the safety. The following code demonstrates the same.
#include <iostream>#include <cstdlib>#include <vector>using namespace std;class Polynomial {vector<int> coef;public:Polynomial(const vector<int> & coefficients) : coef(coefficients) {}void showTerm(int val) const {if (val != 0) {if (val > 0) {cout << " + ";} else {cout << " - ";}cout << abs(val);}}void show() const {int lim = coef.size() - 1;for (int i = lim; i >= 0; --i) {// Showing the coefficient with the signshowTerm(coef[i]);// Showing the variable and exponentcout << "x" << i;}cout << endl;}friend Polynomial operator- (const Polynomial &one);};// Overloading - operator to return a polynomial with inverted signs of the coefficients of the originalPolynomial operator- (const Polynomial &one) {Polynomial result = one;for (size_t i = 0; i < result.coef.size(); ++i) {result.coef[i] = -result.coef[i];}return result;}int main() {vector<int> va = {1, -2, -3};Polynomial a = va;Polynomial b = -a; // Use of unary minus// Showing the values of all objects by calling the method showa.show();b.show();return 0;}
In the above code, let’s focus only on the friend
function and its details:
operator-()
function as a friend
of the Polynomial
class. This allows the operator-()
function to access the private members of the Polynomial
class.operator-()
function outside of the class. It takes a Polynomial
object as a parameter and overloads the -
operator. Inside the function, it performs the negation of coefficients of the parameters and stores them to the coefficients of another object result
.The logic of not making a friend for binary operators remains the same for unary operators. We don’t need to declare such non-member functions as friends if they do not access the class’s private members directly. If we properly encapsulate the class’s private members through public methods, we don’t need to access the private members directly. In this case, we need to provide the public methods degree()
, getTerm()
, and setTerm()
to perform the negation of a polynomial.
The overloading of operators is a powerful tool provided by C++ to mimic the abstract data types (classes) that behave more like primitive data types. Certain operators cannot be overloaded, while the majority of operators are allowed to be overloaded. Some operators must be made the member of the class if there is a need to overload them. All other operators can be overloaded as member functions and as non-members. If the caller (the first operand) of an operator is not a class object, then the operator cannot be overloaded as a member of that class.
Happy learning!
To start learning these concepts and more, check out Educative’s Data Structures Preliminaries (Refresher of Fundamentals in C++) course.
Free Resources