This lesson introduces operator overloading, a fundamental concept to create more intuitive and readable code by customizing operators for user-defined types

Updated

In the previous lesson, we created a simple `Vector3`

struct to hold 3 numbers, which we could use to represent concepts like a position in a 3D world:

```
struct Vector3 {
float x;
float y;
float z;
}
```

It would be nice if we were able to use operators like `+`

and `+=`

with our new custom type, in much the same way we were able to do with built-in types like `int`

and `float`

.

For example, we would like to be able to do things like this:

```
Vector3 CurrentPosition { 1.0, 2.0, 3.0 };
Vector3 Movement { 4.0, 5.0, 6.0 };
// Create a new object using the + operator
Vector3 NewPosition { CurrentPosition + Movement };
```

After running the above code, we'd want `NewPosition`

to be a `Vector3`

with the values `5.0`

, `7.0`

, and `9.0`

.

For this, we need to * overload operators*.

In C++, operators are simply functions that have a specific name and parameter list.

When we write code like `1 + 2`

, the compiler is going to try to call a function with the following properties:

- Has the name of
`operator+`

- Accepts an
`int`

as the first argument - Accepts an
`int`

as the second argument

With that in mind, the function prototype that allows two our our `Vector3`

objects to use the `+`

operator could have this prototype:

`void operator+(Vector3 a, Vector3 b);`

It is up to us to define what an operator does, and what it returns. Naturally, when we see `+`

, we’d expect that operator to add the two operands together, and return the result.

So let's update the function’s return type and body to do that:

```
Vector3 operator+(Vector3 a, Vector3 b){
return Vector3{
a.x + b.x,
a.y + b.y,
a.z + b.z
};
}
```

With that, we can now easily add our `Vector3`

objects together:

```
#include <iostream>
using namespace std;
struct Vector3 {
float x;
float y;
float z;
};
Vector3 operator+(Vector3 a, Vector3 b){
return Vector3{
a.x + b.x,
a.y + b.y,
a.z + b.z
};
}
int main(){
Vector3 CurrentPosition{1.0, 2.0, 3.0};
Vector3 Movement{4.0, 5.0, 6.0};
Vector3 NewPosition{
CurrentPosition + Movement
};
std::cout
<< "x= " << NewPosition.x
<< ", y = " << NewPosition.y
<< ", z = " << NewPosition.z;
}
```

`x= 5, y = 7, z = 9`

Our operator works correctly, but there is a small change we could make to improve its performance.

With our current implementation, our operands are being copied into our function body, which is unnecessary.

Users familiar with other programming languages may recognize this behavior as * passing by value*, which C++ does by default.

We cover this in detail later in the course but for those more comfortable, we can change our parameters to be * passed by reference* by appending an ampersand,

`&`

, to their types in our function parameter list:```
Vector3 operator+(Vector3& a, Vector3& b){
return Vector3{
a.x + b.x,
a.y + b.y,
a.z + b.z
};
}
```

C++ does * not* consider an expression like

`A * B`

to be equivalent to `B * A`

.This has implications for our operator overloading. For example, if we wanted to give our `Vector3`

the ability to be multiplied by an `int`

, we typically need to implement two variations.

We need to implement one where the `int`

is the left operand, to support expressions like `2 * MyVector`

:

```
// int * Vector3
Vector3 operator*(int num, Vector3 vec) {
return Vector3{
vec.x * num, vec.y * num, vec.z * num
};
}
```

And we need to implement a variation where the `int`

is the right operand, to support expressions like `MyVector * 2`

:

```
// Vector3 * int
Vector3 operator*(Vector3 vec, int num) {
return Vector3{
vec.x * num, vec.y * num, vec.z * num
};
}
```

In situations like this, where the parameter order doesn’t change the output of our operation (that is, our operation is * commutative*), we can implement one function in terms of the other.

That is, if someone calls the `Vector3 * int`

variation, we simply defer to the `int * Vector3`

implementation:

```
// int * Vector3
Vector3 operator*(int num, Vector3 vec) {
return Vector3{
vec.x * num, vec.y * num, vec.z * num
};
}
// Vector3 * int
Vector3 operator*(Vector3 vec, int num) {
return num * vec;
}
```

Test your Knowledge### Operator Overloading

What function prototype would we need to allow two `Vector3`

objects to be subtracted using the `-`

operator, returning a new `Vector3`

? For example:

```
Vector3 CurrentPosition { 1.0, 2.0, 3.0 };
Vector3 Reverse { 4.0, 5.0, 6.0 };
Vector3 NewPosition{CurrentPosition - Reverse};
```

