#include <iostream>
using namespace std;

template<typename T>
class matrix;

template<typename T>
ostream& operator<<(ostream& o, const matrix<T>& M);

template<typename T>
matrix<T> operator*(matrix<T>& M1, matrix<T>& M2);

template<typename T>
class matrix
{
public:
    T** a;
    int m;
    int n;
    matrix();
    matrix(int n, int m);
    ~matrix();
    matrix(const matrix<T>& M);
    matrix<T>& operator=(const matrix<T>& M);
    friend matrix<T> operator*<>(matrix<T>& M1, matrix<T>& M2);
    friend ostream& operator<<<>(ostream& o, const matrix<T>& M);

    // Перевантаження оператору [] для двовимірного масиву
    T* operator[](int r);
};

template <typename T>
matrix<T>::matrix()
{
    a = nullptr;
    m = 0;
    n = 0;
};

template <typename T>
matrix<T>::matrix(int n, int m)
{
    this->n = n;
    this->m = m;
    a = new T * [n];
    for (int i = 0; i < n; i++)
    {
        a[i] = new T[m];
        for (int j = 0; j < m; j++)
        {
            a[i][j] = 0;
        }
    }
};

template <typename T>
matrix<T>::~matrix()
{
    if (a != nullptr)
    {
        for (int i = 0; i < n; i++)
        {
            delete[] a[i];
        }
        delete[] a;
        a = nullptr;
    }
};

template<typename T>
matrix<T>::matrix(const matrix& M)
{
    this->n = M.n;
    this->m = M.m;
    a = new T * [n];
    for (int i = 0; i < n; i++)
    {
        a[i] = new T[m];
        for (int j = 0; j < m; j++)
        {
            a[i][j] = M.a[i][j];
        }
    }
};

template<typename T>
matrix<T>& matrix<T>::operator=(const matrix<T>& M)
{
    if (this->a != nullptr)
    {
        for (int i = 0; i < n; i++)
        {
            delete[] a[i];
        }
        delete[] a;
        a = nullptr;
    }
    this->n = M.n;
    this->m = M.m;
    a = new T * [n];
    for (int i = 0; i < n; i++)
    {
        a[i] = new T[m];
        for (int j = 0; j < m; j++)
        {
            a[i][j] = M.a[i][j];
        }
    }
    return *this;
};



template<typename T>
ostream& operator<<(ostream& o, const matrix<T>& M)
{
    for (int i = 0; i < M.n; i++)
    {
        for (int j = 0; j < M.m; j++)
        {
            o << M.a[i][j] << '\t';
        }
        o << endl;
    }
    o << endl;
    return o;
};

template<typename T>
matrix<T> operator*(matrix<T>& M1, matrix<T>& M2)
{
    matrix<T> temp(M1.n, M2.m);
    if (M1.m != M2.n)
    {
        cout << "error *" << endl;
        return temp;
    }
    for (int i = 0; i < M1.n; i++)
    {
        for (int j = 0; j < M2.m; j++)
        {
            for (int k = 0; k < M1.m; k++)
                temp.a[i][j] += M1.a[i][k] * M2.a[k][j];
        }
    }
    return temp;
};


template<typename T>
T* matrix<T>::operator[](int r) {
    if (r >= n) {
        throw std::out_of_range("Вихід за діапазон рядків");
    }
    return this->a[r];
}

int main()
{
    matrix<double> m1(2, 2);
    matrix<double> m2(2, 2);
    matrix<double> res;
    m1[0][0] = 0.5;
    m1[0][1] = 2.0;
    m1[1][0] = 3.0;
    m1[1][1] = 4.0;

    m2[0][0] = 0.5;
    m2[0][1] = 3.0;
    m2[1][0] = 2.0;
    m2[1][1] = 4.0;

    res = m1 * m2;
    cout << res << endl;
};

