2

Writing an operator function to multiply a custom template matrix class with a scalar.

template <typename T>
class Matrix 
{
public:
    Matrix() //default constructor
    Matrix(size_t Row, size_t Col); //initialize with zeros of row,col 
    explicit Matrix(const vector<vector<T>> matElems); // create a matrix from vec<vec>>
    friend Matrix<ret_type> operator*(const Matrix<mat_type> matrix,
        const scalar_type scalar);

private:
    vector<vector<T>> elements;
    std::size_t nRows;
    std::size_t nCols;
};

template <typename mat_type, typename scalar_type,
    typename ret_type = decltype(mat_type()* scalar_type())>
    Matrix<ret_type> operator*(const Matrix<mat_type> matrix,
        const scalar_type scalar) 
{
    Matrix<mat_type> copy_matrix = matrix;

    Matrix<ret_type> result = static_cast<Matrix<ret_type>>(copy_matrix);

    for (vector<ret_type> v : result.elements)
    {
        std::transform(v.begin(), v.end(), v.begin(),
            std::bind1st(std::multiplies<ret_type>(), scalar));
    }
    return result;
}

Error:

no matching function for call to ‘Matrix<double>::Matrix(Matrix<int>&)’
  on the line   **Matrix<ret_type> result = static_cast<Matrix<ret_type>>(copy_matrix)**

The reason of doing this is to cast an int type matrix to double if it is multiplied by a double scalar.

JeJo
  • 30,635
  • 6
  • 49
  • 88
palas
  • 19
  • 4
  • Construct `Matrix` using the constructor taking `Row, Col`, then call `std::transform` so it reads from `matrix` and writes into `result`. – Igor Tandetnik Aug 22 '21 at 13:23

1 Answers1

2

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 Ts 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.

JeJo
  • 30,635
  • 6
  • 49
  • 88