I am currently playing a bit around. While the current compiler starts to finally release the c++17
standard implementations, I am trying to create a mathematical vector class, with variable dimensions, which are stored in an std::array
. This dimensions can be specified via template parameter.
The other requirement (beside the dynamic dimensions) is the usage of constexpr
. I want to be able to use this in compile time expressions; this is the reason why I avoid the std algorithms
. They are unluckily not declared constexpr
, which is a shame, imo...
But ok, either way; here is my implementation.
Some words before you start reading:
I will implement all vector algorithms (like length, normalize, etc.) as free functions. I don't want to bloat my class with that.
Second, I like getter
and setter
. I know, I could simply return a reference to my internal values, but I really don't like that. That's simply a matter of taste.
Last but not least, the free operators like +, -, etc are implemented in the inherited structs; That are just some simple helper classes, thus I think it isn't necessary to post them here.
#include <array>
#include <cassert>
#include <type_traits>
#include "operators/compare.hpp"
#include "operators/arithmetic.hpp"
/*!
* \class Vector
*
* \brief A dynamic dimensional vector class. It provides access via the operator [], and for common dimensions,
* getters and setters (e.g. getX(), setX()).
*
* It provides (inherited) arithmetic functions for both, Vector<T, DIM> and T. Look at the specific struct documentations
* to get more details.
*
* \tparam T Generic type parameter. Must be an arithmetic type.
* \tparam DIM Number of dimensions.
*/
template<class T, std::size_t DIM>
class Vector :
operators::Equal<Vector<T, DIM>>,
operators::Plus<Vector<T, DIM>>,
operators::Minus<Vector<T, DIM>>,
operators::Arithmetic2<Vector<T, DIM>, T>
{
private:
static_assert(DIM > 0, "DIM must be greater than 0.");
static_assert(std::is_arithmetic_v<T>, "T must be an arithmetic type.");
public:
enum class CommonDimensions
{
x = 0,
y,
z
};
/*!
* \brief default constructor
*/
constexpr Vector() noexcept = default;
/*!
* \brief copy constructor
*/
constexpr Vector(const Vector&) noexcept = default;
/*!
* \brief move constructor
*/
constexpr Vector(Vector&&) noexcept = default;
/*!
* \brief copy-assignment operator.
*
* \return A reference of this object.
*/
constexpr Vector& operator =(const Vector&) noexcept = default;
/*!
* \brief move-assignment operator.
*
* \return A reference of this object.
*/
constexpr Vector& operator =(Vector&&) noexcept = default;
/*!
* \brief template constructor
*
* \details Variadic template constructor for direct access on the underlaying std::array.
* \remark The enable_if is used to SFINAE this constructor for copy or move purposes.
*
* \tparam Args Type of the arguments.
* \param _args Arguments for initialising the underlaying std::array.
*/
template <class... Args, typename = std::enable_if_t<!(sizeof...(Args) == 1 && std::is_same_v<std::common_type_t<Args...>, Vector<T, DIM>>)>>
constexpr explicit Vector(Args&&... _args) noexcept :
m_Values{ std::forward<Args>(_args)... }
{
}
/*!
* \brief Gets the value at the passed index.
*
* \remark This function doesn't perform any out-of-bound check, thus it is undefined
* behavior to pass an invalid index [0, DIM).
* \tparam Index Type of the index.
* \param _index Index of value.
*
* \return The result of the operation.
*/
template <class Index>
constexpr T operator [](Index _index) const noexcept
{
std::size_t index = 0;
if constexpr (std::is_same_v<Index, CommonDimensions>)
index = static_cast<std::size_t>(_index);
else
index = _index;
assert(0 <= index && index < DIM);
return m_Values[index];
}
/*!
* \brief Sets the value at the passed index.
*
* \remark This function doesn't perform any out-of-bound check, thus it is undefined
* behavior to pass an invalid index [0, DIM).
* \tparam Index Type of the index.
* \tparam U must be implicit convertible to T.
* \param _index Index of value.
* \param _val The value.
*/
template <class Index, typename U>
constexpr void set(Index _index, U&& _val) noexcept
{
std::size_t index = 0;
if constexpr (std::is_same_v<Index, CommonDimensions>)
index = static_cast<std::size_t>(_index);
else
index = _index;
assert(0 <= index && index < DIM);
m_Values[index] = std::forward<U>(_val);
}
/*!
* \brief Gets the dimensions
*
* \return The dimensions.
*/
constexpr std::size_t getDimensions() const noexcept
{
return DIM;
}
/*!
* \brief gets X value
*
* \tparam Index This is a little trick to make the std::enable_if a dependent name. Do not pass any other type than the default one.
* \return Returns value of X.
*/
template <typename Index = std::size_t, typename = std::enable_if_t<std::greater<Index>()(DIM, static_cast<Index>(CommonDimensions::x))>>
constexpr decltype(auto) getX() const noexcept
{
return (*this)[CommonDimensions::x];
}
/*!
* \brief sets X value
*
* \tparam U must be implicit convertible to T
* \tparam Index This is a little trick to make the std::enable_if a dependent name. Do not pass any other type than the default one.
* \param _value The value.
*/
template <typename U, typename Index = std::size_t, typename = std::enable_if_t<std::greater<Index>()(DIM, static_cast<Index>(CommonDimensions::x))>>
constexpr void setX(U&& _value) noexcept
{
set(CommonDimensions::x, std::forward<U>(_value));
}
/*!
* \brief gets Y value
*
* \remark This function will be available only, if this Vector has 2 or more dimensions.
* \tparam Index This is a little trick to make the std::enable_if a dependent name. Do not pass any other type than the default one.
* \return Returns value of Y.
*/
template <typename Index = std::size_t, typename = std::enable_if_t<std::greater<Index>()(DIM, static_cast<Index>(CommonDimensions::y))>>
constexpr decltype(auto) getY() const noexcept
{
return (*this)[CommonDimensions::y];
}
/*!
* \brief sets Y value
*
* \remark This function will be available only, if this Vector has 2 or more dimensions.
*
* \tparam U must be implicit convertible to T
* \tparam Index This is a little trick to make the std::enable_if a dependent name. Do not pass any other type than the default one.
* \param _value The value.
*/
template <typename U, typename Index = std::size_t, typename = std::enable_if_t<std::greater<Index>()(DIM, static_cast<Index>(CommonDimensions::y))>>
constexpr void setY(U&& _value) noexcept
{
set(CommonDimensions::y, std::forward<U>(_value));
}
/*!
* \brief gets Z value
*
* \remark This function will be available only, if this Vector has 3 or more dimensions.
* \tparam Index This is a little trick to make the std::enable_if a dependent name. Do not pass any other type than the default one.
* \return Returns value of Z.
*/
template <typename Index = std::size_t, typename = std::enable_if_t<std::greater<Index>()(DIM, static_cast<Index>(CommonDimensions::z))>>
constexpr decltype(auto) getZ() const noexcept
{
return (*this)[CommonDimensions::z];
}
/*!
* \brief sets Z value
*
* \remark This function will be available only, if this Vector has 3 or more dimensions.
*
* \tparam U must be implicit convertible to T
* \tparam Index This is a little trick to make the std::enable_if a dependent name. Do not pass any other type than the default one.
* \param _value The value.
*/
template <typename U, typename Index = std::size_t, typename = std::enable_if_t<std::greater<Index>()(DIM, static_cast<Index>(CommonDimensions::z))>>
constexpr void setZ(U&& _value) noexcept
{
set(CommonDimensions::z, std::forward<U>(_value));
}
/*!
* \brief member wise addition
*
* \param _other The other.
*
* \return A reference of this object.
*/
constexpr Vector& operator +=(const Vector& _other) noexcept
{
for (std::size_t i = 0; i < DIM; ++i)
m_Values[i] += _other.m_Values[i];
return *this;
}
/*!
* \brief member wise subtraction
*
* \param _other The other.
*
* \return A reference of this object.
*/
constexpr Vector& operator -=(const Vector& _other) noexcept
{
for (std::size_t i = 0; i < DIM; ++i)
m_Values[i] -= _other.m_Values[i];
return *this;
}
/*!
* \brief member wise addition
*
* \param _val The value.
*
* \return A reference of this object.
*/
constexpr Vector& operator +=(const T& _val) noexcept
{
for (auto& el : m_Values)
el += _val;
return *this;
}
/*!
* \brief member wise subtraction
*
* \param _val The value.
*
* \return A reference of this object.
*/
constexpr Vector& operator -=(const T& _val) noexcept
{
for (auto& el : m_Values)
el -= _val;
return *this;
}
/*!
* \brief member wise multiplication
*
* \param _val The value.
*
* \return A reference of this object.
*/
constexpr Vector& operator *=(const T& _val) noexcept
{
for (auto& el : m_Values)
el *= _val;
return *this;
}
/*!
* \brief member wise division
*
* \param _val The value.
*
* \return A reference of this object.
*/
constexpr Vector& operator /=(const T& _val) noexcept
{
for (auto& el : m_Values)
el /= _val;
return *this;
}
/*!
* \brief member wise modulo
*
* \param _val The value.
*
* \return A reference of this object.
*/
constexpr Vector& operator %=(const T& _val) noexcept
{
for (auto& el : m_Values)
el %= _val;
return *this;
}
/*!
* \brief Equality operator
*
* \details performs a member wise equality check.
*
* \param _lhs The first instance to compare.
* \param _rhs The second instance to compare.
*
* \todo possibly better implementation when declared as constexpr: array equal check
* return _lhs.m_Values == _rhs.m_Values;
*
* \return True if the parameters are considered equivalent.
*/
friend constexpr bool operator ==(const Vector& _lhs, const Vector& _rhs) noexcept
{
for (std::size_t i = 0; i < DIM; ++i)
{
if (!(_lhs.m_Values[i] == _rhs.m_Values[i]))
return false;
}
return true;
}
private:
std::array<T, DIM> m_Values{};
};
operators arithmetic
namespace operators {
/*!
* \struct Plus
*
* \brief Single template helper struct for additions. Provides the inheritor with an implementation of operator +(const T&, const T&) as friend function.
* The inheritors must implement the operator +=(const T&, const T&) themselves.
*
* \tparam T Generic type parameter.
*/
template <class T>
struct Plus
{
/*!
* \brief Addition operator.
*
* \param _lhs The first value.
* \param _rhs A value to add to it.
*
* \return The result of the operation.
*/
friend constexpr T operator +(const T& _lhs, const T& _rhs)
{
T tmp(_lhs);
tmp += _rhs;
return tmp;
}
};
/*!
* \struct Minus
*
* \brief Single template helper struct for subtractions. Provides the inheritor with an implementation of operator -(const T&, const T&) as friend function.
* The inheritors must implement the operator -=(const T&, const T&) themselves.
*
* \tparam T Generic type parameter.
*/
template <class T>
struct Minus
{
/*!
* \brief Subtraction operator.
*
* \param _lhs The first value.
* \param _rhs A value to subtract from it.
*
* \return The result of the operation.
*/
friend constexpr T operator -(const T& _lhs, const T& _rhs)
{
T tmp(_lhs);
tmp -= _rhs;
return tmp;
}
};
/*!
* \struct Multiply
*
* \brief Single template helper struct for multiplications. Provides the inheritor with an implementation of operator *(const T&, const T&) as friend function.
* The inheritors must implement the operator *=(const T&, const T&) themselves.
*
* \tparam T Generic type parameter.
*/
template <class T>
struct Multiply
{
/*!
* \brief Multiplication operator.
*
* \param _lhs The first value to multiply.
* \param _rhs The second value to multiply.
*
* \return The result of the operation.
*/
friend constexpr T operator *(const T& _lhs, const T& _rhs)
{
T tmp(_lhs);
tmp *= _rhs;
return tmp;
}
};
/*!
* \struct Divide
*
* \brief Single template helper struct for divisions. Provides the inheritor with an implementation of operator /(const T&, const T&) as friend function.
* The inheritors must implement the operator /=(const T&, const T&) themselves.
*
* \tparam T Generic type parameter.
*/
template <class T>
struct Divide
{
/*!
* \brief Division operator.
*
* \param _lhs The numerator.
* \param _rhs The denominator.
*
* \return The result of the operation.
*/
friend constexpr T operator /(const T& _lhs, const T& _rhs)
{
T tmp(_lhs);
tmp /= _rhs;
return tmp;
}
};
/*!
* \struct Modulo
*
* \brief Single template helper struct for modulo. Provides the inheritor with an implementation of operator %(const T&, const T&) as friend function.
* The inheritors must implement the operator %=(const T&, const T&) themselves.
*
* \tparam T Generic type parameter.
*/
template <class T>
struct Modulo
{
/*!
* \brief Modulus operator.
*
* \param _lhs The numerator.
* \param _rhs The denominator.
*
* \return The result of the operation.
*/
friend constexpr T operator %(const T& _lhs, const T& _rhs)
{
T tmp(_lhs);
tmp %= _rhs;
return tmp;
}
};
/*!
* \struct Arithmetic
*
* \brief Single template helper struct for all arithmetic operations. Look at the inherited classes for more details.
*
* \tparam T Generic type parameter.
*/
template <class T>
struct Arithmetic :
Plus<T>,
Minus<T>,
Multiply<T>,
Divide<T>
{
};
/*!
* \struct Plus2
*
* \brief Multi template helper struct for additions. Provides the inheritor with an implementation of operator +(const T1&, const T2&) as friend function.
* The inheritors must implement the operator +=(const T1&, const T2&) themselves.
* \remark Because lack of consistency, the reversed operator +=(const T2&, const T1&) isn't implemented by this struct. If you need it, you either have to provide it by yourself
* or inherit Plus2<T2, T1>.
*
* \tparam T1 Generic type parameter.
* \tparam T2 Generic type parameter.
*/
template <class T1, class T2>
struct Plus2
{
/*!
* \brief Addition operator.
*
* \param _lhs The first value.
* \param _rhs A value to add to it.
*
* \return The result of the operation.
*/
friend constexpr T1 operator +(const T1& _lhs, const T2& _rhs)
{
T1 tmp(_lhs);
tmp += _rhs;
return tmp;
}
};
/*!
* \struct Minus2
*
* \brief Multi template helper struct for subtractions. Provides the inheritor with an implementation of operator -(const T1&, const T2&) as friend function.
* The inheritors must implement the operator -=(const T1&, const T2&) themselves.
* \remark Because lack of consistency, the reversed operator -=(const T2&, const T1&) isn't implemented by this struct. If you need it, you either have to provide it by yourself
* or inherit Minus2<T2, T1>.
*
* \tparam T1 Generic type parameter.
* \tparam T2 Generic type parameter.
*/
template <class T1, class T2>
struct Minus2
{
/*!
* \brief Subtraction operator.
*
* \param _lhs The first value.
* \param _rhs A value to subtract from it.
*
* \return The result of the operation.
*/
friend constexpr T1 operator -(const T1& _lhs, const T2& _rhs)
{
T1 tmp(_lhs);
tmp -= _rhs;
return tmp;
}
};
/*!
* \struct Multiply2
*
* \brief Multi template helper struct for multiplications. Provides the inheritor with an implementation of operator *(const T1&, const T2&) as friend function.
* The inheritors must implement the operator *=(const T1&, const T2&) themselves.
* \remark Because lack of consistency, the reversed operator *=(const T2&, const T1&) isn't implemented by this struct. If you need it, you either have to provide it by yourself
* or inherit Multiply2<T2, T1>.
*
* \tparam T1 Generic type parameter.
* \tparam T2 Generic type parameter.
*/
template <class T1, class T2>
struct Multiply2
{
/*!
* \brief Multiplication operator.
*
* \param _lhs The first value to multiply.
* \param _rhs The second value to multiply.
*
* \return The result of the operation.
*/
friend constexpr T1 operator *(const T1& _lhs, const T2& _rhs)
{
T1 tmp(_lhs);
tmp *= _rhs;
return tmp;
}
};
/*!
* \struct Divide2
*
* \brief Multi template helper struct for divisions. Provides the inheritor with an implementation of operator /(const T1&, const T2&) as friend function.
* The inheritors must implement the operator /=(const T1&, const T2&) themselves.
* \remark Because lack of consistency, the reversed operator /=(const T2&, const T1&) isn't implemented by this struct. If you need it, you either have to provide it by yourself
* or inherit Divide2<T2, T1>.
*
* \tparam T1 Generic type parameter.
* \tparam T2 Generic type parameter.
*/
template <class T1, class T2>
struct Divide2
{
/*!
* \brief Division operator.
*
* \param _lhs The numerator.
* \param _rhs The denominator.
*
* \return The result of the operation.
*/
friend constexpr T1 operator /(const T1& _lhs, const T2& _rhs)
{
T1 tmp(_lhs);
tmp /= _rhs;
return tmp;
}
};
/*!
* \struct Modulo2
*
* \brief Multi template helper struct for modulo. Provides the inheritor with an implementation of operator %(const T1&, const T2&) as friend function.
* The inheritors must implement the operator %=(const T1&, const T2&) themselves.
* \remark Because lack of consistency, the reversed operator %=(const T2&, const T1&) isn't implemented by this struct. If you need it, you either have to provide it by yourself
* or inherit Modulo2<T2, T1>.
*
* \tparam T1 Generic type parameter.
* \tparam T2 Generic type parameter.
*/
template <class T1, class T2>
struct Modulo2
{
/*!
* \brief Modulus operator.
*
* \param _lhs The numerator.
* \param _rhs The denominator.
*
* \return The result of the operation.
*/
friend constexpr T1 operator %(const T1& _lhs, const T2& _rhs)
{
T1 tmp(_lhs);
tmp %= _rhs;
return tmp;
}
};
/*!
* \struct Arithmetic2
*
* \brief Multi template helper struct for all arithmetic operations. Look at the inherited classes for more details.
*
* \tparam T1 Generic type parameter.
* \tparam T2 Generic type parameter.
*/
template <class T1, class T2>
struct Arithmetic2 :
Plus2<T1, T2>,
Minus2<T1, T2>,
Multiply2<T1, T2>,
Divide2<T1, T2>
{
};
} // namespace operators
operators equal
namespace operators {
/*!
* \struct Equal
*
* \brief Single template helper struct for compare equal. Provides the inheritor with an implementation of operator !=(const T&, const T&) as friend function.
* The inheritors must implement the operator ==(const T&, const T&) themselves.
*
* \tparam T Generic type parameter.
*/
template <class T>
struct Equal
{
/*!
* \brief Inequality operator
*
* \param _lhs The first instance to compare.
* \param _rhs The second instance to compare.
*
* \return True if the parameters are not considered equivalent.
*/
friend constexpr bool operator !=(const T& _lhs, const T& _rhs)
{
return !(_lhs == _rhs);
}
};
} // namespace operators
simple use-cases
constexpr Vector<int, 2> getVector()
{
Vector<int, 2> v(2); // initializes only x with 2
Vector<int, 2> p(1, 2);// inits x with 1 and y with 2
v.setX(2);
v.setY(2);
v += v;
v -= v;
v += 5;
v -= 3;
v *= 10;
v /= 2;
v %= 2;
auto t = v + 1; // copy construction
v = std::move(t); // move assign
auto x = v * 6; // move construction
auto d = v.getDimensions();
return v;
}
int main()
{
constexpr Vector<int, 2> vec(1, 2);
auto x = vec.getX();
auto y = vec.getY();
constexpr auto v(getVector()); // move construction
if constexpr (vec == v)
x = v.getX();
return 0;
}
operators::Plus
and the like? Why does functionality need to be inherited? That could make sense if you are planning also a matrix class, but if you do you might want to make the vector a specialization of the matrix. \$\endgroup\$namespace operators
, various#include
lines, etc. If you have (or could create) a short test program that shows how one would use the class, then that would also be worth adding to the question. \$\endgroup\$