Patterns and molding

I created a matrix class with templates:

template <typename T> class Matrix { static_assert(std::is_arithmetic<T>::value,""); public: Matrix(size_t n_rows, size_t n_cols); Matrix(size_t n_rows, size_t n_cols, const T& value); // Functions // Operators Matrix<T>& operator*=(const T& value) private: size_t rows; size_t cols; std::vector<T> data; }; 

I created the following two (external) operators to multiply my matrix by a number:

 // Inner operator used by the externals ones template <typename T> inline Matrix<T>& Matrix<T>::operator*=(const T& value) { for(size_t i(0); i < data.size(); i++) { data[i] *= value; } return *this; } template <typename T> inline Matrix<T> operator*(const T& value, const Matrix<T>& matrix) { Matrix<T> tmp(matrix); return tmp *= value; } template <typename T> inline Matrix<T> operator*(const Matrix<T>& matrix, const T& value) { return value * matrix; } 

The problem is that if I declare the matrix as double, I can only multiply the matrix by doubles, etc ....

 Matrix<double> m1(3,3,1.); 5. * m1; // Works 5 * m1; // Doesn't work (5 is an int and not a double) 

How can I fix this behavior? Is it possible to double multiply by other arithmetic types?

+4
source share
3 answers

Of course, just enable two parameters for your template free functions and member functions.

For instance:

 template <typename T> class Matrix { /* ... */ template <typename U> inline Matrix<T>& operator*=(const U& value) { for(size_t i(0); i < data.size(); i++) { data[i] *= value; } return *this; } }; template <typename T, typename U> inline Matrix<T> operator*(const U& value, const Matrix<T>& matrix) { Matrix<T> tmp(matrix); return tmp *= value; } 

This will cause compiletime errors if you try to propagate your matrix with something meaningless, that is, if T*U is undefined.

+4
source

Yes. Declare a function in the Matrix class as

 template <typename T> class Matrix { public: /* ... */ template <typename S> inline Matrix & operator*=( const S & value ); /* ... */ }; 

Definition looks like

 template <typename T> template <typename S> inline Matrix<T>& Matrix<T>::operator*=(const S& value) { for(size_t i(0); i < data.size(); i++) { data[i] *= value; } return *this; } 

for a member function. You need to write template twice. A bit strange, but this is the C ++ syntax.

In case of free functions you can write

 template <typename T, typename S> inline Matrix<T> operator*(const S& value, const Matrix<T> &mat) { Matrix<T> tmp(mat); return tmp *= value; } 
+1
source

The problem you see is that template type inference requires a perfect match for all type inference. In your case, you have a template that takes a single argument of type T , which is both scalar and matrix types. When compilers see the operation: 5 * m1 , it outputs T == int for the first argument, but T == double for the second argument, and type inference is not performed.

There are several approaches around this, as suggested, you can add a second template argument:

 template <typename T, typename S> Matrix<T> operator*( Matrix<T> m, S scalar ) { return m*=scalar; } 

[Note: both arguments are by value, the second is because for arithmetic types it is more efficient and idiomatic for passing by value; the first one, because by moving the copy to the function interface, you allow the compiler to delete the copies]. This approach is simple, but will generate one operator* for each combination of S and T in the program, even if the actual multiplication in operator*= always performed by T

Another approach is to fix the type of scalar that you want to propagate, for example, make it double , generating only one operator* per T type that multiplies:

 template <typename T> Matrix<T> operator*( Matrix<T> m, double scalar ) { return m*=scalar; } 

There is one operator* in this approach that takes double as an argument. As in the previous example, a scalar may need to convert two types (say, you multiply Matrix<int> by 5 ), then convert 5 to double , which will then be converted back to int to match the operator*= signature.

The third approach is to create a non-templated function that takes your Matrix and another argument of the same type. This will be closest to your source code, with a slight advantage that, not being a template, it will allow conversions for the scalar argument. Theoretically, you can define all such functions yourself:

 Matrix<int> operator*( Matrix<int>, int ) { ... } Matrix<double> operator*( Matrix<double>, double ) { ... } 

But it becomes a maintenance problem very easily. Fortunately, the language has a function that allows you to outline all these non-template functions. Although the syntax may not be the most natural. You just need to declare a free function as a friend of your template and define it inside the class template definition:

 template <typename T> class Matrix { // ... friend Matrix operator*( Matrix m, T scalar ) { return m*=scalar; } }; 

Since we are inside the Matrix class template, we can use Matrix (without arguments) to refer to the current instance ( Matrix<int> , Matrix<double ...) [This may not seem very important, but it is important, it is important to understand when Matrix refers to the template, and when it refers to the class generated from the template]. The second argument to the function is T Again, this is not a generic T the class template, but the current instance type ( int , double ...).

The language allows you to define a friend function within a class that has a declaration and that will define a function at the namespace level, although the declaration will only be found by searching for Dependent Lookup arguments.

Whenever you instantiate a specific instance of your template (for example, Matrix<int> ) and call the operator, the compiler will generate a free function for you. Since this function is not a template, it allows you to convert arguments, and thus for Matrix<int> m it will allow you to call m * 5. by converting 5. to int .

0
source

All Articles