Skip to content

C++ Overloading

Overview

Overloading is an important feature of C++ that allows multiple functions or operators with the same name to be defined within the same scope, but with different parameter lists. Overloading provides programming flexibility and code readability, including function overloading and operator overloading.

🔧 Function Overloading

Basic Function Overloading

cpp
#include <iostream>
#include <string>
#include <vector>

class Calculator {
public:
    // Integer addition
    int add(int a, int b) {
        std::cout << "Integer addition: ";
        return a + b;
    }
    
    // Floating point addition
    double add(double a, double b) {
        std::cout << "Floating point addition: ";
        return a + b;
    }
    
    // Three parameter addition
    int add(int a, int b, int c) {
        std::cout << "Three number addition: ";
        return a + b + c;
    }
    
    // String concatenation
    std::string add(const std::string& a, const std::string& b) {
        std::cout << "String concatenation: ";
        return a + b;
    }
    
    // Array sum
    int add(const std::vector<int>& numbers) {
        std::cout << "Array sum: ";
        int sum = 0;
        for (int num : numbers) {
            sum += num;
        }
        return sum;
    }
};

int main() {
    std::cout << "=== Function Overloading ===" << std::endl;
    
    Calculator calc;
    
    std::cout << calc.add(5, 3) << std::endl;
    std::cout << calc.add(2.5, 3.7) << std::endl;
    std::cout << calc.add(1, 2, 3) << std::endl;
    std::cout << calc.add(std::string("Hello"), std::string(" World")) << std::endl;
    
    std::vector<int> nums = {1, 2, 3, 4, 5};
    std::cout << calc.add(nums) << std::endl;
    
    return 0;
}

Overloading Resolution Rules

cpp
#include <iostream>

void print(int x) {
    std::cout << "print(int): " << x << std::endl;
}

void print(double x) {
    std::cout << "print(double): " << x << std::endl;
}

void print(const char* x) {
    std::cout << "print(const char*): " << x << std::endl;
}

void print(const std::string& x) {
    std::cout << "print(string): " << x << std::endl;
}

// const overloading
void process(int& x) {
    std::cout << "process(int&): " << x << std::endl;
    x *= 2;
}

void process(const int& x) {
    std::cout << "process(const int&): " << x << std::endl;
}

int main() {
    std::cout << "=== Overloading Resolution ===" << std::endl;
    
    // Exact match
    print(42);              // int
    print(3.14);            // double
    print("Hello");         // const char*
    print(std::string("World")); // string
    
    // Type conversion
    print(3.14f);           // float -> double
    print('A');             // char -> int
    
    // const overloading
    int mutable_var = 10;
    const int const_var = 20;
    
    process(mutable_var);   // Call non-const version
    process(const_var);     // Call const version
    process(100);           // Temporary object, call const version
    
    std::cout << "Modified value: " << mutable_var << std::endl;
    
    return 0;
}

⚙️ Operator Overloading

Basic Operator Overloading

cpp
#include <iostream>

class Complex {
private:
    double real_, imag_;
    
public:
    Complex(double real = 0, double imag = 0) : real_(real), imag_(imag) {}
    
    // Addition operator overloading (member function)
    Complex operator+(const Complex& other) const {
        return Complex(real_ + other.real_, imag_ + other.imag_);
    }
    
    // Subtraction operator overloading
    Complex operator-(const Complex& other) const {
        return Complex(real_ - other.real_, imag_ - other.imag_);
    }
    
    // Multiplication operator overloading
    Complex operator*(const Complex& other) const {
        return Complex(real_ * other.real_ - imag_ * other.imag_,
                      real_ * other.imag_ + imag_ * other.real_);
    }
    
    // Assignment operator overloading
    Complex& operator=(const Complex& other) {
        if (this != &other) {
            real_ = other.real_;
            imag_ = other.imag_;
        }
        return *this;
    }
    
    // Compound assignment operators
    Complex& operator+=(const Complex& other) {
        real_ += other.real_;
        imag_ += other.imag_;
        return *this;
    }
    
    // Comparison operators
    bool operator==(const Complex& other) const {
        return real_ == other.real_ && imag_ == other.imag_;
    }
    
    bool operator!=(const Complex& other) const {
        return !(*this == other);
    }
    
    // Unary operators
    Complex operator-() const {
        return Complex(-real_, -imag_);
    }
    
    Complex& operator++() {  // Prefix ++
        ++real_;
        return *this;
    }
    
    Complex operator++(int) {  // Postfix ++
        Complex temp = *this;
        ++real_;
        return temp;
    }
    
    // Accessors
    double real() const { return real_; }
    double imag() const { return imag_; }
    
    void display() const {
        std::cout << real_;
        if (imag_ >= 0) std::cout << " + ";
        else std::cout << " - ";
        std::cout << std::abs(imag_) << "i";
    }
};

