You have type mismatch issue!
The Matrix
is a class template which gives you a concrete type, when you instantiate with a template argument T
. That means the instantiated classes with two different T
s will be two distinct types of Matrix
.
That means, the Matrix<ret_type>
is different from Matrix<mat_type>
, hence the casting will not work, which you have tried at this line:
Matrix<ret_type> result = static_cast<Matrix<ret_type>>(copy_matrix);
This produces, the compiler error!
You need to iterate via each element of the Matrix<mat_type>
and cast to the ret_type
and make a new Matrix<ret_type>
out of it.
That being said,
The following is the updated code. I hope the comments can drive through the code!
#include <iostream>
#include <vector>
#include <utility> // std::move
#include <type_traits> // std::common_type
template <typename T> class Matrix /* final */
{
private:
std::vector<std::vector<T>> elements{};
std::size_t nRows{};
std::size_t nCols{};
public:
Matrix() = default; // default constructor
explicit Matrix(std::vector<std::vector<T>> matElems)
: elements{ std::move(matElems) }
, nRows{ elements.size() }
, nCols{ elements.empty() ? 0u : elements[0].size() }
{}
template<typename mat_type, typename scalar_type>
friend auto operator*(const Matrix<mat_type>& matrix, const scalar_type scalar)
->Matrix<typename std::common_type<mat_type, scalar_type>::type>;
// or
// -> Matrix<decltype(mat_type{} * scalar_type{})> ;
// some useful member functions!
void reserve(const std::size_t n) noexcept { elements.reserve(n); }
std::size_t size() const noexcept { return elements.size(); }
// to make the "Matrix" iteratable: Useful in range-based for loop/ standard algorithms.
auto begin() noexcept ->decltype(elements.begin()) { return elements.begin(); }
auto end() noexcept ->decltype(elements.end()) { return elements.end(); }
auto begin() const noexcept ->decltype(elements.cbegin()) { return elements.cbegin(); }
auto end() const noexcept ->decltype(elements.cend()) { return elements.cend(); }
// operator<< overload for printing
friend std::ostream& operator<<(std::ostream& out, const Matrix& obj) noexcept
{
for (const auto& raw: obj)
{
for (const T ele : raw) out << ele << ' ';
out << '\n';
}
return out;
}
};
// non-member operator* definition
template<typename mat_type, typename scalar_type>
auto operator*(const Matrix<mat_type>& matrix, const scalar_type scalar)
-> Matrix<typename std::common_type<mat_type, scalar_type>::type>
// or
// -> Matrix<decltype(mat_type{} * scalar_type{}) >
{
using ret_type = typename std::common_type<mat_type, scalar_type>::type;
Matrix<ret_type> result; // default constructed matrix!
// reserve some memory for "Matrix" for unwanted reallocations!
result.reserve(matrix.size()); // calls the Matrix<T>::reserve()
for (const std::vector<mat_type>& rawVec : matrix)
{
std::vector<ret_type> newRaw;
// reserve the memory for raw vectors for unwanted reallocations!
newRaw.reserve(rawVec.size());
for (mat_type element : rawVec) newRaw.emplace_back(element * scalar);
// add the updated raws to the "result"
result.elements.emplace_back(newRaw);
}
return result;
}
int main()
{
Matrix<int> matInt{ { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} } };
auto res = matInt * 0.1;
std::cout << res; // 0.1 0.2 0.3
// 0.4 0.5 0.6
// 0.7 0.8 0.9
return 0;
}
Here is (a complete demo)
Also, note that, you can always have one dimensional std::vector
(if the raw-size and col-size are run time) or std::array
(if the dimension is known at compile time), and treat them as two dimentional array by index manupulations.