Modern examples of C++ operator overloading: custom operators examples that actually matter

If you’ve ever stared at a messy C++ class and thought, “This should behave more like a built‑in type,” you’re in the right place. In this guide, we’ll walk through modern, real‑world examples of C++ operator overloading: custom operators examples that show how to make your types feel natural to use, instead of awkward and verbose. These are not toy snippets you forget in five minutes. We’ll wire up arithmetic operators for vectors, comparisons for money types, smart pointer‑like behavior, and even custom literals. You’ll see examples of C++ operator overloading: custom operators examples used in numeric code, resource management, domain models, and modern C++ libraries. Along the way, we’ll call out common pitfalls, current (2024–2025) best practices like `explicit` and `=delete`, and where operator overloading genuinely improves readability instead of turning your code into a puzzle. If you want practical, opinionated guidance and real examples, keep reading.
Written by
Jamie
Published

Let’s start where most developers actually feel the benefit: making domain types behave like the built‑ins they represent.

1. Overloading arithmetic operators for a 2D vector

A classic example of C++ operator overloading is a small math type like a 2D vector. Instead of writing add(v1, v2) everywhere, you can use +, -, and * in a readable way.

#include <cmath>
#include <iostream>

struct Vec2 {
    double x{};
    double y{};

    // Vector addition
    Vec2 operator+(const Vec2& other) const {
        return {x + other.x, y + other.y};
    }

    // Vector subtraction
    Vec2 operator-(const Vec2& other) const {
        return {x - other.x, y - other.y};
    }

    // Scalar multiplication (Vec2 * scalar)
    Vec2 operator*(double scalar) const {
        return {x * scalar, y * scalar};
    }

    // Compound assignment
    Vec2& operator+=(const Vec2& other) {
        x += other.x;
        y += other.y;
        return *this;
    }
};

// Scalar * Vec2 (non-member to allow implicit conversion on left-hand side)
inline Vec2 operator*(double scalar, const Vec2& v) {
    return v * scalar;
}

int main() {
    Vec2 a{1.0, 2.0};
    Vec2 b{3.0, 4.0};

    Vec2 c = a + b;       // uses operator+
    Vec2 d = 2.0 * c;     // uses non-member operator*
    c += d;               // uses operator+=

    std::cout << c.x << ", " << c.y << '\n';
}

This is one of the best examples of C++ operator overloading: custom operators examples that genuinely improve readability. The code expresses math, not plumbing.

2. Strongly typed money with comparison and arithmetic

Floating‑point for money is a bad idea. A better approach is a Money type using integers (cents) and overloaded operators. This is a very practical example of C++ operator overloading used in finance and e‑commerce systems.

#include <compare>
#include <cstdint>

class Money {
public:
    explicit Money(std::int64_t cents = 0) : cents_(cents) {}

    // Arithmetic
    Money operator+(const Money& other) const {
        return Money(cents_ + other.cents_);
    }

    Money operator-(const Money& other) const {
        return Money(cents_ - other.cents_);
    }

    Money& operator+=(const Money& other) {
        cents_ += other.cents_;
        return *this;
    }

    // C++20 three-way comparison
    auto operator<=>(const Money&) const = default;

private:
    std::int64_t cents_;
};

int main() {
    Money price(1999);      // $19.99 in cents
    Money tax(160);         // $1.60
    Money total = price + tax;

    if (total > Money(2000)) {
        // ...
    }
}

Here, operator+, operator-, and the comparison operator make Money feel like a numeric type while still being type‑safe. This is a clean, domain‑driven example of C++ operator overloading: custom operators examples used in real business code.

3. Smart pointer‑like behavior with operator* and operator->

You should almost always use std::unique_ptr or std::shared_ptr instead of writing your own smart pointer. But understanding how they work is still valuable.

#include <utility>

template <typename T>
class OwningPtr {
public:
    explicit OwningPtr(T* ptr = nullptr) noexcept : ptr_(ptr) {}

    ~OwningPtr() { delete ptr_; }

    OwningPtr(const OwningPtr&) = delete;
    OwningPtr& operator=(const OwningPtr&) = delete;

    OwningPtr(OwningPtr&& other) noexcept : ptr_(other.ptr_) {
        other.ptr_ = nullptr;
    }

