Unit 4.0: Overloading, Templates & Inheritance 🔧

1. Operator Overloading

  • Giving existing C++ operators new meaning for user-defined types. You cannot create new operators or change the number of operands.

Syntax

return_type operator symbol (parameters) {
    // implementation
}

Overloading Arithmetic Operators

#include <iostream>
using namespace std;
 
class Vector2D {
public:
    double x, y;
 
    Vector2D(double x = 0, double y = 0) : x(x), y(y) {}
 
    // + operator
    Vector2D operator+(const Vector2D& other) const {
        return Vector2D(x + other.x, y + other.y);
    }
 
    // - operator
    Vector2D operator-(const Vector2D& other) const {
        return Vector2D(x - other.x, y - other.y);
    }
 
    // * scalar
    Vector2D operator*(double scalar) const {
        return Vector2D(x * scalar, y * scalar);
    }
 
    // == comparison
    bool operator==(const Vector2D& other) const {
        return (x == other.x && y == other.y);
    }
 
    // != comparison
    bool operator!=(const Vector2D& other) const {
        return !(*this == other);
    }
 
    // Unary minus
    Vector2D operator-() const {
        return Vector2D(-x, -y);
    }
 
    void display() const {
        cout << "(" << x << ", " << y << ")" << endl;
    }
};
 
int main() {
    Vector2D v1(3.0, 4.0);
    Vector2D v2(1.0, 2.0);
 
    Vector2D sum = v1 + v2;
    sum.display();          // (4, 6)
 
    Vector2D diff = v1 - v2;
    diff.display();         // (2, 2)
 
    Vector2D scaled = v1 * 2.0;
    scaled.display();       // (6, 8)
 
    cout << (v1 == v2) << endl;  // 0 (false)
 
    Vector2D neg = -v1;
    neg.display();          // (-3, -4)
    return 0;
}

Overloading << and >> (Stream Operators)

#include <iostream>
#include <string>
using namespace std;
 
class Complex {
public:
    double real, imag;
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}
 
    Complex operator+(const Complex& other) const {
        return Complex(real + other.real, imag + other.imag);
    }
 
    // << must be a friend (non-member) to access private data
    friend ostream& operator<<(ostream& out, const Complex& c) {
        out << c.real;
        if (c.imag >= 0) out << " + " << c.imag << "i";
        else             out << " - " << (-c.imag) << "i";
        return out;
    }
 
    // >> must be a friend too
    friend istream& operator>>(istream& in, Complex& c) {
        in >> c.real >> c.imag;
        return in;
    }
};
 
int main() {
    Complex c1(3.0, 4.0);
    Complex c2(1.0, -2.0);
 
    cout << c1 << endl;          // 3 + 4i
    cout << c2 << endl;          // 1 - 2i
    cout << c1 + c2 << endl;     // 4 + 2i
 
    Complex c3;
    cout << "Enter real and imaginary: ";
    // cin >> c3;
    return 0;
}

Overloading ++ and — (Increment/Decrement)

#include <iostream>
using namespace std;
 
class Counter {
    int count;
public:
    Counter(int c = 0) : count(c) {}
 
    // Prefix ++ (++obj)
    Counter& operator++() {
        ++count;
        return *this;
    }
 
    // Postfix ++ (obj++) — dummy int parameter to distinguish
    Counter operator++(int) {
        Counter old = *this;
        ++count;
        return old;    // return value BEFORE increment
    }
 
    // Prefix --
    Counter& operator--() {
        --count;
        return *this;
    }
 
    int getCount() const { return count; }
};
 
int main() {
    Counter c(5);
    cout << (++c).getCount() << endl;  // 6 (pre-increment)
    cout << (c++).getCount() << endl;  // 6 (post-increment: old value)
    cout << c.getCount() << endl;      // 7
    return 0;
}

Overloading [] (Subscript)

#include <iostream>
#include <stdexcept>
using namespace std;
 
class SafeArray {
    int data[100];
    int size;
public:
    SafeArray(int s) : size(s) {
        for (int i = 0; i < s; i++) data[i] = 0;
    }
 
    // Non-const version (can modify)
    int& operator[](int index) {
        if (index < 0 || index >= size)
            throw out_of_range("Index out of bounds!");
        return data[index];
    }
 
