Arithmetic Operator Overloading
Implement +, -, *, / as friend functions for symmetric operand access.
How Do You Overload Arithmetic Operators Using Friend Functions?
Friend function operator overloading lets you define arithmetic operators (+, -, *, /) as non-member functions that have direct access to a class's private members. This is often the most intuitive approach for binary operators because it treats both operands symmetrically.
The arithmetic operators (+, -, *, /) are among the most commonly used operators in C++. These are all binary operators (taking two operands - one on each side). They can all be overloaded following the same pattern.
There are three approaches to overloading operators: member functions, friend functions, and normal functions. This lesson covers the friend function approach (the most intuitive for binary operators). The next lesson discusses normal functions, and a later lesson covers member functions. We'll also summarize when to use each approach.
Overloading Operators Using Friend Functions
Consider this class representing weight:
class Weight
{
private:
double m_kilograms{};
public:
explicit Weight(double kg) : m_kilograms{kg} {}
double getKilograms() const { return m_kilograms; }
};
Here's how to overload operator+ to add two Weight objects:
#include <iostream>
class Weight
{
private:
double m_kilograms{};
public:
explicit Weight(double kg) : m_kilograms{kg} {}
friend Weight operator+(const Weight& w1, const Weight& w2);
double getKilograms() const { return m_kilograms; }
};
// Note: not a member function!
Weight operator+(const Weight& w1, const Weight& w2)
{
// Access m_kilograms directly (friend function)
return Weight{w1.m_kilograms + w2.m_kilograms};
}
int main()
{
Weight apple{0.25};
Weight orange{0.3};
Weight total{apple + orange};
std::cout << "Total weight: " << total.getKilograms() << " kg\n";
return 0;
}
Output:
Total weight: 0.55 kg
Overloading + requires declaring a function named operator+ with two parameters (the operand types), choosing an appropriate return type, and implementing the logic.
For our Weight class: we're adding two Weight objects, so we take two Weight parameters. We want to return a Weight result, so that's our return type.
Implementation: adding two Weight objects means adding their m_kilograms members. Since our overloaded operator+ is a friend, we can access m_kilograms directly. Since m_kilograms is a double and C++ knows how to add doubles, we simply use the built-in + operator.
Overloading subtraction (-) follows the same pattern:
#include <iostream>
class Weight
{
private:
double m_kilograms{};
public:
explicit Weight(double kg) : m_kilograms{kg} {}
friend Weight operator+(const Weight& w1, const Weight& w2);
friend Weight operator-(const Weight& w1, const Weight& w2);
double getKilograms() const { return m_kilograms; }
};
Weight operator+(const Weight& w1, const Weight& w2)
{
return Weight{w1.m_kilograms + w2.m_kilograms};
}
Weight operator-(const Weight& w1, const Weight& w2)
{
return Weight{w1.m_kilograms - w2.m_kilograms};
}
int main()
{
Weight package{5.0};
Weight contents{3.2};
Weight packaging{package - contents};
std::cout << "Packaging weight: " << packaging.getKilograms() << " kg\n";
return 0;
}
Overloading multiplication (*) and division (/) works the same way by defining operator* and operator/ respectively.
Friend Functions Can Be Defined Inside the Class
While friend functions aren't class members, they can be defined inside the class:
#include <iostream>
class Weight
{
private:
double m_kilograms{};
public:
explicit Weight(double kg) : m_kilograms{kg} {}
// Friend function defined inside the class (still not a member!)
friend Weight operator+(const Weight& w1, const Weight& w2)
{
return Weight{w1.m_kilograms + w2.m_kilograms};
}
double getKilograms() const { return m_kilograms; }
};
int main()
{
Weight item1{1.5};
Weight item2{2.3};
Weight combined{item1 + item2};
std::cout << "Combined weight: " << combined.getKilograms() << " kg\n";
return 0;
}
This is convenient for trivial implementations.
Overloading Operators for Operands of Different Types
Often you'll want overloaded operators to work with mixed types. For example, if we have Weight{2.0}, we might want to multiply by the integer 3 to get Weight{6.0}.
When C++ evaluates x * y, x becomes the first parameter and y becomes the second. When both have the same type, order doesn't matter - x * y and y * x call the same function. However, with different types, x * y calls a different function than y * x.
For example, Weight{2.0} * 3 calls operator*(Weight, int), while 3 * Weight{2.0} calls operator*(int, Weight). We need to write both:
#include <iostream>
class Weight
{
private:
double m_kilograms{};
public:
explicit Weight(double kg) : m_kilograms{kg} {}
friend Weight operator*(const Weight& w, double factor);
friend Weight operator*(double factor, const Weight& w);
double getKilograms() const { return m_kilograms; }
};
Weight operator*(const Weight& w, double factor)
{
return Weight{w.m_kilograms * factor};
}
Weight operator*(double factor, const Weight& w)
{
return Weight{w.m_kilograms * factor};
}
int main()
{
Weight single{0.5};
Weight batch1{single * 6};
Weight batch2{4 * single};
std::cout << "Batch 1: " << batch1.getKilograms() << " kg\n";
std::cout << "Batch 2: " << batch2.getKilograms() << " kg\n";
return 0;
}
Both functions have identical implementations since they perform the same operation with parameters in different orders.
Another Example
Let's create a class representing a 2D rectangle that can be scaled and combined:
#include <iostream>
class Rectangle
{
private:
double m_width{};
double m_height{};
public:
Rectangle(double width, double height)
: m_width{width}, m_height{height}
{}
double getWidth() const { return m_width; }
double getHeight() const { return m_height; }
double getArea() const { return m_width * m_height; }
friend Rectangle operator+(const Rectangle& r1, const Rectangle& r2);
friend Rectangle operator*(const Rectangle& r, double scale);
friend Rectangle operator*(double scale, const Rectangle& r);
};
Rectangle operator+(const Rectangle& r1, const Rectangle& r2)
{
// Combining rectangles: use max dimensions
double newWidth{r1.m_width > r2.m_width ? r1.m_width : r2.m_width};
double newHeight{r1.m_height > r2.m_height ? r1.m_height : r2.m_height};
return Rectangle{newWidth, newHeight};
}
Rectangle operator*(const Rectangle& r, double scale)
{
return Rectangle{r.m_width * scale, r.m_height * scale};
}
Rectangle operator*(double scale, const Rectangle& r)
{
return r * scale; // Reuse the other overload
}
int main()
{
Rectangle box1{3.0, 2.0};
Rectangle box2{2.0, 4.0};
Rectangle box3{1.0, 1.0};
Rectangle combined{(box1 + box2 + box3) * 2.0};
std::cout << "Combined: " << combined.getWidth() << " x "
<< combined.getHeight() << " = " << combined.getArea() << " sq units\n";
return 0;
}
Output:
Combined: 6 x 8 = 48 sq units
The Rectangle class tracks width and height. We've overloaded + to combine rectangles (taking maximum dimensions) and * for scaling.
The expression (box1 + box2 + box3) * 2.0 evaluates left to right:
box1 + box2producesRectangle{3.0, 4.0}Rectangle{3.0, 4.0} + box3producesRectangle{3.0, 4.0}Rectangle{3.0, 4.0} * 2.0producesRectangle{6.0, 8.0}
Each operation returns a Rectangle object used as an operand for the next operation.
Implementing Operators Using Other Operators
In the example above, operator*(double, Rectangle) calls operator*(Rectangle, double), reducing duplication and simplifying maintenance. When one operator can be implemented by calling another producing the same result, do so.
Summary
Friend function operators: Overloaded operators implemented as friend functions have access to private members but aren't class members themselves.
Arithmetic operator pattern: Binary arithmetic operators like +, -, *, / follow the same overloading pattern - take two const references as parameters, return by value, and implement using the underlying member operations.
Inline friend definitions: Friend functions can be defined inside the class definition for convenience with trivial implementations, though they remain non-member functions.
Mixed-type operations: When overloading operators for different operand types (e.g., Weight * double), both orders may need separate overloads since x * y and y * x call different functions when types differ.
Implementing operators using others: When one operator can be implemented by calling another (e.g., operator*(double, Rectangle) calling operator*(Rectangle, double)), do so to reduce code duplication and simplify maintenance.
Operator chaining: Returning by value allows chaining multiple operations, as each operation produces a new object that becomes an operand for the next operation.
Friend functions are ideal for binary operators that don't modify operands, providing symmetric access to both parameters and working with all parameter types (including when the left operand isn't a class or is unmodifiable). The next lessons cover normal functions and member functions as alternatives.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
Arithmetic Operator Overloading - Quiz
Test your understanding of the lesson.
Practice Exercises
Complex Number Arithmetic
Create a Complex class to represent complex numbers (with real and imaginary parts). Overload arithmetic operators (+, -, *) using friend functions to enable natural mathematical operations.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!