I would like to design a Matrix
class that uses expression templates and avoids temporaries. I would like to start with something very simple, so I decided to just implement matrix addition, first.
I created a class AddExp
to represent a compound expression and overloaded the ()
operator that will help . And the copy-assignment operator Matrix& Matrix::operator=(AddExp& addExp)
triggers the evaluation, by the recursively invoking ()
on each of the elements in this expression.
However, the compiler complains that it can't convert AddExp<Matrix<int,3,3>,Matrix<int,3,3>>
to a Matrix<int,3,3>
. It puzzles me, why the copy-assignment operator of the Matrix
class can't do that.
Note that, if I replace Matrix C
by auto C
, the code will execute fine, but it's important to be able to write statements of the form Matrix C = <something>
.
Compiler Explorer - C++ (x86-64 gcc 12.2)
I spend quite some time over this. I would like to ask for some help; if you guys have played with expression templates, or find something fundamentally wrong with my code, it would be nice to correct it, and learn from it.
#include <array>
#include <iostream>
/**
* Compile time fixed-size matrix.
*/
template <typename LHS, typename RHS, typename T>
class AddExp {
public:
AddExp(LHS& lhsExp, RHS& rhsExp) : m_lhsExp{lhsExp}, m_rhsExp{rhsExp} {}
T operator()(int i, int j) { return m_lhsExp(i, j) + m_rhsExp(i, j); }
template <typename R>
AddExp<AddExp<LHS, RHS, T>, R, T> operator+(R& exp) {
return AddExp<AddExp<LHS, RHS, T>, R, T>(*this, exp);
}
// friend class Exp<AddExp,T>;
private:
LHS& m_lhsExp;
RHS& m_rhsExp;
};
template <typename T = double, int ROWS = 3, int COLS = 3>
class Matrix {
public:
Matrix() : m_rows{ROWS}, m_cols{COLS}, data{} {}
Matrix(std::initializer_list<std::initializer_list<T>> lst)
: m_rows{ROWS}, m_cols{COLS}, data{} {
int i{0};
for (auto& l : lst) {
int j{0};
for (auto& v : l) {
data[m_rows * i + j] = v;
++j;
}
++i;
}
}
T& operator()(int i, int j) { return data[ROWS * i + j]; }
template <typename RHS>
AddExp<Matrix<T, ROWS, COLS>, RHS, T> operator+(RHS& exp) {
return AddExp<Matrix<T, ROWS, COLS>, RHS, T>(*this, exp);
}
template <typename RHS>
Matrix<T, ROWS, COLS>& operator=(const RHS& exp) {
for (int i{}; i < ROWS; ++i) {
for (int j{}; j < COLS; ++j) {
(*this)(i, j) = exp(i, j);
}
}
return (*this);
}
// friend class Exp<Matrix,T>;
private:
std::array<T, ROWS * COLS> data;
int m_rows;
int m_cols;
};
int main() {
Matrix A{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
Matrix B{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
Matrix<int, 3, 3> C = A + B;
return 0;
}
I see two problems: assignment vs initialization and const-correctness.
In your main
you have:
Matrix<int, 3, 3> C = A + B;
This requires a Matrix
constructor which takes a const RHS& exp
. Chenge it to:
Matrix<int, 3, 3> C;
C = A + B;
Your T operator()(int i, int j)
should be const
, since you don't want to change the expression or data when performing a sum.
Check it on Godbolt.