    // Const version (read-only)
    const int& operator[](int index) const {
        if (index < 0 || index >= size)
            throw out_of_range("Index out of bounds!");
        return data[index];
    }
};
 
int main() {
    SafeArray arr(5);
    arr[0] = 10;
    arr[2] = 30;
    arr[4] = 50;
 
    for (int i = 0; i < 5; i++)
        cout << arr[i] << " ";    // 10 0 30 0 50
    cout << endl;
 
    // arr[10] = 5;  // throws out_of_range exception
    return 0;
}

Operators That CAN and CANNOT Be Overloaded

Can OverloadCannot Overload
+ - * / %:: (scope resolution)
== != < > <= >=. (member access)
<< >> (stream).* (pointer to member)
[] () ->?: (ternary)
++ -- ! ~sizeof typeid
= += -= etc.static_cast etc.

2. Function Overloading

(Detailed coverage in Unit 3. Here is a quick recap plus advanced aspects.)

Overload Resolution Rules

#include <iostream>
using namespace std;
 
void print(int x)    { cout << "int: " << x << endl; }
void print(double x) { cout << "double: " << x << endl; }
void print(char x)   { cout << "char: " << x << endl; }
void print(int x, int y) { cout << "two ints: " << x << " " << y << endl; }
 
int main() {
    print(42);        // int: 42
    print(3.14);      // double: 3.14
    print('A');       // char: A
    print(1, 2);      // two ints: 1 2
    print(3.14f);     // float → promoted to double
    return 0;
}

3. Function Templates

A function template lets you write a single function that works with any data type. The compiler generates the actual function for each type used.

Basic Function Template

#include <iostream>
using namespace std;
 
// T is a type parameter (placeholder)
template <typename T>
T findMax(T a, T b) {
    return (a > b) ? a : b;
}
 
// Template with multiple type parameters
template <typename T1, typename T2>
void display(T1 a, T2 b) {
    cout << a << " and " << b << endl;
}
 
int main() {
    cout << findMax(3, 7) << endl;         // int: 7
    cout << findMax(3.14, 2.71) << endl;   // double: 3.14
    cout << findMax('a', 'z') << endl;     // char: z
    cout << findMax(string("hello"), string("world")) << endl; // world
 
    display(42, "hello");          // 42 and hello
    display(3.14, true);           // 3.14 and 1
    return 0;
}

Practical Function Templates

#include <iostream>
using namespace std;
 
// Generic swap
template <typename T>
void swapValues(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}
 
// Generic array print
template <typename T>
void printArray(const T arr[], int size) {
    cout << "[ ";
    for (int i = 0; i < size; i++) {
        cout << arr[i];
        if (i < size - 1) cout << ", ";
    }
    cout << " ]" << endl;
}
 
// Generic binary search
template <typename T>
int binarySearch(const T arr[], int size, T target) {
    int low = 0, high = size - 1;
    while (low <= high) {
        int mid = (low + high) / 2;
        if (arr[mid] == target)   return mid;
        else if (arr[mid] < target) low = mid + 1;
        else                        high = mid - 1;
    }
    return -1;
}
 
int main() {
    int a = 5, b = 10;
    swapValues(a, b);
    cout << a << " " << b << endl;   // 10 5
 
    double x = 1.1, y = 2.2;
    swapValues(x, y);
    cout << x << " " << y << endl;   // 2.2 1.1
 
    int nums[] = {1, 3, 5, 7, 9, 11};
    printArray(nums, 6);   // [ 1, 3, 5, 7, 9, 11 ]
 
    cout << binarySearch(nums, 6, 7) << endl;   // 3 (index)
    cout << binarySearch(nums, 6, 4) << endl;   // -1 (not found)
    return 0;
}

Template Specialization

#include <iostream>
#include <cstring>
using namespace std;
 
// General template
template <typename T>
T findMax(T a, T b) {
    return (a > b) ? a : b;
}
 
// Specialization for C-strings
template <>
const char* findMax<const char*>(const char* a, const char* b) {
    return (strcmp(a, b) > 0) ? a : b;
}
 
int main() {
    cout << findMax(10, 20) << endl;                     // 20
    cout << findMax("banana", "apple") << endl;          // banana
    return 0;
}

4. Class Templates

A class template creates a blueprint for a class that works with any type.

