As part of a physics simulation written in C++ using SFML I needed a vector3d class.
Here is my implementation:
#include <cmath>
#include <ostream>
template <typename T>
class vector3d {
public:
T x{};
T y{};
T z{};
T norm() { return std::hypot(x, y, z); }
vector3d<T> unit() { return vector3d<T>(*this) / norm(); }
T dot(const vector3d<T>& RHS) { return x * RHS.x + y * RHS.y + z * RHS.z; }
vector3d<T> cross(const vector3d<T>& RHS) {
return vector3d<T>{y * RHS.z - z * RHS.y, z * RHS.x - x * RHS.z, x * RHS.y - y * RHS.x};
}
bool operator==(const vector3d<T>& rhs) {
return std::abs(x - rhs.x) < std::numeric_limits<T>::epsilon() &&
std::abs(y - rhs.y) < std::numeric_limits<T>::epsilon() &&
std::abs(z - rhs.z) < std::numeric_limits<T>::epsilon();
}
bool operator!=(const vector3d<T>& rhs) { return !(*this == rhs); }
// clang-format off
vector3d<T>& operator+=(vector3d<T> RHS) { x += RHS.x; y += RHS.y; z += RHS.z; return *this; }
vector3d<T>& operator-=(vector3d<T> RHS) { x -= RHS.x; y -= RHS.y; z -= RHS.z; return *this; }
vector3d<T>& operator*=(vector3d<T> RHS) { x *= RHS.x; y *= RHS.y; z *= RHS.z; return *this; }
vector3d<T>& operator/=(vector3d<T> RHS) { x /= RHS.x; y /= RHS.y; z /= RHS.z; return *this; }
vector3d<T>& operator*=(T s) { x *= s; y *= s; z *= s; return *this; }
vector3d<T>& operator/=(T s) { x /= s; y /= s; z /= s; return *this; }
friend vector3d<T> operator+(const vector3d<T>& LHS, const vector3d<T>& RHS) { return vector3d<T>(LHS) += RHS; }
friend vector3d<T> operator-(const vector3d<T>& LHS, const vector3d<T>& RHS) { return vector3d<T>(LHS) -= RHS; }
friend vector3d<T> operator*(const vector3d<T>& LHS, const vector3d<T>& RHS) { return vector3d<T>(LHS) *= RHS; }
friend vector3d<T> operator/(const vector3d<T>& LHS, const vector3d<T>& RHS) { return vector3d<T>(LHS) /= RHS; }
friend vector3d<T> operator*(const vector3d<T>& v, T s) { return vector3d<T>(v) *= s; }
friend vector3d<T> operator*(T s, const vector3d<T>& v) { return vector3d<T>(v) *= s; }
friend vector3d<T> operator/(const vector3d<T>& v, T s) { return vector3d<T>(v) /= s; }
// note that scalar / vector3d does not make any logical sense
// clang-format on
friend std::ostream& operator<<(std::ostream& os, vector3d<T> v) {
return os << "[" << v.x << ", " << v.y << ", " << v.z << "]";
}
};
using vec3 = vector3d<double>;
using vec3f = vector3d<float>;
using vec3l = vector3d<long double>;
And its potential usage:
#include "vector3d.h"
#include <iostream>
int main() {
vec3 z;
std::cout << "z = " << z << "\n";
auto a = vec3{1, 2, 3};
auto b = vec3{4, 5, 6};
std::cout << "a = " << a << "\n";
std::cout << "b = " << b << "\n";
std::cout << "a + b = " << a + b << "\n";
auto c = 2 * b;
std::cout << "c = " << c << "\n";
auto d = b * 2;
std::cout << "d = " << d << "\n";
auto e = b / 2;
std::cout << "e = " << e << "\n";
std::cout << "a.norm() = " << a.norm() << "\n";
std::cout << "a.unit() = " << a.unit() << "\n";
std::cout << "a.unit().norm() = " << a.unit().norm() << "\n";
std::cout << "[4, 8, 10] . [9, 2, 7] = " << vec3{4, 8, 10}.dot(vec3{9, 2, 7}) << "\n";
std::cout << "[1, 0, 0] x [0, 1, 0] = " << vec3{1, 0, 0}.cross(vec3{0, 1, 0}) << "\n";
std::cout << "[2, 3, 4] x [5, 6, 7] = " << vec3{2, 3, 4}.cross(vec3{5, 6, 7}) << "\n";
}
- I tried to "do as the ints do" and make all basic operators available.
- I realise that +-*/ operators could be free functions but I preferred to write them as friends to avoid having to template every one of them. A valid style choice?
- I realise that the
dot()
andcross()
could be free functions, but preferred thea.cross(b)
usage syntax. - Minor point: I chose to format the code to wider than normal as I believe it makes it more readable / makes the patterns more obvious.
- I added some type aliases for convenience.
- Is relying on aggregate initialisation (without any constructor) and the default copy constructor a good choice?
Any comments on the code and use of member / friend / free functions please?
struct
instead ofclass
and omitpublic:
. \$\endgroup\$