3

I'm writing a template matrix class, and I am a bit confused as to how overloading the * operator would work.

I would want to overload something like this (omitting irrelevant code):

template<typename T>class Matrix4t
{

friend vector3 operator*(const vector3 &inputV3, const matrix4t &inputM4t);

template <typename scalarT>
friend scalarT operator*(const scalarT &inputSclT, const matrix4t &inputM4t);

public:
const matrix4t operator*(const matrix4t)

vector3 operator*(const vector3 &inputV3);

template <typename scalarT>
const matrix4t operator*(const scalarT&);

}

Assuming correct definitions for the individual multiplications I assume this should allow multiplication of my matrix object with an object of type vector3 from both sides of the operand, returning a vector3 each time. Also it should allow multiplication of my matrix with a scalar template value, which could be float, double etc. The definitions of these methods would need to be different, to allow for vector vs scalar multiplication, hence not just using the template for both

On using this operator with a vector3 though, would the compiler know to use the explicitly declared vector3 method, or would it try and create the templated version of the method, which would not work as the definition is written to only allow for a scalar value, and probably also then complain about method redefinition.

Any thoughts on if this would work, or how else I could go about it?

Cheers

timdykes
  • 610
  • 6
  • 12
  • What is `vector3`? (is it a template? are there any implicit conversions allowed?) What types will be used as `scalarT`? Do you want to multiply *any* scalar type with any instantiation of `Matrix4t`? (i.e. `double * Matrix4T`?) – David Rodríguez - dribeas Aug 07 '12 at 21:29
  • I havent tried it yet, as I am planning out how the class will be structured before fully writing it and I couldnt think of a way around this. – timdykes Aug 07 '12 at 21:31
  • @DavidRodríguez-dribeas vector3 is template yes, and yes ScalarT will be any scalar type so could be float, double, int, whatever. I would like to be able to to as you say, multiply any scalar type with any instantiation – timdykes Aug 07 '12 at 21:35
  • @DavidRodríguez-dribeas I just noticed I missed the bit where you ask about implicit conversions, and i didnt really describe what my vector3 was. Im not totally sure what you mean there - the vector is a simple templated container for 3 elements xyz - implicit conversions would be allowed in the methods definition, so it wouldnt matter if it was a vector of 3 floats or 3 doubles the multiplication would work the same. But I would still need another method for single scalar values, or some way of detecting the datatype from within the template method and performing appropriate multiplications – timdykes Aug 07 '12 at 22:19
  • So `vector3` represents both 1x3 and 3x1 vectors with no way to distinguish between the two, and the multiplication just assumes you're passing in the right type? Also, I assume that `Matrix4t` represents a 3x3 matrix? – Mark B Aug 07 '12 at 23:27
  • @timofiend: Given a `Matrix4t` and a scalar of type `double`, what would be the type of the result? (Conversely if the matrix held `double` and the scalar was `float`) What about multiplications with vectors of different types? What would the resulting types be? – David Rodríguez - dribeas Aug 08 '12 at 00:17

1 Answers1

4

I have the feeling that you might be aiming for a more complex solution than needed, so I will start building from the ground up, and leave some details for later. First I will start analyzing your proposed syntax and what it means.

friend vector3 operator*(const vector3 &v, const matrix4t &m);
template <typename scalarT>
friend scalarT operator*(const scalarT &inputSclT, const matrix4t &inputM4t);

These are friend declarations. Friend declarations declare (tell the compiler that there is) an entity external to the class and that such an entity should be granted full access to the internals of this class (which in this case is a template). The second friend declaration is of a free function template operator* that takes a scalarT type and a matrix4T<T> object both by const reference and yields an scalarT value. This friend declaration seems strange in that the multiplication of a matrix by a scalar value usually yields another matrix of the same dimensions, rather than just a scalar value.

Note that inside the class template, the name of the template matrix4t does not refer to the template, but the particular specialization (i.e. it represents matrix4t<T>, not the name of the template). The distinction might not seem important now, but sooner or later you will realize the importance of it.

The second declaration is of a non-templated free function operator* that takes a vector3 and a matrix4t both by const reference and yields another vector3. Since we are inside the definition of the matrix4t, the name of the template refers to the specialization matrix4t<T>, but vector3 refers just to the template, and not any particular instantiation. You should either make that a template that accepts vector3<U> for any given type U, or else a non-templated function that accepts a single type (which can be the T argument of our template): vector3<T>. In the same way as with the other declaration, the return value might be off or not... depends on what are the dimensions of the vector (is it row or column?)

Regarding the actual befriending, I recommend that you read this answer to a different similar question.

As of the actual operators, for the scalar type, I would suggest the following approach:

  • Implement operator*= taking the scalar of the same type as is stored as an argument
  • Implement both versions of operator* in terms of operator*=

.