Stack Implementation (Class Template)

#include <iostream>
#include <stdexcept>
using namespace std;
 
template <typename T, int MAX_SIZE = 100>
class Stack {
private:
    T data[MAX_SIZE];
    int top;
 
public:
    Stack() : top(-1) {}
 
    void push(const T& value) {
        if (top >= MAX_SIZE - 1)
            throw overflow_error("Stack overflow!");
        data[++top] = value;
    }
 
    T pop() {
        if (isEmpty())
            throw underflow_error("Stack underflow!");
        return data[top--];
    }
 
    T peek() const {
        if (isEmpty())
            throw underflow_error("Stack is empty!");
        return data[top];
    }
 
    bool isEmpty() const { return top == -1; }
    int  size()    const { return top + 1; }
};
 
int main() {
    // Integer stack
    Stack<int> intStack;
    intStack.push(10);
    intStack.push(20);
    intStack.push(30);
    cout << intStack.pop() << endl;    // 30
    cout << intStack.peek() << endl;   // 20
 
    // String stack
    Stack<string> strStack;
    strStack.push("Hello");
    strStack.push("World");
    cout << strStack.pop() << endl;    // World
 
    // Stack with custom size
    Stack<double, 5> smallStack;
    smallStack.push(1.1);
    smallStack.push(2.2);
    cout << smallStack.size() << endl; // 2
 
    return 0;
}

Pair Class Template

#include <iostream>
#include <string>
using namespace std;
 
template <typename T1, typename T2>
class Pair {
public:
    T1 first;
    T2 second;
 
    Pair(T1 f, T2 s) : first(f), second(s) {}
 
    void display() const {
        cout << "(" << first << ", " << second << ")" << endl;
    }
 
    // Swap the pair
    Pair<T2, T1> swapped() const {
        return Pair<T2, T1>(second, first);
    }
};
 
int main() {
    Pair<int, string> p1(1, "Alice");
    Pair<double, bool> p2(3.14, true);
 
    p1.display();   // (1, Alice)
    p2.display();   // (3.14, 1)
    return 0;
}

5. Single Inheritance

A derived class inherits from one base class. It gets all public/protected members of the base class.

Syntax

class Derived : access_specifier Base {
    // additional members
};

Inheritance Access Specifiers

Base memberpublic inheritanceprotected inheritanceprivate inheritance
publicpublicprotectedprivate
protectedprotectedprotectedprivate
privateinaccessibleinaccessibleinaccessible

Basic Single Inheritance

#include <iostream>
#include <string>
using namespace std;
 
class Vehicle {
protected:
    string brand;
    int year;
    double speed;
 
public:
    Vehicle(string b, int y, double s) : brand(b), year(y), speed(s) {}
 
    void accelerate(double amount) { speed += amount; }
    void brake(double amount)      { speed = max(0.0, speed - amount); }
 
    virtual void display() const {
        cout << brand << " (" << year << ") | Speed: " << speed << " km/h" << endl;
    }
 
    virtual ~Vehicle() {}
};
 
class Car : public Vehicle {
private:
    int doors;
    string fuelType;
 
public:
    Car(string brand, int year, int doors, string fuel)
        : Vehicle(brand, year, 0), doors(doors), fuelType(fuel) {}
 
    void display() const override {
        Vehicle::display();    // call base class version
        cout << "  Doors: " << doors << " | Fuel: " << fuelType << endl;
    }
};
 
class Motorcycle : public Vehicle {
private:
    bool hasSidecar;
 
public:
    Motorcycle(string brand, int year, bool sidecar = false)
        : Vehicle(brand, year, 0), hasSidecar(sidecar) {}
 
    void display() const override {
        Vehicle::display();
        cout << "  Sidecar: " << (hasSidecar ? "Yes" : "No") << endl;
    }
};
 
int main() {
    Car car("Toyota", 2022, 4, "Petrol");
    car.accelerate(60);
    car.display();
 
    Motorcycle moto("Harley-Davidson", 2021, true);
    moto.accelerate(100);
    moto.display();
    return 0;
}

Calling Base Class Constructor

#include <iostream>
using namespace std;
 
class Shape {
protected:
    string color;
public:
    Shape(string c) : color(c) {
        cout << "Shape constructor: " << color << endl;
    }
    virtual ~Shape() {
        cout << "Shape destructor" << endl;
    }
};
 
