Chapter 15

Operator Overloading

Unary and binary operators, equality, comparison, subscript, function operator, user-defined literals, and move assignment.

In this chapter

  1. Unary Operators
  2. Binary Operators
  3. Equality & Comparison
  4. Copy Assignment Operator
  5. Subscript Operator
  6. Function Operator
  7. Move Assignment Operator
  8. Conversion Operators
  9. User-Defined Literals
1

Unary Operators

Unary operators act on a single operand. Examples: ++, --, *, &, !, ~, -, +, and conversion operators.

They can be defined as static/global functions or non-static class members:

Static / Global
// Takes the operand as a parameter
ReturnType operator~(MyType param);
Non-Static Class Member
// No parameter — operates on *this
ReturnType operator~();

Member unary operators operate on themselves using *this. For post-increment, the compiler passes a dummy int parameter to distinguish it from pre-increment:

class Date {
public:
    Date& operator++();      // pre-increment: ++d
    Date  operator++(int);  // post-increment: d++ (dummy int param)
};

2

Binary Operators

Binary operators act on two operands. Examples: &, +, -, /, *, %, !=, +=, -=, *=, /=, =, <<, >>, [].

Global / Static
// Both operands are parameters
Ret operator+(T lhs, T rhs);
Class Member
// Left operand is *this
Ret operator+(T rhs);
class Date {
public:
    // Member: left side is *this, right side is parameter
    Date operator+(int days);
};

Date myDate;
Date later = myDate + 5;  // myDate.operator+(5)
Prefer global/non-member binary operators when you want implicit conversions to apply to both sides (see Chapter 14). Prefer member operators for compound assignment (+=, -=, etc.) since they naturally modify *this.

3

Equality & Comparison

Without an overloaded ==, the compiler performs a binary bitwise comparison by default — which is usually not what you want for class types.

class Date {
public:
    bool operator==(const Date& other) const {
        // compare the fields that define equality
        return day == other.day && month == other.month && year == other.year;
    }
};
Once you have ==, you can implement != for free by inverting it:
return !(this->operator==(other));

Conditional Checks (<, >, <=, >=)

To support full ordering, you need to overload all four comparison operators. As with !=, you can often derive them from each other to reduce duplication.

In C++20, you can define a single operator<=> (the "spaceship operator") and the compiler automatically generates all six comparison operators from it.

4

Copy Assignment Operator

The copy assignment operator (operator=) modifies an existing object to match another. Always guard against self-assignment:

Student& operator=(const Student& source) {
    if (this != &source) {  // self-assignment guard
        // copy source's members into *this
    }
    return *this;         // enables chaining: a = b = c
}
Without the self-assignment check, a = a could free the object's own memory before copying it — causing a crash.

5

Subscript Operator

The subscript operator (operator[]) lets a class behave like an array. You should provide both a mutable and a const version:

class Container {
public:
    // Non-const: caller can modify the returned element
    char& operator[](int index);

    // Const: protects both the returned ref and the class members
    const char& operator[](int index) const;
};

The trailing const on the method prevents the function from modifying any class member values. The leading const on the return type prevents the caller from modifying the returned reference.


6

Function Operator

operator() lets a class instance be called like a function. Such objects are called functors or function objects. The parameter list and return type can be anything:

class Multiplier {
public:
    int operator()(int a, int b) const {
        return a * b;
    }
};

Multiplier mul;
mul(3, 4);  // same as mul.operator()(3, 4) → 12
Functors can carry state (unlike raw function pointers), making them more powerful. They're the foundation of STL algorithms that accept a "comparator" or "predicate" argument. Lambdas in modern C++ are syntactic sugar that generate a functor class under the hood.

7

Move Assignment Operator

Certain workflows — returning a value from a function, passing a temporary — can result in unnecessary calls to the copy constructor. C++11 compilers try to use the move constructor/operator instead if one is implemented, stealing resources rather than copying them:

// Move constructor
Sample(Sample&& source) noexcept;

// Move assignment operator
Sample& operator=(Sample&& source) noexcept;

Both use an rvalue reference (&&). The source is left in a valid but unspecified state after the move (typically empty/null). The structure is similar to copy/assignment — check for self-assignment, transfer ownership, nullify the source.

If your class manages dynamic memory (Rule of Five territory), always implement both move constructor and move assignment to avoid unnecessary heap allocations.

8

Conversion Operators

A conversion operator allows a class to implicitly (or explicitly) convert to another type. The syntax uses operator followed by the target type — no return type is written:

class Temperature {
public:
    operator double() const {  // converts Temperature → double
        return celsius;
    }
private:
    double celsius;
};

Temperature t;
double d = t;  // implicit conversion via operator double()
Conversion operators can cause unexpected implicit conversions. Use the explicit keyword to require an explicit cast instead:

explicit operator double() const;

Then double d = t is a compile error; double d = (double)t or static_cast<double>(t) is required.

9

User-Defined Literals

User-defined literals let you attach meaning to numeric or string literals using a suffix. The operator name must start with an underscore:

// Converts a long double to a Temperature in Celsius
Temperature operator""_C(long double celsius) {
    return Temperature(celsius);
}

Temperature boiling = 100.0_C;  // calls operator""_C(100.0)
The parameter type for numeric UDLs is restricted to a small set: unsigned long long, long double, const char*, etc. You cannot use arbitrary types as the raw parameter.
The C++ standard library uses UDLs for things like 1s (1 second), "hello"s (std::string), and 1i (imaginary number). Suffixes without a leading underscore are reserved for the standard library.
← Chapter 14 ↑ Index Chapter 16 →