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
- Exact match
- Promotion conversion (such as char to int)
- Standard conversion (such as int to double)
- User-defined conversion
- 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.