University of Wales Trinity Saint David
ACCB5017 - Games Architecture and Mechanics

Operator Overloading

Overview

This lab builds a small Vector2 value type that overloads +, -, ==, != and << so it behaves in ordinary expressions the way a built-in float or int already does. Starting from a Vector2 with just two public members, x and y, you will add member operators that let two vectors be added, subtracted and compared with the same symbols used for numbers, then a free function operator that lets a Vector2 be printed straight into std::cout. By the end, a small program builds several vectors, combines and compares them, and sums a list of them in a loop, with no named function such as add(a, b) or equals(a, b) written anywhere.

Learning Objectives

By the end of this lab you will be able to:

Concept

Without operator overloading, combining two objects of a custom type needs a named function. adding two Vector2s would mean writing something like add(a, b), and every call site involving Vector2 would read differently from the plain a + b that already works for two floats or ints. Operator overloading lets a class define what an existing operator symbol means for its own type, so Vector2 can take part in the same +, -, == and << expressions that built-in numeric types already support.

Most operators overload as ordinary member functions. When the left-hand operand of an expression is always an instance of the class, such as a + b where a is a Vector2, operator+ can be declared and implemented on Vector2 the same way start() or any other method would be. writing a + b is really just a more readable way of writing a.operator+(b). operator+ and operator- each build and return a new Vector2 from x and y, while operator== and operator!= return a bool built from comparing both members.

operator<< cannot be a member of Vector2. In std::cout << myVector, the left-hand operand of << is the std::ostream, and C++ looks up which operator<< to call based on that left-hand type first, not Vector2. Overloading an operator for a type that was not written by the same class, here std::ostream, means declaring it as a free function that takes both operands as ordinary parameters, operator<<(std::ostream& stream, const Vector2& vector), and returns the stream by reference so multiple << calls can chain in a single expression.

Tasks

Task 1 - Member operators: operator+, operator-, operator==, operator!=

Open Vector2.h and Vector2.cpp and complete the constructor and the four member operators. Each one behaves like an ordinary method, the left-hand operand of the expression is always the Vector2 the method runs on.

  1. In Vector2.cpp, implement both constructors so x and y are set from their arguments (the default constructor sets both to 0.0f).
  2. Implement operator+ to return a new Vector2 whose x and y are the sum of this and other's matching members.
  3. Implement operator- the same way, using subtraction instead of addition.
  4. Implement operator== to return true only when both x and y match other's.
  5. Implement operator!= by reusing operator==, return !(*this == other);, rather than repeating the comparison.

Success condition: a small test in main(), Vector2 a(2.0f, 3.0f); Vector2 b(5.0f, -1.0f); Vector2 c = a + b;, compiles, and a == a evaluates to true while a == b evaluates to false.

Vector2.h
#pragma once
#include <iostream>

// Vector2 is a small value type. Without operator overloading, combining
// two of them would need named functions - add(a, b), equals(a, b) - and
// every call site would read differently from the plain arithmetic a
// built-in float or int supports.
class Vector2
{
public:
    float x;
    float y;

    Vector2();
    Vector2(float x, float y);

    // TODO: declare operator+(const Vector2& other) const returning Vector2
    // TODO: declare operator-(const Vector2& other) const returning Vector2
    // TODO: declare operator==(const Vector2& other) const returning bool
    // TODO: declare operator!=(const Vector2& other) const returning bool
};
Vector2.cpp
#include "Vector2.h"

Vector2::Vector2()
{
    // TODO: set x and y to 0.0f
}

Vector2::Vector2(float x, float y)
{
    // TODO: set this->x and this->y from the constructor arguments
}

Vector2 Vector2::operator+(const Vector2& other) const
{
    // TODO: return a new Vector2 built from x + other.x and y + other.y
}

Vector2 Vector2::operator-(const Vector2& other) const
{
    // TODO: return a new Vector2 built from x - other.x and y - other.y
}

bool Vector2::operator==(const Vector2& other) const
{
    // TODO: return true only if x and y both match other's
}

bool Vector2::operator!=(const Vector2& other) const
{
    // TODO: return !(*this == other), reusing operator== above
}

Task 2 - operator<< as a free function

Open Vector2.h again and add one declaration below the class, then open Vector2.cpp and add its definition at the bottom of the file.

  1. In Vector2.h, below the closing }; of the class, declare std::ostream& operator<<(std::ostream& stream, const Vector2& vector); as a free function, not a class member.
  2. In Vector2.cpp, define it to write (x, y) to stream using vector.x and vector.y, then return stream; so further << calls can chain onto the same expression.

Success condition: std::cout << "a = " << a << std::endl; compiles and prints a = (2, 3) for Vector2 a(2.0f, 3.0f);, with no changes needed to Vector2's member list.

Vector2.h
// Add below the closing "};" of the Vector2 class from Task 1.

// operator<< cannot be a Vector2 member: in an expression like
// "std::cout << myVector", the left-hand operand is a std::ostream, not a
// Vector2, and C++ always looks up an operator on its left operand's type
// first. Declaring it as a free function taking both sides as parameters
// is the only way to overload it for a type you did not write std::ostream
// yourself.
// TODO: declare operator<<(std::ostream& stream, const Vector2& vector)
// returning std::ostream&
Vector2.cpp
// Add at the bottom of Vector2.cpp, below operator!= from Task 1.

std::ostream& operator<<(std::ostream& stream, const Vector2& vector)
{
    // TODO: write "(" << vector.x << ", " << vector.y << ")" to stream,
    // then return stream so further << calls can chain
}

Task 3 - Using the overloaded operators

Open OperatorOverloadingDemo.cpp and complete main() so it exercises every operator from Task 1 and Task 2.

  1. Construct Vector2 a(2.0f, 3.0f); and Vector2 b(5.0f, -1.0f);, then print both using operator<<.
  2. Print a + b and a - b, each wrapped in parentheses inside the << expression.
  3. Print std::boolalpha once, then print the results of a == b, a == a and a != b.
  4. Build std::vector<Vector2> steps = { Vector2(1.0f, 0.0f), Vector2(0.0f, 1.0f), Vector2(2.0f, 2.0f) }; and a Vector2 total(0.0f, 0.0f);, then sum steps into total using a range-based for loop and plain +, the same way you would sum a std::vector<float>.
  5. Print total.

Success condition: the program prints a, b, their sum and difference, three boolean comparisons, and a final total displacement line, with no named function such as add, subtract or equals written anywhere in this file.

OperatorOverloadingDemo.cpp
#include <iostream>
#include <vector>
#include "Vector2.h"

int main()
{
    // TODO: construct Vector2 a(2.0f, 3.0f) and Vector2 b(5.0f, -1.0f),
    // then print "a = " << a and "b = " << b, each followed by std::endl

    // TODO: print "a + b = " << (a + b) and "a - b = " << (a - b)

    // TODO: print std::boolalpha, then "a == b: " << (a == b),
    // "a == a: " << (a == a) and "a != b: " << (a != b)

    std::cout << "---" << std::endl;

    // TODO: build std::vector<Vector2> steps with three Vector2s, sum them
    // into a Vector2 total(0.0f, 0.0f) using a range-based for loop and
    // plain +, then print "total displacement = " << total
}

Solution

Try the tasks above before revealing the solution.