Reputation:
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 with 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 declared the matrix as a double, I can multiply the matrix only by doubles and so on...
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 behave? It is possible to let doubles be multiplied by others arithmetic types?
Upvotes: 4
Views: 157
Reputation: 23590
Yes. Declare the function in the Matrix class as
template <typename T>
class Matrix
{
public:
/* ... */
template <typename S>
inline Matrix & operator*=( const S & value );
/* ... */
};
The 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 the member function. You need to write template
twice. A bit odd, but that's C++ syntax.
In case of the 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;
}
Upvotes: 1
Reputation: 208323
The problem that you are seeing is that template type deduction requires a perfect match of all of the deduced types. In your case you have a template that takes a single type argument T
that is both the scalar and the matrix types. When the compilers sees the operation: 5 * m1
, it deduces T == int
for the first argument but T == double
for the second argument, and type deduction fails.
There are multiple approaches around this, as it has been 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 by value, the second one because for arithmetic types it is more efficient and idiomatic to pass by value; the first one because by moving the copy to the interface of the function you allow the compiler to elide copies]. This approach is simple, but will generate one operator*
for each combination of S
and T
in the program, even though the actual multiplication in operator*=
is always performed on T
.
Another approach would be to fix the type of the scalar
that you want to multiply by, for example, make it double
, generating only one operator*
per T
type that is multiplied:
template <typename T>
Matrix<T> operator*( Matrix<T> m, double scalar ) {
return m*=scalar;
}
In this approach there is a single operator*
, the one taking a double
as argument. As in the previous example, it might require two type conversions on the scalar (say you multiply Matrix<int>
by 5
, it will then convert 5
into a double
, which will then be converted back to int
to match the signature of operator*=
.
The third approach is to create a non-templated function that takes your Matrix
and another argument of the same type. This will be the closest to your original code, with the slight advantage that not being a template, it will allow conversions for the scalar argument. Theoretically you could define all such functions yourself manually:
Matrix<int> operator*( Matrix<int>, int ) { ... }
Matrix<double> operator*( Matrix<double>, double ) { ... }
But this becomes a maintenance problem very easily. Luckily, there is a feature in the language that allows for the definition of all those non-template functions generically. Although the syntax might not be the most natural. You just need to declare the 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; }
};
As we are inside the class template Matrix
, we can use Matrix
(without arguments) to refer to the current instantiation (Matrix<int>
, Matrix<double
...) [This might not seem obviously important, but it is, it is important to realize 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 the generic T
of the class template, but the current instantiating type (int
, double
...).
The language allows for the definition of a friend
function inside the class that has the declaration, and that will define the function at namespace level, although the declaration will only be found through Argument Dependent Lookup.
Whenever you instantiate a particular instance of your template (say Matrix<int>
) and call the operator, the compiler will generate the free function for you. Because the function is not templated, it will allow conversions on the arguments, and thus for Matrix<int> m
it will allow you to call m * 5.
by converting 5.
into an int
.
Upvotes: 0
Reputation: 34618
Sure, just allow two parameters to your templated free functions and member functions.
For example:
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 trigger compiletime errors if you try to multiply your Matrix with something nonsensical, that is, if T*U
is undefined.
Upvotes: 4