class Circle : public Shape {
    double radius;
public:
    Circle(string color, double r)
        : Shape(color), radius(r) {     // explicitly call base constructor
        cout << "Circle constructor: r=" << radius << endl;
    }
    ~Circle() {
        cout << "Circle destructor" << endl;
    }
    double area() const { return 3.14159 * radius * radius; }
};
 
int main() {
    Circle c("Red", 5.0);
    cout << "Area: " << c.area() << endl;
    return 0;
}
/*
Shape constructor: Red
Circle constructor: r=5
Area: 78.5397
Circle destructor
Shape destructor
*/

6. Multiple Inheritance

A derived class inherits from two or more base classes.

class Derived : public Base1, public Base2 { ... };

Example

#include <iostream>
#include <string>
using namespace std;
 
class Flyable {
public:
    void fly() const {
        cout << "Flying at altitude 10,000 ft" << endl;
    }
    virtual ~Flyable() {}
};
 
class Swimmable {
public:
    void swim() const {
        cout << "Swimming at 5 knots" << endl;
    }
    virtual ~Swimmable() {}
};
 
class Walkable {
public:
    void walk() const {
        cout << "Walking on land" << endl;
    }
    virtual ~Walkable() {}
};
 
// Duck inherits from all three
class Duck : public Flyable, public Swimmable, public Walkable {
    string name;
public:
    Duck(string n) : name(n) {}
    void display() const {
        cout << name << " can:" << endl;
        fly();
        swim();
        walk();
    }
};
 
int main() {
    Duck donald("Donald");
    donald.display();
    return 0;
}

Ambiguity in Multiple Inheritance

#include <iostream>
using namespace std;
 
class A {
public:
    void show() { cout << "A::show()" << endl; }
};
 
class B {
public:
    void show() { cout << "B::show()" << endl; }
};
 
class C : public A, public B {
public:
    void show() {
        A::show();    // resolve ambiguity with scope ::
        B::show();
    }
    void showA() { A::show(); }
    void showB() { B::show(); }
};
 
int main() {
    C obj;
    obj.show();    // calls C's version
    // obj.A::show();    // directly call A's version
    // obj.B::show();    // directly call B's version
    return 0;
}

7. Virtual Base Class

Solves the Diamond Problem in multiple inheritance (where a base class is inherited twice).

The Diamond Problem

        Animal
       /      \
    Dog       Cat
       \      /
        DogCat      ← Animal is inherited TWICE → ambiguity!

Without Virtual (Problem)

class Animal { public: string name = "Animal"; };
class Dog : public Animal {};
class Cat : public Animal {};
class DogCat : public Dog, public Cat {};
 
// DogCat obj;
// obj.name;   // ERROR: ambiguous — Dog::Animal::name or Cat::Animal::name?

With Virtual Base Class (Solution)

#include <iostream>
#include <string>
using namespace std;
 
class Animal {
public:
    string name;
    int age;
 
    Animal(string n = "Animal", int a = 0) : name(n), age(a) {}
 
    void breathe() const {
        cout << name << " is breathing." << endl;
    }
};
 
class Dog : virtual public Animal {   // virtual inheritance
public:
    Dog(string n, int a) : Animal(n, a) {}
    void bark() const { cout << name << " says: Woof!" << endl; }
};
 
class Cat : virtual public Animal {   // virtual inheritance
public:
    Cat(string n, int a) : Animal(n, a) {}
    void meow() const { cout << name << " says: Meow!" << endl; }
};
 
// Now only ONE copy of Animal
class DogCat : public Dog, public Cat {
public:
    DogCat(string n, int a) : Animal(n, a), Dog(n, a), Cat(n, a) {}
    // Must initialize Animal directly in most-derived class
};
 
int main() {
    DogCat hybrid("Hybrid", 3);
    hybrid.breathe();          // No ambiguity!
    hybrid.bark();             // Woof!
    hybrid.meow();             // Meow!
    cout << hybrid.name << endl;  // Hybrid (single copy)
    return 0;
}

8. Abstract Class

A class that cannot be instantiated — it exists only to be a base class. Contains at least one pure virtual function (= 0).

#include <iostream>
#include <string>
#include <cmath>
using namespace std;
 
