# COMP6771 Week 4.1

• Line 26 is our best attempt to "Add two points together and print them"

• This is clumsy and ugly. We'd much prefer to have a semantic like this

std::cout << p1 + p2;

``````#include <iostream>

class Point {
public:
Point(int x, int y) : x_{x}, y_{y} {};
const int& x() const { return this->x_; };
const int& y() const { return this->y_; };
static Point add(const Point& p1, const Point& p2);
private:
int x_;
int y_;
};

void print(std::ostream& os, const Point& p) {
os << "(" << p.x() << "," << p.y() << ")";
}

Point Point::add(const Point& p1, const Point& p2) {
return Point{p1.x() + p2.x(), p1.y() + p2.y()};
}

int main() {
Point p1{1, 2};
Point p2{2, 3};
std::cout << "\n";
}``````

``````#include <iostream>
#include <ostream>

class Point {
public:
Point(int x, int y) : x_{x}, y_{y} {};
const int& x() const { return this->x_; };
const int& y() const { return this->y_; };
static Point add(const Point& p1, const Point& p2);
private:
int x_;
int y_;
};

void print(std::ostream& os, const Point& p) {
os << "(" << p.x() << "," << p.y() << ")";
}

Point Point::add(const Point& p1, const Point& p2) {
return Point{p1.x() + p2.x(), p1.y() + p2.y()};
}

int main() {
Point p1{1, 2};
Point p2{2, 3};
std::cout << "\n";
}``````
``````#include <iostream>
#include <ostream>

class Point {
public:
Point(int x, int y) : x_{x}, y_{y} {};
friend Point operator+(const Point& lhs, const Point& rhs);
friend std::ostream& operator<<(std::ostream& os,
const Point& p);
private:
int x_;
int y_;
};

Point operator+(const Point& lhs, const Point& rhs) {
return Point{lhs.x_ + rhs.x_, lhs.y_ + rhs.y_};
}

std::ostream& operator<<(std::ostream& os, const Point& p) {
os << "(" << p.x_ << "," << p.y_ << ")";
return os;
}

int main() {
Point p1{1, 2};
Point p2{2, 3};
std::cout << p1 + p2 << "\n";
}``````

• C++ supports a rich set of operator overloads
• All operator overloads must have at least one operand of its type
• Reuse existing code semantics
• No verbosity required for simple operations
• Lack of context on operations
• Only create an overload if your type has a single, obvious meaning to an operator

## Operator Overload Design

Type Operator(s) Member / friend
I/O <<, >> friend
Arithmetic +, -, *, / friend
Relational, Equality >, <, >=, <=, ==, != friend
Assignment = member (non-const)
Compound assignment +=, -=, *=, /= member (non-const)
Subscript [] member (both)
Increment/Decrement ++, -- member (non-const)
Arrow, Deference ->, * member (both)
Call () member
• Use members when the operation is called in the context of a particular instance
• Use friends when the operation is called without any particular instance
• Even if they don't require access to private details

``````// Point.h:
#include <ostream>
#include <istream>
class Point {
public:
Point(int x, int y) : x_{x}, y_{y} {};
friend std::ostream& operator<<(std::ostream& os, const Point& type);
friend std::istream& operator>>(std::istream& is, Point& type);

private:
int x_;
int y_;
};

// Point.cpp:
#include <ostream>
#include <istream>
#include <oistream>
std::ostream& operator<<(std::ostream& os, const Point& p) {
os << "(" << p.x_ << "," << p.y_ << ")";
return os;
}
std::istream& operator>>(std::istream& is, Point& type) {
// To be done in tutorials
}

int main() {
Point p{1,2};
std::cout << p << '\n';
}``````

## Overload: Compound assignment

``````// Point.h:
class Point {
public:
Point& operator+=(const Point& p);
Point& operator-=(const Point& p);
Point& operator*=(const Point& p);
Point& operator/=(const Point& p);
Point& operator*=(const int& i);

private:
int x_;
int y_;
};

// Point.cpp:
Point& Point::operator+=(const Point& p) {
this->x_ += p.x_;
this->y_ += p.y_;
return *this;
}
Point& Point::operator-=(const Point& p) { /* Should we do this one? */ }
Point& Point::operator*=(const Point& p) { /* Should we do this one? */ }
Point& Point::operator/=(const Point& p) { /* Should we do this one? */ }
Point& Point::operator*=(const int& p) { /* Should we do this one? */ }
``````

