Перегрузка friend бинарного оператора шаблонного класса с определёнными типами C++

Необходимо переопределить перемножение матриц для разных структур, чтобы реализовать следующий метод

Matrix<Neuron> operator*(const Matrix<Weight>& arg_1, const Matrix<Neuron>& arg_2)

Проблема в следующем, данный метод приводит к duplicate symbols. Каким образом мне реализовать данный метод, чтобы тот был работоспособный?


Вот код

#ifndef MLP_SRC_MATRIX_H_
#define MLP_SRC_MATRIX_H_

#include <cstring>
#include <random>
#include <stdexcept>

struct Weight {
  double data = {};

  Weight() {
    static std::random_device rd;
    static std::default_random_engine generator(rd());
    static std::uniform_real_distribution<double> distribution(-1.0, 1.0);
    data = distribution(generator);
  }
};

struct Neuron {
  double data = {};
};

template <class T>
class Matrix {
 public:
  Matrix(){};
  Matrix(const unsigned rows, const unsigned cols);
  Matrix(const Matrix<T>& other);

  ~Matrix();

  Matrix<T>& operator=(const Matrix<T>& other);

  friend Matrix<Neuron> operator*(const Matrix<Weight>& arg_1, const Matrix<Neuron>& arg_2);

  T& operator()(const unsigned row, const unsigned col);
  const T& operator()(const unsigned row, const unsigned col) const;

  const unsigned GetRows() const;
  const unsigned GetCols() const;

 private:
  T** m_matrix = nullptr;
  unsigned m_rows = 0U;
  unsigned m_cols = 0U;

  void Init(const unsigned rows, const unsigned cols);
  void Destroy();
};

template <class T>
Matrix<T>::Matrix(const unsigned rows, const unsigned cols)
    : m_rows{rows}, m_cols{cols} {
  Init(m_rows, m_cols);
}

template <class T>
Matrix<T>::Matrix(const Matrix<T>& other) {
  *this = other;
}

template <class T>
Matrix<T>::~Matrix() {
  Destroy();
}

template <class T>
Matrix<T>& Matrix<T>::operator=(const Matrix<T>& other) {
  if (this != &other) {
    Destroy();

    m_rows = other.m_rows;
    m_cols = other.m_cols;

    Init(m_rows, m_cols);

    for (unsigned i = 0; i < m_rows; ++i) {
      for (unsigned j = 0; j < m_cols; ++j) {
        m_matrix[i][j] = other.m_matrix[i][j];
      }
    }
  }
  return *this;
}

template <class T>
T& Matrix<T>::operator()(const unsigned row, const unsigned col) {
  return m_matrix[row][col];
}

template <class T>
const T& Matrix<T>::operator()(const unsigned row, const unsigned col) const {
  return m_matrix[row][col];
}

template <class T>
const unsigned Matrix<T>::GetRows() const {
  return m_rows;
}

template <class T>
const unsigned Matrix<T>::GetCols() const {
  return m_cols;
}

template <class T>
void Matrix<T>::Init(const unsigned rows, const unsigned cols) {
  m_matrix = new T*[rows];
  for (unsigned i = 0; i < rows; ++i) {
    m_matrix[i] = new T[cols]{};
  }
}

template <class T>
inline void Matrix<T>::Destroy() {
  if (m_matrix != nullptr) {
    for (unsigned i = 0; i < m_rows; ++i) {
      delete[] m_matrix[i];
    }
    delete[] m_matrix;
  }

  m_rows = m_cols = {};
}

Matrix<Neuron> operator*(const Matrix<Weight>& arg_1, const Matrix<Neuron>& arg_2) {
  Matrix<Neuron> result(arg_1.GetRows(), arg_2.GetCols());
  for (int i = 0; i < result.GetRows(); ++i) {
    for (int j = 0; j < result.GetCols(); ++j) {
      result(j, i).data = 0.f;
      for (int k = 0; k < arg_1.GetCols(); ++k) {
        result(i, j).data += arg_1(i, k).data * arg_2(k, j).data;
      }
    }
  }
  return result;
}

#endif  // MLP_SRC_MATRIX_H_

Ответы (1 шт):

Автор решения: DmitryK

Попробовал ваш код - у меня ошибки нет.
Есть несколько замечаний по коду:

Какой смысл в двух одинаковых структурах struct Weight и struct Neuron. У них отличие, что Weight создается со случайным значением, а Neuron со значением по умолчанию? Проще в классе Matrix сделать функцию RandomInit() которая будет заполнять матрицу случайными значениями. И тогда вы будете работать только с одним типом Matrix<double>.

Зачем в функцию Init() передавать значения? Она является методом класса и переменные-члены класса ей доступны.

// rows и cols являются членами класса и доступны в функции
template <class T>
void Matrix<T>::Init(const unsigned rows, const unsigned cols) 
{
    m_matrix = new T*[rows];
    for (unsigned i = 0; i < rows; ++i) 
        m_matrix[i] = new T[cols]{};
}

В деструкторе вы проверяете на nullptr только указатель на массив указателей. При этом, операция new() в общем случае может не выделить память. А вы не обнуляете создаваемые указатели в функции Init() и не проверяете на nullptr в деструкторе.

void Matrix<T>::Init(const unsigned rows, const unsigned cols) 
{
    m_matrix = new T*[rows];
    for (unsigned i = 0; i < rows; ++i) 
        m_matrix[i] = new T[cols]{};
}

inline void Matrix<T>::Destroy() 
{
    if (m_matrix != nullptr) // здесь есть проверка
    {
        for (unsigned i = 0; i < m_rows; ++i)
            delete[] m_matrix[i];     // а здесь нет проверки

        delete[] m_matrix;
    }
}

А самое главное - почему вы сами управляете памятью? Почему не используете контейнеры? Наступание повторно на те же грабли конечно полезно для запоминания материала, но зачем?

class Matrix 
{
  private:
    vector< vector<T> > m_matrix;

В операторе умножения у вас ошибка - поменяны индексы местами. Вы обнуляете result(j, i).data = 0.f; а суммируете в другой элемент result(i, j).data +=. Ваше обнуление начнет портить уже посчитанный результат.
Операция умножения двух матриц выполнима только в том случае, если число столбцов в первом сомножителе равно числу строк во втором. Как минимум - нужно ввести такую проверку. У вас проверки нет, а циклы идут до количества строк и столбцов. Если они не совпадают вы можете выйти за пределы массива. Ну конечно возможно вы где-то делаете эту проверку, но логично её сделать именно в операторе умножения.

Matrix<Neuron> operator*(const Matrix<Weight>& arg_1, const Matrix<Neuron>& arg_2) 
{
    // вот здесь нужна проверка на совпадение !!!
    Matrix<Neuron> result(arg_1.GetRows(), arg_2.GetCols());
    for (int i = 0; i < result.GetRows(); ++i)
        for (int j = 0; j < result.GetCols(); ++j) 
        {
            result(j, i).data = 0.f; // испортит посчитанный результат
            // result(i, j).data = 0.f; можно не делать, т.к. result инициирован значениями по-умолчанию
            for (int k = 0; k < arg_1.GetCols(); ++k)
                result(i, j).data += arg_1(i, k).data * arg_2(k, j).data;
        }
    return result;
}
→ Ссылка