// Abstract base class
class Shape {
protected:
    string color;
public:
    Shape(string c = "White") : color(c) {}
 
    // Pure virtual functions — MUST be overridden in derived classes
    virtual double area()      const = 0;
    virtual double perimeter() const = 0;
    virtual string name()      const = 0;
 
    // Regular virtual function — CAN be overridden
    virtual void display() const {
        cout << name() << " [" << color << "]"
             << " | Area: " << area()
             << " | Perimeter: " << perimeter() << endl;
    }
 
    virtual ~Shape() {}
};
 
class Circle : public Shape {
    double radius;
public:
    Circle(double r, string c = "White") : Shape(c), radius(r) {}
    double area()      const override { return 3.14159 * radius * radius; }
    double perimeter() const override { return 2 * 3.14159 * radius; }
    string name()      const override { return "Circle"; }
};
 
class Rectangle : public Shape {
    double l, w;
public:
    Rectangle(double l, double w, string c = "White") : Shape(c), l(l), w(w) {}
    double area()      const override { return l * w; }
    double perimeter() const override { return 2 * (l + w); }
    string name()      const override { return "Rectangle"; }
};
 
class Triangle : public Shape {
    double a, b, c;
public:
    Triangle(double a, double b, double c, string col = "White")
        : Shape(col), a(a), b(b), c(c) {}
    double area() const override {
        double s = (a + b + c) / 2;
        return sqrt(s * (s-a) * (s-b) * (s-c));  // Heron's formula
    }
    double perimeter() const override { return a + b + c; }
    string name()      const override { return "Triangle"; }
};
 
int main() {
    // Shape s;  // ERROR! Cannot instantiate abstract class
 
    Shape* shapes[] = {
        new Circle(5.0, "Red"),
        new Rectangle(4.0, 6.0, "Blue"),
        new Triangle(3.0, 4.0, 5.0, "Green")
    };
 
    for (Shape* s : shapes) {
        s->display();
    }
 
    for (Shape* s : shapes) delete s;
    return 0;
}

9. Pointer and Inheritance

Pointers to base classes are fundamental to polymorphism in C++.

Base Class Pointer to Derived Object

#include <iostream>
using namespace std;
 
class Animal {
public:
    virtual void sound()   const { cout << "..."; }
    virtual string type()  const { return "Animal"; }
    virtual ~Animal() {}
};
 
class Dog  : public Animal {
public:
    void sound()  const override { cout << "Woof!" << endl; }
    string type() const override { return "Dog"; }
};
 
class Cat  : public Animal {
public:
    void sound()  const override { cout << "Meow!" << endl; }
    string type() const override { return "Cat"; }
};
 
class Bird : public Animal {
public:
    void sound()  const override { cout << "Tweet!" << endl; }
    string type() const override { return "Bird"; }
};
 
int main() {
    // Array of base class pointers pointing to derived objects
    Animal* zoo[] = {
        new Dog(),
        new Cat(),
        new Bird(),
        new Dog()
    };
 
    for (Animal* a : zoo) {
        cout << a->type() << ": ";
        a->sound();
    }
 
    for (Animal* a : zoo) delete a;
    return 0;
}

dynamic_cast for Safe Downcasting

#include <iostream>
using namespace std;
 
class Base {
public:
    virtual void info() const { cout << "Base" << endl; }
    virtual ~Base() {}
};
 
class Derived : public Base {
public:
    void info() const override { cout << "Derived" << endl; }
    void specialFunction() const { cout << "Special!" << endl; }
};
 
int main() {
    Base* bptr = new Derived();
 
    // Upcast: always safe (implicit)
    // Derived → Base
 
    // Downcast: use dynamic_cast for safety
    Derived* dptr = dynamic_cast<Derived*>(bptr);
    if (dptr) {
        dptr->specialFunction();   // Safe!
    } else {
        cout << "Cast failed!" << endl;
    }
 
    // Unsafe C-style cast (avoid!)
    // Derived* d2 = (Derived*)bptr;  // not checked at runtime
 
    delete bptr;
    return 0;
}

Polymorphic Container Example

#include <iostream>
#include <vector>
#include <memory>    // for smart pointers
using namespace std;
 
