2

I'm a mechanical engineer trying to create a simulation library with physical types (distance, force, moments, acceleration, velocity etc.) along with some vector maths (template Vector3D) to support those types. Everything is going smoothly for normal vector operations except for the cross product for which I want to apply an overload to the operator '^'. A cross product operation takes in two different types and returns a third see below for a list of examples.

Vector3D<Moment> = Vector3D<Distance> ^ Vector3D<Force>;
Vector3D<Acceleration> = Vector3D<AngularAcceleration> ^ Vector3D<Velocity>;
Vector3D<Velocity> = Vector3D<AngularVelocity> ^ Vector3D<Distance> etc. etc.

Now ideally I would love to not setup a different method for the '^' operand overload based on the return type and input arguments. Now I know the code below does not work fault of you cannot do a classical operator overload with three different templates and the compiler will not know what to do.

template <typename T, typename U, typename V>
Vector3D<T> operator^ (const Vector3D<U>& p_left, const Vector3D<V>& p_right)

And sure enough I get the following compilation error C2593: 'operator ^' is ambiguous. I was wondering if there was some type inference and/or instantiation that could be put to good use here though I've done some reading online nothing seems to fit the specific requirements at hand. I figured that I would need constructors for the conversion of types but that by itself will not be enough

/// Constructor
/// \param p_value Moment arm
/// \param p_unit Force
//
Moment(Distance p_radius, Force p_force);

/// Constructor
/// \param p_omega      Magnitude of angular velocity
/// \param p_velocity   Magnitude of linear velocity
//
Acceleration(AngularVelocity p_omega, Velocity p_velocity);

/// Constructor
/// \param p_value  Angular velocity
/// \param p_unit   Radius
Velocity(AngularVelocity p_omega, Distance p_radius);

Your assistance in this matter would be of great help for me as programming is still an area where I'm constantly learning... Please see below for some further information on my class Vector3D<>. Thank you in advance!

//Vector3D.h
template <typename T>
class Vector3D
{
public:
    //////////////////////////////////////////////////////////////////////////
    // *** CONSTRUCTORS ***
    //////////////////////////////////////////////////////////////////////////

    /// Default Constructor
    //
    Vector3D(void);

    /// External initialization Constructor
    //
    Vector3D(const T& p_x, const T& p_y, const T& p_z);

    //////////////////////////////////////////////////////////////////////////
    // *** DESTRUCTOR ***
    //////////////////////////////////////////////////////////////////////////

    /// Destructor
    //
    ~Vector3D(void);

    template <typename U, typename V>
    friend Vector3D<T> operator^ (const Vector3D<U>& lhs, const Vector3D<V>& rhs);
'''
private:
//////////////////////////////////////////////////////////////////////////
// *** PRIVATE DATA MEMBERS ***
//////////////////////////////////////////////////////////////////////////
T m_elem[3];

}

//Vector3D.cpp
/// Calculate the cross product
//
template <typename T, typename U, typename V>
Vector3D<T> operator^ (const Vector3D<U>& p_left, const Vector3D<V>& p_right)
{
    Vector3D<T>cross_product;
    // Calculation
    cross_product.m_elem[0] = T(p_left.m_elem[1], p_right.m_elem[2]) - T(p_left.m_elem[2], p_right.m_elem[1]);
    cross_product.m_elem[1] = T(p_left.m_elem[2], p_right.m_elem[0]) - T(p_left.m_elem[0], p_right.m_elem[2]);
    cross_product.m_elem[2] = T(p_left.m_elem[0], p_right.m_elem[1]) - T(p_left.m_elem[1], p_right.m_elem[0]);

    return cross_product;
}

template class Vector3D<double>;
template class Vector3D<double>;
template class Vector3D<Numeric>;

template class Vector3D<Force>;
template class Vector3D<Acceleration>;
template class Vector3D<Velocity>;
template class Vector3D<Distance>;

template class Vector3D<Moment>;
template class Vector3D<AngularAcceleration>;
template class Vector3D<AngularVelocity>;
template class Vector3D<Angle>;
hdamlaj
  • 51
  • 4
  • 2
    Do you already have `operator*` overloaded for your units? If `Distance * Force` already returns a `Moment`, I think there should be a way to `decltype` out the correct return value from `U` and `V`. – Nathan Pierson Nov 22 '20 at 06:14
  • 1
    `//Vector3D.cpp`: Templates need to be [implemented in headers entirely](https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file) (with few special exceptions). – Aconcagua Nov 22 '20 at 06:17
  • 1
    @Aconcagua It looks like `Vector3D.cpp` is implementing the "explicitly instantiate all the template instances you'll need" approach from the first answer in that question. – Nathan Pierson Nov 22 '20 at 06:20
  • @NathanPierson Partly. There's no explicit instantiation of `operator^` though. – cigien Nov 22 '20 at 06:24
  • Actually, I'm having a hard time figuring out a way to use `decltype` the way I was hoping you'd be able to. What I'd like to suggest is something like `decltype(T * U)` or `decltype(operator*(T, U))`, except you can't pass class types to `decltype`. `decltype(T{} * U{})` could work but only if all your unit types have default constructors. Not sure how to write just "the type `operator* would return, when called with a T and a U`. – Nathan Pierson Nov 22 '20 at 06:33
  • Apparently the helper function there is [std::declval](https://en.cppreference.com/w/cpp/utility/declval). – Nathan Pierson Nov 22 '20 at 06:42
  • 1
    Perhaps get dimensioned scalar maths working first. There are libraries out there that do just that, for example [boost.units](https://www.boost.org/doc/libs/1_74_0/doc/html/boost_units/Quick_Start.html). Constructors like `Moment(Distance p_radius, Force p_force)` are not terribly useful in this situation. BTW it's called "momentum". – n. m. could be an AI Nov 22 '20 at 06:51

1 Answers1

3

The main problem is: What is the type T in the implementation of the cross product operator? This is where the ambiguity results from.

At very first, you might want to replace the constructors with multiplication operators instead of constructors, i.e.

Moment operator*(Distance p_radius, Force p_force);

instead of

Moment(Distance p_radius, Force p_force);

This allows you to let deduce the return type from the template arguments in a trailing return type. Your operator then might look like this:

template<typename U, typename V>
auto operator^(const Vector3D<U>& left, const Vector3D<V>& right)
    -> Vector3D<decltype(std::declval<U>() * std::declval<V>())>
{
    return
    {
        left.m_elem[1] * right.m_elem[2] - left.m_elem[2] * right.m_elem[1],
        left.m_elem[2] * right.m_elem[0] - left.m_elem[0] * right.m_elem[2],
        left.m_elem[0] * right.m_elem[1] - left.m_elem[1] * right.m_elem[0]
    };
}
Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • Hey @Aconcagua when I implement your method everything works. However I lose my ability do simple code math such as Vector3D = Vector3D ^ Vector3D which is the only thing i need to support other than my nominal types mentioned (Force, Moment, Distance etc.) The linker seems to always go for the operator^ overload associated decltype and throws an error even if I support Vector3D operator^ (const Vector3D& lhs, const Vector3D& rhs) to try to avoid linkage error. Would you know how to proceed to fix that discrepancy. – hdamlaj Nov 23 '20 at 05:54
  • @hdamlaj Compiler and exact error message? GCC compiles totally fine, see [here](godbolt.org/z/WY669Y). As you had subtraction of types already in initial version, I assume you have custom `operator-` already defined? – Aconcagua Nov 23 '20 at 08:14