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;
}
\$\endgroup\$
  • 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\$ – Cris Luengo Dec 21 '17 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\$ – Cris Luengo Dec 21 '17 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\$ – DNKpp Dec 21 '17 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\$ – Toby Speight Dec 21 '17 at 8:50
  • 1
    \$\begingroup\$ Why is it interesting? Because the code won't compile without them! \$\endgroup\$ – Toby Speight Dec 21 '17 at 16:23
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;
}
\$\endgroup\$
  • \$\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\$ – DNKpp Dec 21 '17 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\$ – Toby Speight Dec 22 '17 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\$ – Toby Speight Dec 22 '17 at 9:08
  • \$\begingroup\$ Be wary of suggesting std::rel_ops: reddit.com/r/cpp/comments/7c5h80/… \$\endgroup\$ – Justin Dec 22 '17 at 23:25
  • \$\begingroup\$ @TobySpeight, Justin is right. Lets wait for three way comparison (<=>), it should make it in C++20. \$\endgroup\$ – Incomputable Dec 23 '17 at 16:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.