// Friend function overloads output operator
std::ostream& operator<<(std::ostream& os, const Complex& c) {
    os << c.real();
    if (c.imag() >= 0) os << " + ";
    else os << " - ";
    os << std::abs(c.imag()) << "i";
    return os;
}

int main() {
    std::cout << "=== Operator Overloading ===" << std::endl;
    
    Complex c1(3, 4);
    Complex c2(1, 2);
    
    std::cout << "c1 = " << c1 << std::endl;
    std::cout << "c2 = " << c2 << std::endl;
    
    // Arithmetic operations
    Complex c3 = c1 + c2;
    std::cout << "c1 + c2 = " << c3 << std::endl;
    
    Complex c4 = c1 - c2;
    std::cout << "c1 - c2 = " << c4 << std::endl;
    
    Complex c5 = c1 * c2;
    std::cout << "c1 * c2 = " << c5 << std::endl;
    
    // Compound assignment
    c1 += c2;
    std::cout << "c1 += c2: " << c1 << std::endl;
    
    // Unary operators
    Complex c6 = -c2;
    std::cout << "-c2 = " << c6 << std::endl;
    
    // Increment operators
    std::cout << "++c2 = " << ++c2 << std::endl;
    std::cout << "c2++ = " << c2++ << ", c2 = " << c2 << std::endl;
    
    return 0;
}

Subscript and Function Call Operators

cpp
#include <iostream>
#include <vector>
#include <stdexcept>

class Matrix {
private:
    std::vector<std::vector<int>> data_;
    size_t rows_, cols_;
    
public:
    Matrix(size_t rows, size_t cols, int init_value = 0) 
        : rows_(rows), cols_(cols) {
        data_.resize(rows_, std::vector<int>(cols_, init_value));
    }
    
    // Subscript operator overloading
    std::vector<int>& operator[](size_t row) {
        return data_[row];
    }
    
    const std::vector<int>& operator[](size_t row) const {
        return data_[row];
    }
    
    // Function call operator overloading
    int& operator()(size_t row, size_t col) {
        if (row >= rows_ || col >= cols_) {
            throw std::out_of_range("Matrix index out of range");
        }
        return data_[row][col];
    }
    
    const int& operator()(size_t row, size_t col) const {
        if (row >= rows_ || col >= cols_) {
            throw std::out_of_range("Matrix index out of range");
        }
        return data_[row][col];
    }
    
    void display() const {
        for (size_t i = 0; i < rows_; ++i) {
            for (size_t j = 0; j < cols_; ++j) {
                std::cout << data_[i][j] << " ";
            }
            std::cout << std::endl;
        }
    }
    
    size_t rows() const { return rows_; }
    size_t cols() const { return cols_; }
};

// Function object example
class Multiplier {
private:
    int factor_;
    
public:
    Multiplier(int factor) : factor_(factor) {}
    
    // Function call operator
    int operator()(int value) const {
        return value * factor_;
    }
};

int main() {
    std::cout << "=== Subscript and Function Call Operators ===" << std::endl;
    
    // Matrix example
    Matrix matrix(3, 3);
    
    // Use subscript operator
    matrix[0][0] = 1;
    matrix[1][1] = 5;
    matrix[2][2] = 9;
    
    // Use function call operator
    matrix(0, 1) = 2;
    matrix(1, 0) = 4;
    
    std::cout << "Matrix content:" << std::endl;
    matrix.display();
    
    // Function object
    Multiplier times3(3);
    std::cout << "5 * 3 = " << times3(5) << std::endl;
    std::cout << "7 * 3 = " << times3(7) << std::endl;
    
    return 0;
}

Type Conversion Operators

cpp
#include <iostream>
#include <string>

class Temperature {
private:
    double celsius_;
    
public:
    explicit Temperature(double celsius) : celsius_(celsius) {}
    
    // Type conversion operator
    operator double() const {
        return celsius_;
    }
    
    operator int() const {
        return static_cast<int>(celsius_);
    }
    
    operator std::string() const {
        return std::to_string(celsius_) + "°C";
    }
    
    // Explicit conversion function
    double toFahrenheit() const {
        return celsius_ * 9.0 / 5.0 + 32.0;
    }
    
    double toKelvin() const {
        return celsius_ + 273.15;
    }
    
    double getCelsius() const { return celsius_; }
};

class Distance {
private:
    double meters_;
    
public:
    explicit Distance(double meters) : meters_(meters) {}
    
    // Constructor can serve as conversion function
    static Distance fromKilometers(double km) {
        return Distance(km * 1000);
    }
    
    static Distance fromMiles(double miles) {
        return Distance(miles * 1609.34);
    }
    
    double toKilometers() const { return meters_ / 1000; }
    double toMiles() const { return meters_ / 1609.34; }
    double getMeters() const { return meters_; }
    
    // Type conversion operator
    operator double() const { return meters_; }
};

