Adding to our Vector2d Class#

Let’s extend our Vector2d class with the following operators / functions.

Multiplication by a scalar#

We need to consider 2 different multiplication operations—given a Vector2d v and a double a, we can do:

auto v1 = v * a;
auto v2 = a * v;

In the first, our Vector2d object appears to the left of the operator, so this is a member of the class, and the function will look like:

Vector2d operator* (double a);

But for the second, our Vector2d is to the right of the operator, so we need to create this function outside of the class, but give it permission to access a Vector2d ‘s data—this means it needs to be a friend:

friend Vector2d operator* (double a, const Vector2d& v);

In both cases, this operator returns a new Vector2d.

Division by a scalar#

For division, it only makes sense to do v / a, where v is a Vector2d and a is a double. For this operation, our object is to the left of the operator, so this would be a member of the class. This function would look like:

Vector2d operator/ (double a);

Dot product#

For vectors \(u\) and \(v\), the dot-product is \(u_x v_x + u_y v_y\).

We could imagine overloading * to work for doing the dot-product of two vectors, e.g., u * v would do u.x * v.x + u.y * v.y. But this could be confusing, because some might think it is the element-wise multiplication operation, Vector2d(u.x * v.x, u.y * v.y).

In python, the @ operator serves as the dot-product operator, but no such operator exists in C++. So we’ll use a function:

double dot(const Vector2d& v);

Cross product#

For vectors \(u\) and \(v\), the cross-product is \(u \times v = (u_x v_y - u_y v_x) \hat{z}\). We’ll just return the magnitude, since we don’t have the ability in Vector2d to express the \(z\)-component of a vector.

double cross(const Vector2d& v);

Magnitude#

The magnitude of a vector \(v\) is \(|v| = \sqrt{v_x^2 + v_y^2}\). We’ll implement this as a function abs to be similar to the absolute value function we already use from the standard library:

double abs();

Equality test#

We want the == operator to be able to do assertions. This has the form:

bool operator== (const Vector2d& v) const;

Note the const after the parameter list—this indicates that this function can be used on a const Vector2d. This const is basically saying that this operator will not modify our object. This is called a const member function.

But with C++20, we could also use the default comparison operator, since our object is simple enough for the compiler to understand:

bool operator==(const Vector2d& v) const = default;

Input stream operator, >>#

We want to overload >> so we can read directly into a Vector2d object, e.g.,

Vector2d v;

cin >> v;

Your function should look like:

friend std::istream& operator>>(std::istream& is, Vector2d &v);

Notice that the Vector2d is not const, since we will be modifying it.

You will want to read 2 pieces of data from the input stream and directly set v.x and v.y.

Implementation#

Here’s a test driver that exercises these new capabilities. It uses assertions to ensure that we implemented them correctly.

Listing 122 test_vectors.cpp#
#include <cassert>
#include <iostream>

#include "vector2d.H"

int main() {

    // create 2 vectors

    Vector2d v1(1, 2);
    Vector2d v2(2, 4);

    // ask for input for a third:
    Vector2d v_user{};
    std::cout << "Enter x and y for a vector: ";
    std::cin >> v_user;
    std::cout << "Your vector is: " << v_user << std::endl;
    std::cout << std::endl;

    // output our vectors

    std::cout << "test vectors: " << std::endl;
    std::cout << "v1 = " << v1 << std::endl;
    std::cout << "v2 = " << v2 << std::endl;
    std::cout << std::endl;

    // output their sum

    auto v_add = v1 + v2;
    assert(v_add == Vector2d(3, 6));
    std::cout << "v1 + v2 = " << v_add << std::endl;

    // create a new vector from subtracting our two vectors

    auto v_sub = v1 - v2;
    assert(v_sub == Vector2d(-1, -2));
    std::cout << "v1 - v2 = " << v_sub << std::endl;
    std::cout << std::endl;

    // test multiplication
    auto v_mul1 = v1 * 10;
    auto v_mul2 = 10 * v1;
    assert (v_mul1 == v_mul2);
    std::cout << "v1 * 10 = " << v_mul1 << std::endl;
    std::cout << "10 * v1 = " << v_mul2 << std::endl;
    std::cout << std::endl;

    // test division
    auto v_div = v2 / 2;
    assert (v_div == v1);
    std::cout << "v2 / 2 = " << v_div << std::endl;
    std::cout << std::endl;

    // test vector products
    auto v_dot = v1.dot(v2);
    assert(v_dot == 10);
    std::cout << "v1 . v2 = " << v_dot << std::endl;

    auto v_cross = v1.cross(v2);
    assert(v_cross == 0);
    std::cout << "v1 x v2 = " << v_cross << std::endl;

    auto v_mag = v1.abs();
    assert(std::abs(v_mag - std::sqrt(5)) < 1.e-14);
    std::cout << "|v1| = " << v_mag << std::endl;

}

Here’s the implementation of the class:

solution
Listing 123 vector2d.H#
#ifndef VECTOR_2D_H
#define VECTOR_2D_H

#include <iostream>
#include <cmath>

class Vector2d {

private:

    // our member data

    double x;
    double y;

public:

    // default constructor

    Vector2d()
        : x{0.0}, y{0.0}
    {}

    // another constructor

    Vector2d(double _x, double _y)
        : x{_x}, y{_y}
    {}

    // setters

    inline void set_x(double _x) {x = _x;}

    inline void set_y(double _y) {y = _y;}

    // operators

    // add two vectors

    Vector2d operator+(const Vector2d& vec) {
        return Vector2d(x + vec.x, y + vec.y);
    }

    // subtract two vectors

    Vector2d operator-(const Vector2d& vec) {
        return Vector2d(x - vec.x, y - vec.y);
    }

    // unary minus

    Vector2d operator-() {
        return Vector2d(-x, -y);
    }

    // multiplication

    Vector2d operator*(double a) {
        return Vector2d(x * a, y * a);
    }

    friend Vector2d operator*(double a, const Vector2d& v);

    // division

    Vector2d operator/(double a) {
        return Vector2d(x / a, y / a);
    }

    // special vector operations

    double dot(const Vector2d& v) {
        return x * v.x + y * v.y;
    }

    double cross(const Vector2d& v) {
        return x * v.y - y * v.x;
    }

    double abs() {
        return std::sqrt(x * x + y * y);
    }

    // equality

    bool operator==(const Vector2d& v) const = default;

    // stream operators -- not class members

    friend std::ostream& operator<< (std::ostream& os, const Vector2d& v);
    friend std::istream& operator>> (std::istream& is, Vector2d& v);

};

inline
Vector2d operator*(double a, const Vector2d& v) {
    return Vector2d(v.x * a, v.y * a);
}

inline
std::ostream& operator<< (std::ostream& os, const Vector2d& v)
{
    os << "(" << v.x << ", " << v.y << ")";
    return os;
}

inline
std::istream& operator>> (std::istream& is, Vector2d& v)
{
   is >> v.x >> v.y;
   return is;
}

#endif