What function would we need to allow a `Vector3`

to be multiplied by an `int`

using the `*`

operator? For example:

```
Vector3 CurrentPosition { 1.0, 2.0, 3.0 };
Vector3 NewPosition { CurrentPosition * 5 };
```

The previous examples implemented operator overloading using a standalone function, outside of any class or struct. These are sometimes referred to as * free functions*.

However, it is also possible to implement operators as a member function, within the relevant class or struct.

Let's see what the `+`

operator might look like using that method:

```
struct Vector3 {
float x;
float y;
float z;
Vector3 operator+ (Vector3 Other) {
return Vector3 {
x + Other.x,
y + Other.y,
z + Other.z
};
}
};
```

The key thing to note in the declaration of `operator+`

as a member function is that there is only one parameter.

This might be confusing, as the `+`

operator has two operands - a left and a right. When we created this as a free function earlier, we needed to have two parameters:

`Vector3 operator+ (Vector3 a, Vector3 b);`

However, when we overload the operator as a member function, the function is called within the * context* of the left operand. So for example, an expression like

`x`

is accessing the `x`

member of the left operand.Therefore, there is no need to have the left operand be provided as an argument - we can just access its state in the same way we would from any other class function.

Test your Knowledge### Operator Overloading as a Member Function

How can we allow our `Vector3`

objects to be multiplied with a `float`

using a member function operator overload? For example:

```
struct Vector3 {
float x;
float y;
float z;
// Add a function here
};
Vector3 MyVector { 4.0, 5.0, 6.0 };
Vector3 BigVector { MyVector * 3.0 };
```

The previous sections are all examples of binary operators. Binary operators have two operands - a left, and a right.

```
// Add LeftOperand and RightOperand
LeftOperand + RightOperand;
```

Some operators take only one operand - these are called unary operators. `++`

is an example of a unary operand.

```
// Increment SomeNumber
SomeNumber++;
```

Some symbols, such as `-`

can be used either as a unary or binary operand. The unary `-`

is generally used to get the negative form of an operand, whilst the binary `-`

is used to subtract the right operand from the left:

```
int Number{5};
-Number; // Return -5
Number - Number; // Return 0
```

We implement unary and binary operators in exactly the same way. The only difference is the number of parameters our function will have:

- Overloading a binary operator as a standalone function will use 2 parameters
- Overloading a binary operator as a member function will use 1 parameter
- Overloading a unary operator as a standalone function will use 1 parameter
- Overloading a unary operator as a member function will use no parameters

Below, we overload the unary `-`

operator using a member function:

```
struct Vector3 {
float x;
float y;
float z;
Vector3 operator-() {
return Vector3 { -x, -y, -z };
}
};
```

Test your Knowledge### Overloading Unary Operators

How would we overload the unary `-`

operator using a standalone function?

Like any other member function, we can declare and define our prototypes in different locations, if we prefer:

```
struct Vector3 {
float x;
float y;
float z;
// prototype
Vector3 operator+ (const Vector3& Other);
};
```

```
// definition
Vector3 Vector3::operator+ (Vector3 Other) {
return Vector3 {
x + Other.x,
y + Other.y,
z + Other.z
};
}
```

In this lesson, we’ve focused on operators that are typically expected to return new objects, such as the `+`

and `*`

operators.

Some operators are expected to modify their operand in place. Examples include `++`

and `*=`

.

We’ve intentionally avoided these operators for now, as we need to learn some more advanced concepts first.

Specifically, we haven’t currently covered enough to make these operators work when they are chained together, for example:

`--Position *= 2;`

We have a second lesson on operator overloading later in the course, where we cover implementing more advanced operators once we’ve learned all the prerequisites

In this lesson, we introduced operator overloading, allowing us to extend the capability of our user-defined types, and interact with them in more expressive ways.

The key learnings are:

- Operators are implemented as simple functions in C++, with a specific naming convention using the word
`operator`

- Operator overloads can be implemented using free functions or member functions, and which approach we choose has implications on what our function parameters will be
- Operators can be defined and declared in different locations, just like any other function

The upcoming lesson will introduce * structured binding*. This feature, a part of C++17, offers a more concise and readable way to handle simple types, such as the

`Vector3`

we’ve been using in this lesson.We’ll cover:

- Basics and syntax of structured binding.
- Practical application and examples of structured binding.
- Limitations on when it can be used.

Was this lesson useful?

Updated

Lesson Contents### Operator Overloading

This lesson introduces operator overloading, a fundamental concept to create more intuitive and readable code by customizing operators for user-defined types

This lesson is part of the course:### Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way