## Overload: Relational & Equality

``````// Point.h:
class Point {
public:
// hidden friend - preferred
friend bool operator==(const Point& p1, const Point& p2) {
return p1.x_ == p2.x_ && p1.y_ == p2.y_;
// return std::tie(p1.x_, p1.y_) == std::tie(p2.x_, p2.y_);
}
friend bool operator!=(const Point& p1, const Point& p2) {
return !(p1 == p2);
}
friend bool operator<(const Point& p1, const Point& p2) {
// Do we want this? Alternatives?
}
friend bool operator<=(const Point& p1, const Point& p2);
friend bool operator>(const Point& p1, const Point& p2);
friend bool operator>=(const Point& p1, const Point& p2);

private:
int x_;
int y_;
};``````

``````// Point.h:
#include <istream>
class Point {
public:
Point& operator=(const Point& p);
Point& operator=(std::istream &is);

private:
int x_;
int y_;
};

// Point.cpp:
#include <istream>
Point& Point::operator=(const Point& p) {
this->x_ = p.x_;
this->y_ = p.y_;
return *this;
}
Point& Point::operator=(std::istream &is) {
// etc
}``````

``````// Point.h:
class Point {
public:
int& operator[](int i);       // setting via []
int  operator[](int i) const; // getting via []

private:
int x_;
int y_;
};

// Point.cpp:
#include <cassert>
int& Point::operator[](int i) {
assert(i == 0 || i == 1);
if (i == 0) return this->x_;
else return this->y_;
};
int Point::operator[](int i) const {
assert(i == 0 || i == 1);
if (i == 0) return this->x_;
else return this->y_;
};``````
• Usually only defined on indexable containers
• Different operator for get/set
• Asserts are the right approach here as preconditions:
• In other containers (e.g. vector), invalid index access is undefined behaviour. Usually an explicit crash is better than undefined behaviour
• Asserts are stripped out of optimisation builds

``````// RoadPosition.h:
public:
RoadPosition(int km) : km_from_sydney_(km) {}
RoadPosition& operator++();      // prefix
// This is *always* an int, no
// matter your type.
RoadPosition operator++(int);   // postfix
void tick();
int km() { return km_from_sydney_; }

private:
void tick_();
int km_from_sydney_;
};

#include <iostream>
this->tick_();
return *this;
}
RoadPosition rp = *this;
this->tick_();
return rp;
}
++(this->km_from_sydney_);
} ``````
• prefix: ++x, --x, returns lvalue reference
• postfix: x++, x--, returns rvalue
• Performance: prefix > postfix
• Different operator for get/set
• Postfix operator takes in an int
• This is not to be used
• It is only for function matching
• Don't name the variable
``````int main() {
std::cout << rp.km() << '\n';
int val1 = (rp++).km();
int val2 = (++rp).km();
std::cout << val1 << '\n';
std::cout << val2 << '\n';
}``````

## Overload: Arrow & Dereferencing

``````#include <iostream>
class StringPtr {
public:
StringPtr(std::string *p) : ptr{p} { }
~StringPtr() { delete ptr; }
std::string* operator->() { return ptr; }
std::string& operator*() { return *ptr; }
private:
std::string *ptr;
};

int main() {
std::string *ps = new std::string{"smart pointer"};
StringPtr p{ps};
std::cout << *p << std::endl;
std::cout << p->size() << std::endl;
}``````
• Classes exhibit pointer-like behaviour when -> is overloaded
• For -> to work it must return a pointer to a class type or an object of a class type that defines its own -> operator
• Interesting example: std::optional

``````// Point.h:
#include <vector>
class Point {
public:
Point(int x, int y) : x_(x), y_(y) {}
operator std::vector<int>() {
std::vector<int> vec;
vec.push_back(x_);
vec.push_back(y_);
return vec;
}

private:
int x_;
int y_;
};

// Point.cpp:
#include <iostream>
#include <vector>
int main() {
Point p{1,2};
std::vector<int> vec = static_cast<std::vector<int>>(p);
std::cout << vec[0] << '\n';
std::cout << vec[1] << '\n';
}``````

By cs6771

• 485