C++ course menu
C++ Operator Overloading: Arithmetic, Comparison, << and []
Learn operator overloading in C++: defining + and += for your own types, member vs non-member operators, comparison with == and the C++20 spaceship operator <=>, printing with operator<<, the subscript operator [], and rules for overloading sensibly.
On this page
C++ lets your own types work with operators like +, == and <<, just like built-in types. Writing total = price + tax with a Money class, or std::cout << point, makes code read naturally. This is operator overloading: an operator is just a function with a special name, such as operator+.
Overloading + and +=#
#include <iostream>
struct Vec2 {
double x = 0;
double y = 0;
Vec2& operator+=(const Vec2& other) {
x += other.x;
y += other.y;
return *this;
}
};
Vec2 operator+(Vec2 a, const Vec2& b) {
a += b; // reuse +=
return a;
}
Vec2 operator*(const Vec2& v, double k) {
return {v.x * k, v.y * k};
}
int main() {
Vec2 position{1, 2};
Vec2 velocity{0.5, -1};
Vec2 next = position + velocity;
position += velocity * 2;
std::cout << next.x << ',' << next.y << " " << position.x << ',' << position.y << '\n';
}1.5,1 2,0The usual pattern is to write the compound operator (+=) as a member that changes the object and returns *this, and then the plain operator (+) as a non-member function built on it.
Member or non-member?#
- Operators that change the left operand (
+=,-=,=,[],++) are members. - Symmetric operators (
+,-,*,==) are usually non-members, so conversions work on both sides. <<and>>for streams must be non-members, because the left operand is the stream, not your object.
Printing with operator<<#
To make std::cout << object work, overload operator<< taking an std::ostream&, and return the stream so calls can be chained:
#include <iostream>
#include <string>
class Money {
public:
explicit Money(long long paise) : paise_(paise) {}
long long paise() const { return paise_; }
private:
long long paise_;
};
std::ostream& operator<<(std::ostream& out, const Money& m) {
long long rupees = m.paise() / 100;
long long rest = m.paise() % 100;
out << "Rs " << rupees << '.' << (rest < 10 ? "0" : "") << rest;
return out;
}
int main() {
Money price(129950);
Money fee(705);
std::cout << "Price: " << price << ", fee: " << fee << '\n';
}Price: Rs 1299.50, fee: Rs 7.05Storing money as a whole number of paise avoids floating-point rounding errors. If operator<< needs private members, declare it a friend inside the class (see Encapsulation).
Comparison operators#
Defining == lets objects be compared. In C++20, if you define ==, the compiler also gives you !=:
#include <iostream>
#include <string>
struct Book {
std::string isbn;
std::string title;
};
bool operator==(const Book& a, const Book& b) {
return a.isbn == b.isbn; // the same book if the ISBN matches
}
int main() {
Book a{"978-0134", "The C++ Book"};
Book b{"978-0134", "The C++ Book (2nd print)"};
Book c{"978-1491", "Another Book"};
std::cout << std::boolalpha << (a == b) << ' ' << (a != c) << '\n';
}true trueThe spaceship operator <=> (C++20)#
operator<=> (the "three-way comparison", nicknamed spaceship) defines ordering. Defaulting it compares the members in declaration order and generates <, <=, > and >=; defaulting == generates == and !=:
#include <algorithm>
#include <compare>
#include <iostream>
#include <vector>
struct Date {
int year = 0;
int month = 0;
int day = 0;
auto operator<=>(const Date&) const = default;
bool operator==(const Date&) const = default;
};
int main() {
std::vector<Date> dates = {{2026, 10, 23}, {2025, 12, 31}, {2026, 1, 5}};
std::sort(dates.begin(), dates.end()); // uses <
for (const Date& d : dates) {
std::cout << d.year << '-' << d.month << '-' << d.day << '\n';
}
std::cout << std::boolalpha << (dates[0] < dates[1]) << '\n';
}2025-12-31
2026-1-5
2026-10-23
trueBecause members are compared year first, then month, then day, the dates sort in calendar order. Types that define < work with std::sort, std::map and std::set automatically.
The subscript operator []#
Overloading [] lets your class be indexed like an array. Provide both a non-const version (for writing) and a const version (for reading):
#include <iostream>
#include <stdexcept>
#include <vector>
class Scores {
public:
explicit Scores(std::size_t n) : data_(n, 0) {}
int& operator[](std::size_t i) { return data_.at(i); }
const int& operator[](std::size_t i) const { return data_.at(i); }
std::size_t size() const { return data_.size(); }
private:
std::vector<int> data_;
};
int main() {
Scores s(3);
s[0] = 90;
s[2] = 75;
for (std::size_t i = 0; i < s.size(); ++i) std::cout << s[i] << ' ';
std::cout << '\n';
try {
s[5] = 1;
} catch (const std::out_of_range&) {
std::cout << "index 5 is out of range\n";
}
}90 0 75
index 5 is out of rangeRules for sensible overloading#
- Keep the usual meaning.
+should add,==should test equality. Surprising operators make code harder to read. - Be consistent. If you provide
+, provide+=; ifa == b, then!(a != b). - Return the right thing.
+=,=,[]and prefix++return a reference to*this;+returns a new value;<<returns the stream. - Some operators cannot be overloaded at all:
.,::,?:andsizeof.
Common mistakes#
Practice#
Practice exercise
Write a struct Fraction { int num; int den; } with operator+ (returning the sum in lowest terms, using std::gcd), operator== (comparing values, so 1/2 == 2/4), and operator<< printing num/den. Print 1/2 + 1/3 and 1/2 == 2/4.
Show solution
#include <iostream>
#include <numeric>
struct Fraction {
int num = 0;
int den = 1;
};
Fraction reduce(Fraction f) {
int g = std::gcd(f.num, f.den);
return {f.num / g, f.den / g};
}
Fraction operator+(const Fraction& a, const Fraction& b) {
return reduce({a.num * b.den + b.num * a.den, a.den * b.den});
}
bool operator==(const Fraction& a, const Fraction& b) {
return a.num * b.den == b.num * a.den;
}
std::ostream& operator<<(std::ostream& out, const Fraction& f) {
return out << f.num << '/' << f.den;
}
int main() {
Fraction half{1, 2};
Fraction third{1, 3};
std::cout << half + third << '\n';
std::cout << std::boolalpha << (half == Fraction{2, 4}) << '\n';
}5/6
trueKey takeaway#
Finished this lesson?