int main() {
    std::cout << "=== Type Conversion Operators ===" << std::endl;
    
    Temperature temp(25.5);
    
    // Implicit type conversion
    double temp_double = temp;
    int temp_int = temp;
    std::string temp_str = temp;
    
    std::cout << "Temperature(double): " << temp_double << std::endl;
    std::cout << "Temperature(int): " << temp_int << std::endl;
    std::cout << "Temperature(string): " << temp_str << std::endl;
    
    // Explicit conversion
    std::cout << "Fahrenheit: " << temp.toFahrenheit() << "°F" << std::endl;
    std::cout << "Kelvin: " << temp.toKelvin() << "K" << std::endl;
    
    // Distance conversion
    Distance d1(1000);  // 1000 meters
    Distance d2 = Distance::fromKilometers(2.5);  // 2.5 kilometers
    
    std::cout << "d1: " << d1.getMeters() << " meters" << std::endl;
    std::cout << "d2: " << d2.toKilometers() << " kilometers" << std::endl;
    
    double total_meters = d1 + d2;  // Implicit conversion to double
    std::cout << "Total distance: " << total_meters << " meters" << std::endl;
    
    return 0;
}

📋 Overloading Rules and Best Practices

Operator Overloading Guidelines

cpp
#include <iostream>
#include <string>

class Point {
private:
    double x_, y_;
    
public:
    Point(double x = 0, double y = 0) : x_(x), y_(y) {}
    
    // Example of overloadable operators
    Point operator+(const Point& other) const {
        return Point(x_ + other.x_, y_ + other.y_);
    }
    
    Point& operator+=(const Point& other) {
        x_ += other.x_;
        y_ += other.y_;
        return *this;
    }
    
    bool operator==(const Point& other) const {
        return x_ == other.x_ && y_ == other.y_;
    }
    
    // Friend function for symmetric operators
    friend Point operator*(double scale, const Point& p) {
        return Point(scale * p.x_, scale * p.y_);
    }
    
    Point operator*(double scale) const {
        return Point(scale * x_, scale * y_);
    }
    
    // Stream operators
    friend std::ostream& operator<<(std::ostream& os, const Point& p) {
        os << "(" << p.x_ << ", " << p.y_ << ")";
        return os;
    }
    
    friend std::istream& operator>>(std::istream& is, Point& p) {
        is >> p.x_ >> p.y_;
        return is;
    }
    
    double getX() const { return x_; }
    double getY() const { return y_; }
};

int main() {
    std::cout << "=== Operator Overloading Best Practices ===" << std::endl;
    
    Point p1(3, 4);
    Point p2(1, 2);
    
    std::cout << "p1 = " << p1 << std::endl;
    std::cout << "p2 = " << p2 << std::endl;
    
    // Arithmetic operations
    Point p3 = p1 + p2;
    std::cout << "p1 + p2 = " << p3 << std::endl;
    
    // Compound assignment
    p1 += p2;
    std::cout << "p1 += p2: " << p1 << std::endl;
    
    // Scalar multiplication
    Point p4 = p2 * 2.0;
    Point p5 = 3.0 * p2;
    std::cout << "p2 * 2.0 = " << p4 << std::endl;
    std::cout << "3.0 * p2 = " << p5 << std::endl;
    
    // Comparison
    std::cout << "p4 == p5: " << (p4 == p5) << std::endl;
    
    std::cout << "\n=== Overloading Rules ===" << std::endl;
    std::cout << "✓ Overloadable: +, -, *, /, %, ^, &, |, ~, !, =, <, >, +=, -=, *=, /=, %=, ^=, &=, |=, <<, >>, >>=, <<=, ==, !=, <=, >=, &&, ||, ++, --, ,, ->*, ->, (), []" << std::endl;
    std::cout << "✗ Non-overloadable: ::, ., .*, ?:" << std::endl;
    std::cout << "📌 Suggestion: Maintain intuitive meaning of operators" << std::endl;
    std::cout << "📌 Suggestion: Use friend functions for symmetric operators" << std::endl;
    std::cout << "📌 Suggestion: Compound assignment operators should return references" << std::endl;
    
    return 0;
}

Summary

Overloading is an important mechanism in C++ for providing code flexibility and readability:

Overloading Types

  • Function overloading: Same name, different parameters
  • Operator overloading: Define operator behavior for custom types
  • Conversion overloading: Automatic or explicit conversion between types

Overloading Resolution Priority

  1. Exact match
  2. Promotion conversion (such as char to int)
  3. Standard conversion (such as int to double)
  4. User-defined conversion
  5. Ellipsis match

Best Practices

  • Maintain intuitive semantics of operators
  • Use friend functions for symmetric operators
  • Compound assignment operators should return references
  • Avoid overloading &&, ||, comma operators
  • Use implicit type conversion cautiously

Overloading makes C++ code more natural and intuitive, and is an important tool for building user-friendly APIs.

Content is for learning and research only.