COMP6771 Advanced C++ Programming
Week 4.1 Operator Overloading
1
Why?
In this lecture
Operator overloads allow you to decrease your code complexity and utilise well defined semantics.
What?
Many different types of operator overloads
2
Start with an example
Line 32 is our best attempt to “Add two points together and print them”
print(std::cout, point::add(p1, p2));
This is clumsy and ugly. We’d much prefer to have a semantic like this
std::cout << p1 + p2;
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include
class point {
public:
point(int x, int y)
: x_{x}
, y_{y} {};
[[nodiscard]] int const x() const {
return this->x_; }
[[nodiscard]] int const y() const {
return this->y_; }
static point add(point const& p1, point const& p2);
private:
int x_;
int y_;
void print(std::ostream& os, point const& p) {
os << "(" << p.x() << "," << p.y() << ")";
point point::add(point const& p1, point const& p2) {
return point{p1.x() + p2.x(), p1.y() + p2.y()};
}
auto main() -> int {
point p1{1, 2};
point p2{2, 3};
};
}
}
print(std::cout, point::add(p1, p2));
std::cout << "\n";
lecture-3/demo401-point1.cpp
3.1
Start with an example
Using operator overloading:
Allows us to use currently understood semantics (all of the operators!)
Gives us a common and simple interface to define class methods
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include
class point {
public:
point(int x, int y)
: x_{x}
, y_{y} {};
friend point operator+(point const& lhs,
point const& rhs);
friend std::ostream& operator<<(std::ostream& os,
point const& p);
private:
int x_;
int y_;
};
point operator+(point const& lhs, point const& rhs) {
return point(lhs.x_ + rhs.x_, lhs.y_ + rhs.y_);
}
std::ostream& operator<<(std::ostream& os, point const& p) {
os << "(" << p.x_ << "," << p.y_ << ")";
return os;
}
auto main() -> int {
point p1{1, 2};
point p2{2, 3};
}
std::cout << p1 + p2 << "\n";
lecture-3/demo402-point2.cpp
3.2
Operator Overloading
C++ supports a rich set of operator overloads
All operator overloads must have at least one operand of its type
Advantages:
Reuse existing code semantics
No verbosity required for simple operations
Disadvantages:
Lack of context on operations
Only create an overload if your type has a single, obvious meaning to an operator
4.1
Friends
A class may declare friend functions or classes
Those functions / classes are non-member functions that may access private parts of the class
This is, in general, a bad idea, but there are a few cases where it may be required
Nonmember operator overloads (will be discussing soon) Related classes
A Window class might have WindowManager as a friend A TreeNode class might have a Tree as a friend Container could have iterator_t
Though a nested class may be more appropriate Use friends when:
The data should not be available to everyone
There is a piece of code very related to this particular class
In general we prefer to define friends directly in the class they relate to
4.2
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 (const and non-const)
Increment/Decrement
++, --
member (non-const)
Arrow, Deference
->, *
member (const and non-const)
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
5
Overload: I/O
Equivalent to .toString() method in Java
Scope to overload for different types of output and input streams
1 #include
2 #include
3
4 class point {
5 public:
6 point(int x, int y)
7 : x_{x}
8 , y_{y} {};
9 friend std::ostream& operator<<(std::ostream& os, const point& type);
10 friend std::istream& operator>>(std::istream& is, point& type);
11
12 private:
13 int x_;
14 int y_;
15 };
16
17 std::ostream& operator<<(std::ostream& os, point const& p) {
18 os << "(" << p.x_ << "," << p.y_ << ")";
19 return os;
20 }
21
22 std::istream& operator>>(std::istream& is, point& p) {
23 // To be done in tutorials
24 }
25
26 auto main() -> int {
27 point p(1, 2);
28 std::cout << p << '\n';
29 }
lecture-3/demo403-io.cpp
6
Overload: Compound assignment
Sometimes particular methods might not have any real meaning, and they should be omitted (in this case, what does dividing two points together mean).
Each class can have any number of operator+= operators, but there can only be one operator+= (X) where X is a type.
That's why in this case we have two multiplier compound assignment operators
1 class point {
2 public:
3 point(int x, int y)
4 : x_{x}
5 , y_{y} {};
6 point& operator+=(point const& p);
7 point& operator-=(point const& p);
8 point& operator*=(point const& p);
9 point& operator/=(point const& p);
10 point& operator*=(int i);
11
12 private:
13 int x_;
14 int y_;
15 };
16
17 point& point::operator+=(point const& p) {
18 x_ += p.x_;
19 y_ += p.y_;
20 return *this;
21 }
22
23 point& operator+=(point const& p) { /* what do we put here? */}
24 point& operator-=(point const& p) { /* what do we put here? */}
25 point& operator*=(point const& p) { /* what do we put here? */}
26 point& operator/=(point const& p) { /* what do we put here? */}
27 point& operator*=(int i) { /* what do we put here? */}
lecture-3/demo404-compassign.cpp
7
Operator pairings
Many operators should be grouped together. This table should help you work out which are the minimal set of operators to overload for any particular operator.
If you overload
Then you should also overload
operator OP=(T, operator+(T, U) operator-(T, U) operator/(T, U) operator%(T, U) operator++() operator--() operator->()
U)
operator OP(T, U)
operator+(U, T)
operator+(T, U)
operator*(T, U)
operator/(T, U)
operator++(int)
operator++() operator–(int) operator*()
operator+(T)
operator-(T)
8
Overload: Relational & Equality
Do we want all of these?
We’re able to “piggyback” off previous definitions
Check out the spaceship operator
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include
class point {
public:
point(int x, int y)
: x_{x}
, y_{y} {}
// hidden friend – preferred
friend bool operator==(point const& p1, point const& p2) {
return p1.x_ == p2.x_ and p1.y_ == p2.y_;
// return std::tie(p1.x_, p1.y_) == std::tie(p2.x_, p2.y_);
}
friend bool operator!=(point const& p1, point const& p2) {
return not (p1 == p2);
friend bool operator<(point const& p1, point const& p2) {
return p1.x_ < p2.x_ and p1.y_ < p2.y_;
friend bool operator>(point const& p1, point const& p2) {
return p2 < p1;
friend bool operator<=(point const& p1, point const& p2) {
return not (p2 < p1);
friend bool operator>=(point const& p1, point const& p2) {
}
}
}
}
} }
private:
int x_;
int y_;
};
auto main() -> int {
auto const p2 = point{1, 2};
return not (p1 < p2);
auto const p1 = point{1, 2};
std::cout << "p1 == p2 " << (p1 == p2) << '\n';
std::cout << "p1 != p2 " << (p1 != p2) << '\n';
std::cout << "p1 < p2 " << (p1 < p2) << '\n';
std::cout << "p1 > p2 ” << (p1 > p2) << '\n';
std::cout << "p1 <= p2 " << (p1 <= p2) << '\n';
std::cout << "p1 >= p2 ” << (p1 >= p2) << '\n';
lecture-3/demo405-relation1.cpp
9
Overload: Assignment
Similar to compound assignment
1 #include
3 class point {
4 public:
5 point(int x, int y)
6 : x_{x}
7 , y_{y} {};
8 point& operator=(point const& p);
9
10 private:
11 int x_;
12 int y_;
13 };
14
15 point& point::operator=(point const& p) {
16 x_ = p.x_;
17 y_ = p.y_;
18 return *this;
19 }
lecture-3/demo406-assign.h
10
Overload: Subscript
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
16
17
18
19
20
#include
class point {
public:
point(int x, int y)
: x_{x}
, y_{y} {};
int& operator[](int
assert(i ==
return i ==
i) {
0 or i == 1);
0 ? x_ : y_;
} private:
};
}
int operator[](int i) const {
assert(i == 0 or i == 1);
return i == 0 ? x_ : y_;
int x_;
int y_;
lecture-3/demo407-subscript.h
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 optimised builds
11
Overload: Increment/Decrement
prefix: ++x, –x, returns lvalue reference Discussed more in week 5
postfix: x++, x–, returns rvalue
Discussed more in week 5
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
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// RoadPosition.h:
class RoadPosition {
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_;
};
// RoadPosition.cpp:
#include
RoadPosition& RoadPosition::operator++() {
this->tick_();
return *this;
}
RoadPosition RoadPosition::operator++(int) {
RoadPosition rp = *this;
this->tick_();
return rp;
}
void RoadPosition::tick_() {
++(this->km_from_sydney_);
}
1 auto main() -> int {
2
3
4
5
6
7 8}
auto rp = RoadPosition(5);
std::cout << rp.km() << '\n';
auto val1 = (rp++).km();
auto val2 = (++rp).km();
std::cout << val1 << '\n';
std::cout << val2 << '\n';
lecture-3/demo408-incdec.h
lecture-3/demo408-incdec.cpp
12
Overload: Arrow & Dereferencing
This content will feature heavily in week 5
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
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
delete ptr_;
std::string* operator->() const {
return ptr_;
std::string& operator*() const {
return *ptr_;
private:
std::string* ptr_;
};
auto main() -> int {
auto p = stringptr(“smart pointer”);
std::cout << *p << '\n';
std::cout << p->size() << '\n';
}
#include
class stringptr {
public:
explicit stringptr(std::string const& s)
: ptr_{new std::string(s)} {}
~stringptr() {
1
2
3
4
5
6
7 8}
} }
lecture-3/demo409-arrow.cpp
13
Overload: Type Conversion
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
16
17
18
#include
class point {
public:
point(int x, int y)
: x_(x)
, y_(y) {}
explicit operator std::vector
} private:
};
std::vector
vec.push_back(x_);
vec.push_back(y_);
return vec;
int x_;
int y_;
lecture-3/demo410-type.h
Many other operator overloads
Full list here:
https://en.cppreference.com/w/cpp/languag e/operators
Example:
1
2
3
4
5
6
7 8}
#include
#include
int main() {
auto p = point(1, 2);
auto vec = static_cast
std::cout << vec[0] << '\n';
std::cout << vec[1] << '\n';
lecture-3/demo410-type.cpp
14
Overload: New Function Syntax
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
delete ptr_;
auto operator->() const -> std::string* {
return ptr_;
auto operator*() const -> std::string& {
return *ptr_;
#include
class stringptr {
public:
explicit stringptr(std::string const& s)
: ptr_{new std::string(s)} {}
~stringptr() {
1
2
3
4
5
6
7 8}
} }
private:
std::string* ptr_;
};
auto main() -> int {
auto p = stringptr(“smart pointer”);
std::cout << *p << '\n';
std::cout << p->size() << '\n';
}
lecture-3/demo411-syntax.cpp
We are able to use the new function syntax on our operator overloads as well
15
Overload: Spaceship Operator
1 #include
2 #include
3
4 class point {
5 public:
6 point(int x, int y)
7 : x_{x}
8 , y_{y} {}
9
10 // hidden friend – preferred
11 // return type deduced as std::strong_ordering
12 friend auto operator<=>(point p1, point p2) = default;
13
14 private:
15 int x_;
16 int y_;
17 };
18
19 auto main() -> int {
20 auto const p2 = point{1, 2};
21 auto const p1 = point{1, 2};
22 std::cout << "p1 == p2 " << (p1 == p2) << '\n';
23 std::cout << "p1 != p2 " << (p1 != p2) << '\n';
24 std::cout << "p1 < p2 " << (p1 < p2) << '\n';
25 std::cout << "p1 > p2 ” << (p1 > p2) << '\n';
26 std::cout << "p1 <= p2 " << (p1 <= p2) << '\n';
27 std::cout << "p1 >= p2 ” << (p1 >= p2) << '\n';
28 }
1 #include
2 #include
3
4 class point {
5 public:
6 point(double x, double y)
7 : x_{x}
8 , y_{y} {}
9
10 // hidden friend – preferred
11 // return type deduced as std::partial_ordering
12 friend auto operator<=>(point p1, point p2) = default;
13
14 private:
15 double x_;
16 double y_;
17 };
18
19 auto main() -> int {
20 auto const p2 = point{1.0, 2.0};
21 auto const p1 = point{1.0, 2.0};
22 std::cout << "p1 == p2 " << (p1 == p2) << '\n';
23 std::cout << "p1 != p2 " << (p1 != p2) << '\n';
24 std::cout << "p1 < p2 " << (p1 < p2) << '\n';
25 std::cout << "p1 > p2 ” << (p1 > p2) << '\n';
26 std::cout << "p1 <= p2 " << (p1 <= p2) << '\n';
27 std::cout << "p1 >= p2 ” << (p1 >= p2) << '\n';
28 }
lecture-3/demo405-relation2.cpp
16 . 1
Overload: Spaceship Operator
1 #include
4 class point {
5 public:
6 point(int x, int y) 7 : x_{x}
8 , y_{y} {}
9
10 // hidden friend – preferred
11 // return type deduced as std::strong_ordering
12 friend auto operator<=>(point p1, point p2) = default; 13
14 private:
15 int x_;
16 int y_;
17 };
18
19 auto main() -> int {
20 auto const p2 = point{1, 2};
21 auto const p1 = point{1, 2};
22 std::cout << "p1 == p2 " << (p1 == p2) << '\n';
23 std::cout << "p1 != p2 " << (p1 != p2) << '\n';
24 std::cout << "p1 < p2 " << (p1 < p2) << '\n';
25 std::cout << "p1 > p2 ” << (p1 > p2) << '\n';
26 std::cout << "p1 <= p2 " << (p1 <= p2) << '\n';
27 std::cout << "p1 >= p2 ” << (p1 >= p2) << '\n';
28 }
1 #include
2 #include
3
4 class point {
5 public:
6 point(double x, double y)
7 : x_{x}
8 , y_{y} {}
9
10 // hidden friend – preferred
11 // return type deduced as std::partial_ordering
12 friend auto operator<=>(point p1, point p2) = default;
13
14 private:
15 double x_;
16 double y_;
17 };
18
19 auto main() -> int {
20 auto const p2 = point{1.0, 2.0};
21 auto const p1 = point{1.0, 2.0};
22 std::cout << "p1 == p2 " << (p1 == p2) << '\n';
23 std::cout << "p1 != p2 " << (p1 != p2) << '\n';
24 std::cout << "p1 < p2 " << (p1 < p2) << '\n';
25 std::cout << "p1 > p2 ” << (p1 > p2) << '\n';
26 std::cout << "p1 <= p2 " << (p1 <= p2) << '\n';
27 std::cout << "p1 >= p2 ” << (p1 >= p2) << '\n';
28 }
lecture-3/demo405-relation2.cpp
16 . 1
Overload: Spaceship Operator
1 #include
4 class point {
5 public:
6 point(int x, int y) 7 : x_{x}
8 , y_{y} {}
9
10 // hidden friend – preferred
11 // return type deduced as std::strong_ordering
12 friend auto operator<=>(point p1, point p2) = default; 13
14 private:
15 int x_;
16 int y_;
17 };
18
19 auto main() -> int {
20 auto const p2 = point{1, 2};
21 auto const p1 = point{1, 2};
22 std::cout << "p1 == p2 " << (p1 == p2) << '\n';
23 std::cout << "p1 != p2 " << (p1 != p2) << '\n';
24 std::cout << "p1 < p2 " << (p1 < p2) << '\n';
25 std::cout << "p1 > p2 ” << (p1 > p2) << '\n';
26 std::cout << "p1 <= p2 " << (p1 <= p2) << '\n';
27 std::cout << "p1 >= p2 ” << (p1 >= p2) << '\n';
28 }
1 #include
2 #include
3
4 class point {
5 public:
6 point(double x, double y) 7 : x_{x}
8 , y_{y} {}
9
10 // hidden friend – preferred
11 // return type deduced as std::partial_ordering
12 friend auto operator<=>(point p1, point p2) = default; 13
14 private:
15 double x_;
16 double y_;
17 };
18
19 auto main() -> int {
20 auto const p2 = point{1.0, 2.0};
21 auto const p1 = point{1.0, 2.0};
22 std::cout << "p1 == p2 " << (p1 == p2) << '\n';
23 std::cout << "p1 != p2 " << (p1 != p2) << '\n';
24 std::cout << "p1 < p2 " << (p1 < p2) << '\n';
25 std::cout << "p1 > p2 ” << (p1 > p2) << '\n';
26 std::cout << "p1 <= p2 " << (p1 <= p2) << '\n';
27 std::cout << "p1 >= p2 ” << (p1 >= p2) << '\n';
28 }
lecture-3/demo405-relation2.cpp
16 . 1
Overload: Spaceship Operator
// For int-based point
auto const ordering = (p1 <=> p2) == std::strong_ordering::equal;
std::cout << "p1 <=> p2 yields equal ” << ordering << '\n';
// For double-based point
auto const ordering = (p1 <=> p2) == std::partial_ordering::equivalent;
std::cout << "p1 <=> p2 yields equivalent ” << ordering << '\n';
16 . 2
Overload: Spaceship Operator
#include
Example types
Floating-point numbers Complex numbers
2D points
std::partial_ordering::less
std::partial_ordering::equivalent
std::partial_ordering::greater
std::partial_ordering::unordered
std::weak_ordering::less
std::weak_ordering::equivalent
std::weak_ordering::greater
Case-insensitive strings
std::strong_ordering::less
std::strong_ordering::equal
std::strong_ordering::greater
Integers std::string
16 . 3
Overload: Spaceship Operator
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include
#include
class point {
public:
point(int x, int y)
: x_{x}
, y_{y} {}
friend auto operator==(point, point) -> bool = default;
friend auto operator<=>(point const p1, point const p2) -> std::partial_ordering {
auto const x_result = p1.x_ <=> p2.x_;
auto const y_result = p1.y_ <=> p2.y_;
return x_result == y_result ? x_result
}
private:
int x_;
int y_; };
: std::partial_ordering::unordered;
lecture-3/demo405-relation2.cpp
16 . 4