template <typename T> class matrix4t {
public:
    matrix4t& operator*=( T scalar ); // and implement
    friend matrix4t operator*( matrix4t lhs, T scalar ) {
       return lhs*=scalar;
    }
    friend matrix4t operator*( T scalar, matrix4t rhs ) {
       return rhs*=scalar;
    }
};

Notes: operator*= is implemented once, taking the matrix as left hand side (you could also offer an overload taking the matrix as right hand side, but it would look a bit strange: 5 *= matrix yielding a matrix...). Implement operator* as free functions (friendship is only used to provide the free function for each instantiating type, read the linked answer), by forwarding to operator*=.

For the case of multiplying by a vector (and because it is not symmetric) the trick of dispatching to a single implementation will not work, but you can provide the two implementations as non-templated friends as above.

If you wanted to provide operations with mixed types, then all of the above would have to be templates. The added complexity is not the template, but rather determining what the result types should be if you want to promote the types. With C++11 the easiest way would be using decltype and trailing return types:

template <typename U>
friend auto operator*( matrixt4 lhs, U scalar ) -> matrix4t< decltype(scalar * element(0,0) ) > {
   // implement here
}

And similarly for operator*(scalar,matrix). Note that if you are promoting types, operator*= might not make sense at all (as the type would be the same as the lhs), or if it did, it might or might not produce the result that you want. If you consider doing it, the operator+ would have to take the matrix4t<A> argument by reference (as it might not be of the appropriate type) and copy into the matrix4t<B> return type (where A and B are respectively the types of the matrix being multiplied and the result).

From here on, you should decide what you need or want to implement, and you might want to ask more specific questions as they come by.

Community
  • 1
  • 1
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Thanks for the in depth reply. With regards to your comment previous to this answer, the return type should always be the type of the input matrix rather than the scalar, and conversions should be carried out implicitly. The responsibility is on the user to know not to multiply incompatible types. Will the compiler carry out the conversions in the definition to conform to the return type even with a const reference arg? The return type of the friend you pointed out to be strange is actually meant to be matrix4t of the same type as the input matrix rather than a scalarT, good point. – timdykes Aug 08 '12 at 09:10
  • Thanks for the reference to your other answer as well that was also helpful. I would like to provide the operations with mixed types, is there a similar method to the decltype method without c++11? I will think further on how I want to implement with your answers as reference and, as you say, ask more specific questions when they arise. – timdykes Aug 08 '12 at 09:18
  • @timofiend: I am not sure I understand this: *Will the compiler carry out the conversions in the definition to conform to the return type even with a const reference arg?* What conversions? If the return type should be a `matrix4t` of the same type `T`, then there is no conversion required. Maybe if you illustrated your concern with some pseudocode it could help. Re. the mixed types, yes, you can do that with C++03 by explicitly adding metafunctions that given two input types will yield the *common type*, the simplest way would be using a prepacked solution like `boost::common_type` for this. – David Rodríguez - dribeas Aug 08 '12 at 11:44
  • Sorry if I am being unclear, what I meant is if I had a function such as this: `template Matrix4t &operator*=(const Matrix4t& rhs)` And in one particular instance of usage the rhs matrix is double, while the lhs/return matrix is float. When multiplying, the compiler would usually automatically convert the doubles to floats for me would it not? As the input is a const reference, this means it cannot be changed in any way yes? So can the compiler still convert the values even though its not allowed to change the rhs matrix? – timdykes Aug 08 '12 at 12:02
  • @timofiend: Yes and no. While multiplying it will promote the `float` to `double` but then it will demote back to `float` to store it inside your object, since the type of the object cannot be changed it will still store `float`. That is one of the reasons I mentioned that if you want mixed type arithmetic `operator*=` might not be a good option, as it effectively fixes the return type to be that of the left hand side. – David Rodríguez - dribeas Aug 08 '12 at 12:09
  • Mixed type arithmetic is going to be complex, painful and probably inefficient. Do you really need it? In many cases, adding a converting constructor and letting the user convert manually *before* applying the operators will have a small impact on their code (which might actually be good, as it makes it more explicit that a potentially expensive *copy* operation is going on) and will lead to less surprises in behavior. If you still want to provide mixed type arithmetic, you will need to handle all promotions on the return type manually and use *complex* (at least not trivial) metaprogramming.. – David Rodríguez - dribeas Aug 08 '12 at 12:24
  • ... to manage the whole array of combinations (this is simpler in C++11, if you don't want to do it in the most efficient way) – David Rodríguez - dribeas Aug 08 '12 at 12:24
  • Blurgh I deleted my previous comment by accident. Thanks very much for the help, I think I may stick to fixed type arithmetic for now until I have some more experience metaprogramming, and then try my hand at mixed. Im going to mark this as correct, as although my issue is not totally solved I think that is due to me not being completely sure of how I want to do it, and you have provided several viable options, so thanks very much! I shall probably post a more specific problem when I do get round to trying to implement it. – timdykes Aug 08 '12 at 12:28