1
\$\begingroup\$

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;
}
Simon Forsberg
59.7k9 gold badges157 silver badges311 bronze badges
asked Dec 20, 2017 at 22:29
\$\endgroup\$
8
  • 1
    \$\begingroup\$ What are 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\$ Commented Dec 21, 2017 at 2:42
  • \$\begingroup\$ Also: vector addition is vector addition, have never seen it done any other way. The "element-wise" specification is not necessary. \$\endgroup\$ Commented Dec 21, 2017 at 2:44
  • \$\begingroup\$ @chrisluengo vector modification by scalar is common case. like I said in the introduction, the inheritance of the operators is just a way, to remove the bloat out of the class. It is inspired by boost operators. They just implement the +, -, *, / and % operator for the passed template types. \$\endgroup\$ Commented Dec 21, 2017 at 6:25
  • 1
    \$\begingroup\$ Please could you add the missing parts 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\$ Commented Dec 21, 2017 at 8:50
  • 1
    \$\begingroup\$ Why is it interesting? Because the code won't compile without them! \$\endgroup\$ Commented Dec 21, 2017 at 16:23

1 Answer 1

4
\$\begingroup\$

What follows is an unstructured brain-dump; sorry for not giving this more detailed analysis.

I think that the X, Y and Z accessors add clutter to the class, and I would consider writing subclasses or wrappers of Vector<T, 2> and Vector<T, 3> to provide those. That's probably just a judgement call.

Can std::rel_ops take the place of the Equals<> mix-in class? They seem to do the same thing, but one is provided and well-known. If not, then also consider Boost.operators (if Boost is acceptable to you).

Why inherit from Plus and Minus individually, but from Arithmetic2 en bloc? And is it intentional that Arithmetic2 doesn't inherit from Modulo2, or is that an oversight?

Multiplication by a scalar should be commutative: if v * 6 is legal, why not 6 * v?

The std::enable_if on the template constructor looks too constrained - I think you should be able to std::enable_if_t<std::is_assignable<T,std::common_type_t<Args...>>, but maybe I misunderstand what you're trying to do here. I'd have expected a simple

template<typename... Args>
constexpr explicit Vector(Args&&... args) noexcept
 : m_Values{{std::forward<Args>(args)...}}

and rely on std::array rejecting ill-formed construction.

Usually operator[] returns a reference, at least for the non-const overload (but that appears to be missing). Instead of a template, it's probably better to provide overloads for std::size_t and CommonDimensions arguments - or a conversion from the latter to the former.

Also, operator[] conventionally doesn't check array bounds - bounds-checking interfaces are called at() in the standard library, and it makes sense to follow the same conventions.

Consider making a Vector act as a standard container - provide size() as a synonym (or replacement) for getDimensions(). It may well be worth exposing some public typedefs for value_type and similar.

It's a shame that std::array::operator== can't propagate constexpr/noexcept from its value_type::operator==, or we'd be able to write

constexpr bool operator ==(const Vector& other) const noexcept
{
 return m_Values == other.m_Values;
}
answered Dec 21, 2017 at 16:22
\$\endgroup\$
5
  • \$\begingroup\$ Thanks for your answer. My operator structs do almost the same as boost::operators (but mine are constexpr). I don't wanted to add this type of dependency to my privat lib, thus I decided to implement this myself. I didn't know std::rel_ops, but they don't seem to be usable in an elegant way. The missing Modulo(2) for the Arithmetic(2) is a mistake, thanks for this! Yeah, an overload of CommonDimensions would be a better choice than the template! Multiplication and Addition are both commutative; shall I add those operators? Is it practical? Because -, / and % aren't... \$\endgroup\$ Commented Dec 21, 2017 at 21:05
  • \$\begingroup\$ Most of your questions in the comment are choices - I've made observations, but I'm consciously shying away from recommendations. Use your judgement. If you think that unary - would make sense, then I'd suggest implementing T - Vector operator. TBH, I don't see a use case for adding a T to every element (and I like my utility classes to be informed by how I need to use them). N.B. I'm not saying it has no value; just that I'm ignorant of it. \$\endgroup\$ Commented Dec 22, 2017 at 9:06
  • \$\begingroup\$ I'm not sure why you find std::rel_ops inelegant - perhaps construct a new question around that? (Is it because of constexpr? Or is it difficulty with importing it into the right namespace for ADL to work properly?). \$\endgroup\$ Commented Dec 22, 2017 at 9:08
  • \$\begingroup\$ Be wary of suggesting std::rel_ops: reddit.com/r/cpp/comments/7c5h80/… \$\endgroup\$ Commented Dec 22, 2017 at 23:25
  • \$\begingroup\$ @TobySpeight, Justin is right. Lets wait for three way comparison (<=>), it should make it in C++20. \$\endgroup\$ Commented Dec 23, 2017 at 16:47

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.