Programming in C++

Object Oriented Programming (OOP)

Overloading Operators for User Defined Types

Gerald Senarclens de Grancy

Where to Declare / Define Operators

Operators having an object of a user defined type on their left hand side may be declared and defined inside in that class / struct

All other operators have to be declared and defined outside

Declaration and Definition Inside Data Type

Advantages
Sometimes conciseness
Possibility to cleanly access private members
Disadvantages
Limited flexibility for binary operators
Can't handle mixed types easily

Declaration and Definition Outside Data Type

Advantages
Consistent declaration of operators with two different operand types
Handles mixed types easier (templates)
More consistent with overloading operators for built-in types
Disadvantages
Access to private members would require friend declaration
Sometimes less concise

Related Conventions

C.over
Overloading and overloaded operators
C.168
Define overloaded operators in the namespace of their operands
C.86
Make == symmetric with respect to operand types and noexcept

Syntax

Definition Inside Class (Struct)

class Name {
public:
  // examples of binary operators defined inside class
  // both operands are class members; LHS is *this, RHS is sole argument
  Name operator+(const Name& other) const noexcept { ... }
  // only the LHS operand is a class member, RHS is sole argument
  Name operator+(int i) const noexcept { ... }
  // unary operator; just uses *this -> no arguments
  Name operator-() const noexcept { ... }

  // ...
};

Standalone Definition

No access to *this => all operands have to be passed as arguments

const may only be used for arguments as there is no *this

// both operands are class members
Name operator+(const Name& lhs, const Name& rhs) noexcept { ... }
// only the LHS operand is a class member
Name operator+(const Name& n, int i) noexcept { ... }
// unary operator
Name operator-(const Name& n) noexcept { ... }

// the following operators have to be defined outside of class / struct
// only the RHS operand is a class member
Name operator+(int i, const Name& n) noexcept { ... }
// the output stream operator cannot be defined in a class / struct
std::ostream& operator<<(std::ostream& out, const Name& n) { ... }

Example: Complex Numbers

// ... header guard etc
#include <iostream>

class Complex {
public:
  explicit Complex(double real, double imag) : real_{real}, imag_{imag} {}
  double real() const { return real_; }
  double imag() const { return imag_; }
  Complex operator+(const Complex& c) const;
  Complex operator+(double d) const { return Complex{real_ + d, imag_}; }
private:
  double real_ = 0.0;
  double imag_ = 0.0;
};
Complex operator+(double d, const Complex& c);  // cannot be declared in class
std::ostream& operator<<(std::ostream& out, const Complex& c);
// ...
Download complex_numbers.hpp
#include "complex_numbers.hpp"

#include <iostream>
// ...

Complex Complex::operator+(const Complex& c) const {
  return Complex{real_ + c.real(), imag_ + c.imag()};
}

Complex operator+(double d, const Complex& c) {
  return c + d;  // returns result of `operator+(double d)` defined in class
}

std::ostream& operator<<(std::ostream& out, const Complex& c) {
  out << c.real() << (c.imag() >= 0 ? "+" : "") << c.imag() << + "j";
  return out;
}
Download complex_numbers.cpp
#include "complex_numbers.hpp"

#include <iostream>

int main() {
  Complex c1{5, 3};
  Complex c2{3, -2};
  std::cout << "c1: " << c1 << ", c2: " << c2 << std::endl;
  std::cout << "c1+c2: " << c1 + c2 << ", c2+c1: " << c2 + c1 << std::endl;
  std::cout << "c1+3: " << c1 + 3 << ", 3+c1: " << 3 + c1 << std::endl;
  return 0;
}
Download complex_main.cpp

Exercise

Please solve the complex numbers exercise on exercism

Questions
and feedback...