Skip to content
Laviyx Learn

Try

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.

Intermediate12 min readUpdated
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 +=#

C++
#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';
}
Output
1.5,1  2,0

The 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:

C++
#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';
}
Output
Price: Rs 1299.50, fee: Rs 7.05

Storing 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 !=:

C++
#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';
}
Output
true true

The 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 !=:

C++
#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';
}
Output
2025-12-31
2026-1-5
2026-10-23
true

Because 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):

C++
#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";
    }
}
Output
90 0 75
index 5 is out of range

Rules 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 +=; if a == 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: ., ::, ?: and sizeof.

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
C++
#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';
}
Output
5/6
true

Key takeaway#

Finished this lesson?