    OwningPtr& operator=(OwningPtr&& other) noexcept {
        if (this != &other) {
            delete ptr_;
            ptr_ = other.ptr_;
            other.ptr_ = nullptr;
        }
        return *this;
    }

    T& operator*() const noexcept { return *ptr_; }
    T* operator->() const noexcept { return ptr_; }
    explicit operator bool() const noexcept { return ptr_ != nullptr; }

private:
    T* ptr_;
};

struct Widget {
    void ping();
};

int main() {
    OwningPtr<Widget> w(new Widget{});
    if (w) {
        w->ping();   // operator->
        (*w).ping(); // operator*
    }
}

This is one of those examples of C++ operator overloading: custom operators examples that mimic standard library behavior. You get pointer‑like syntax while still enforcing ownership rules.

4. Safe string wrapper with operator+

String concatenation is a classic operator overloading use case. Here’s a thin wrapper around std::string that restricts implicit conversions and still supports +.

#include <string>
#include <utility>

class SafeString {
public:
    SafeString() = default;
    explicit SafeString(std::string s) : data_(std::move(s)) {}

    const std::string& str() const noexcept { return data_; }

    SafeString operator+(const SafeString& other) const {
        SafeString result;
        result.data_ = data_ + other.data_;
        return result;
    }

private:
    std::string data_;
};

int main() {
    SafeString first{"Hello"};
    SafeString second{" World"};
    SafeString message = first + second; // operator+
}

There are better choices in production (just use std::string unless you have a reason not to), but as an example of C++ operator overloading it’s straightforward and maps to how developers already think about strings.

5. Overloading operator[] for bounds‑checked access

Containers are a natural fit for operator overloading. When you want array‑like syntax with custom behavior (for example, bounds checking or logging), operator[] is your friend.

#include <cstddef>
#include <stdexcept>
#include <vector>

class SafeIntArray {
public:
    explicit SafeIntArray(std::size_t size) : data_(size) {}

    int& operator[](std::size_t index) {
        if (index >= data_.size()) {
            throw std::out_of_range("index out of range");
        }
        return data_[index];
    }

    const int& operator[](std::size_t index) const {
        if (index >= data_.size()) {
            throw std::out_of_range("index out of range");
        }
        return data_[index];
    }

    std::size_t size() const noexcept { return data_.size(); }

private:
    std::vector<int> data_;
};

int main() {
    SafeIntArray arr(10);
    arr[0] = 42;              // operator[]
    int value = arr[0];
}

This is one of the best examples of C++ operator overloading: custom operators examples that add safety without changing how the code looks at the call site.

6. User‑defined literals for domain units (C++11+)

User‑defined literals are technically a different syntax, but they are still operator overloading under the hood. They’re heavily used in modern libraries, including time and units libraries.

#include <cstdint>
#include <iostream>

class Meters {
public:
    explicit Meters(long double value) : value_(value) {}

    long double value() const noexcept { return value_; }

private:
    long double value_;
};

// User-defined literal for meters
Meters operator"" _m(long double value) {
    return Meters(value);
}

int main() {
    Meters distance = 100.0_m; // operator""_m
    std::cout << distance.value() << '\n';
}

This is a modern example of C++ operator overloading: custom operators examples that improve expressiveness. Your code starts to look like the problem domain: 100.0_m, 5.0_s, 3.0_kg, and so on.

7. Overloading operator<< for logging and debugging

Streaming to std::ostream is one of the most common real examples of C++ operator overloading used in production.

#include <iostream>
#include <string>

struct User {
    std::string name;
    int age{};
};

std::ostream& operator<<(std::ostream& os, const User& user) {
    return os << "User{name=" << user.name << ", age=" << user.age << "}";
}

int main() {
    User u{"Alice", 30};
    std::cout << u << '\n';  // operator<<
}

This pattern is everywhere: logging, debugging, serialization helpers. It’s probably the first example of C++ operator overloading you’ll see in any sizable codebase.

8. Overloading ++ and -- for iterator‑like types

If you build your own iterator or iterator‑like type, operator++ and operator* give you the standard iteration syntax.

#include <cstddef>
#include <iterator>

class Counter {
public:
    using value_type = int;
    using difference_type = std::ptrdiff_t;
    using iterator_category = std::input_iterator_tag;

    explicit Counter(int value = 0) : value_(value) {}

    int operator*() const { return value_; }