class Employee {
protected:
    string name;
    double baseSalary;
public:
    Employee(string n, double s) : name(n), baseSalary(s) {}
    virtual double calculatePay() const = 0;
    virtual void display() const {
        cout << name << " | Pay: $" << calculatePay() << endl;
    }
    virtual ~Employee() {}
};
 
class FullTime : public Employee {
public:
    FullTime(string n, double s) : Employee(n, s) {}
    double calculatePay() const override { return baseSalary; }
};
 
class PartTime : public Employee {
    int hoursWorked;
public:
    PartTime(string n, double rate, int hours)
        : Employee(n, rate), hoursWorked(hours) {}
    double calculatePay() const override { return baseSalary * hoursWorked; }
};
 
class Commission : public Employee {
    double sales, rate;
public:
    Commission(string n, double s, double sales, double rate)
        : Employee(n, s), sales(sales), rate(rate) {}
    double calculatePay() const override { return baseSalary + sales * rate; }
};
 
int main() {
    // Smart pointers — no manual delete needed!
    vector<unique_ptr<Employee>> staff;
    staff.push_back(make_unique<FullTime>("Alice", 5000));
    staff.push_back(make_unique<PartTime>("Bob", 20, 80));
    staff.push_back(make_unique<Commission>("Carol", 2000, 10000, 0.05));
 
    double total = 0;
    for (const auto& emp : staff) {
        emp->display();
        total += emp->calculatePay();
    }
    cout << "Total payroll: $" << total << endl;
    return 0;
}

10. Overloading Member Functions

Member functions can be overloaded within a class, and inherited ones can be overridden in derived classes.

Overloading Inside a Class

#include <iostream>
#include <string>
using namespace std;
 
class Printer {
public:
    // Overloaded print — same name, different types
    void print(int x) {
        cout << "Integer: " << x << endl;
    }
    void print(double x) {
        cout << "Double: " << x << endl;
    }
    void print(const string& s) {
        cout << "String: " << s << endl;
    }
    void print(int x, int y) {
        cout << "Two ints: " << x << ", " << y << endl;
    }
    void print(const string& label, int value) {
        cout << label << ": " << value << endl;
    }
};
 
int main() {
    Printer p;
    p.print(42);
    p.print(3.14);
    p.print(string("Hello"));
    p.print(10, 20);
    p.print("Score", 95);
    return 0;
}

Overriding vs Overloading

#include <iostream>
using namespace std;
 
class Base {
public:
    void display() {                          // version 1
        cout << "Base::display()" << endl;
    }
    void display(int x) {                     // version 2 — overloaded in Base
        cout << "Base::display(int): " << x << endl;
    }
    virtual void info() const {
        cout << "Base::info()" << endl;
    }
};
 
class Derived : public Base {
public:
    using Base::display;    // bring Base::display into scope
 
    void display(double d) {                  // new overload in Derived
        cout << "Derived::display(double): " << d << endl;
    }
 
    void info() const override {              // OVERRIDING — replaces Base::info
        cout << "Derived::info()" << endl;
    }
};
 
int main() {
    Derived d;
    d.display();         // Base::display() (inherited via using)
    d.display(10);       // Base::display(int)
    d.display(3.14);     // Derived::display(double)
    d.info();            // Derived::info() (overriding)
 
    Base* bptr = &d;
    bptr->info();        // Derived::info() — virtual dispatch
    return 0;
}

Quick Reference Summary

Template Syntax

// Function template
template <typename T>
T funcName(T param) { ... }
 
// Class template
template <typename T>
class ClassName {
    T data;
public:
    void method(T val) { ... }
};
 
// Usage
ClassName<int>    obj1;
ClassName<string> obj2;

Inheritance Cheat Sheet

class Derived : public Base {};     // public: most common
class Derived : protected Base {};  // protected: members become protected
class Derived : private Base {};    // private: members become private
 
// Multiple
class Derived : public A, public B {};
 
// Virtual (diamond fix)
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
 
// Abstract class
class Abstract {
    virtual void method() = 0;  // pure virtual
};

Operator Overloading Rules

RuleDetail
Cannot create new operatorsOnly overload existing ones
Cannot change precedence* still has higher precedence than +
Cannot change associativityLeft-to-right stays left-to-right
At least one operand must be user-defined typeCannot redefine int + int
<< and >> must be friendsNeed access to private data
= () [] -> must be membersCannot be free functions