    Counter& operator++() { // prefix ++
        ++value_;
        return *this;
    }

    Counter operator++(int) { // postfix ++
        Counter temp = *this;
        ++(*this);
        return temp;
    }

    bool operator==(const Counter& other) const { return value_ == other.value_; }
    bool operator!=(const Counter& other) const { return !(*this == other); }

private:
    int value_;
};

int main() {
    for (Counter c{0}; c != Counter{3}; ++c) {
        int v = *c; // operator*
        // ...
    }
}

Again, this is one of those examples of C++ operator overloading: custom operators examples that align your custom types with the expectations of the STL and range‑based algorithms.

When operator overloading is a good idea (and when it isn’t)

By 2024–2025, the C++ community has largely converged on some shared norms about operator overloading:

  • Use it when the meaning of the operator is obvious from math or existing standard types.
  • Avoid “cute” overloads that surprise readers (for example, using operator+ to do logging, or operator[] to perform network calls).
  • Prefer non‑member overloads when you want implicit conversions on the left‑hand side, like scalar * vector.
  • Keep operations cheap and predictable; if + suddenly allocates a file handle or hits a database, that’s confusing.

Modern guidelines from organizations like the ISO C++ committee and large industrial users (Google, Microsoft, etc.) all point in the same direction: use operator overloading to make code clearer, not just shorter.

Modern style tips for cleaner custom operators

A few quick patterns that show up across the best examples of C++ operator overloading:

  • Mark single‑argument constructors explicit unless you really want implicit conversions.
  • Use = default and = delete to control automatically generated operators and special member functions.
  • For comparison, prefer C++20’s operator<=> plus = default when possible.
  • For containers, provide both const and non‑const operator[].
  • For heavy operations, consider named functions instead of operators; clarity wins.

If you want more in‑depth style guidance, the free C++ Core Guidelines maintained by the ISO C++ community and Microsoft are worth a read:
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines

FAQ: examples of C++ operator overloading and common questions

What are some real examples of C++ operator overloading used in production code?

Real‑world examples include:

  • Arithmetic on geometry types (Vec2, Vec3, matrices) in graphics and game engines.
  • Time and date types that overload +, -, and comparison operators (similar to std::chrono).
  • Money and units libraries that overload arithmetic and comparison to reduce bugs.
  • Smart pointers that overload operator*, operator->, and operator bool.
  • Containers that overload operator[] and iterators that overload operator++ and operator*.

These are exactly the kinds of examples of C++ operator overloading: custom operators examples you’ll see in modern codebases.

Can you give an example of a bad use of operator overloading?

A classic anti‑pattern is overloading operator+ to do something unrelated to addition, like writing to a log file:

Logger log;
log + "User logged in"; // This is confusing and non-obvious.

There’s no natural mapping between + and “log this message,” so the code is clever at the expense of readability. A named function like log.info("User logged in"); is far clearer.

How do I choose between a member and a non‑member operator?

As a rule of thumb:

  • Use a member operator when the left‑hand operand must be of your type and you don’t need implicit conversions on the left.
  • Use a non‑member (often a friend) when you want symmetric behavior or implicit conversions on both sides, like scalar * vector and vector * scalar.

Many of the best examples of C++ operator overloading: custom operators examples in the standard library follow this pattern.

Are there performance costs to operator overloading?

The operators themselves are just functions. If you write them efficiently, they’re as fast as calling a named function. The main performance pitfalls come from hidden allocations, unnecessary copies, or surprising side effects. In modern C++, return‑value optimization and move semantics make idiomatic operator overloading very cheap.

Where can I learn more about idiomatic operator overloading in modern C++?

For deeper reading beyond quick examples of C++ operator overloading: custom operators examples, these resources are worth bookmarking:

  • The C++ Core Guidelines (free, maintained by experts): https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines
  • CPP Reference’s operator overloading overview: https://en.cppreference.com/w/cpp/language/operators
  • MIT’s open C++ course material, which often includes operator overloading examples: https://ocw.mit.edu/

Put simply: operator overloading is a sharp tool. Used thoughtfully, it lets your types behave like first‑class citizens in the language. Misused, it turns your code into a guessing game. The examples of C++ operator overloading: custom operators examples above should give you a solid, modern baseline for doing it right.

Explore More C++ Code Snippets

Discover more examples and insights in this category.

View All C++ Code Snippets