This is a follow-on question to my earlier post on writing a matrix-algebra header. I am using expression templates to avoid the creation of temporaries.
Lazy-evaluation is great for matrix operations performed coefficient-wise such as +
, -
, +=
, -=
, ==
, scalar-multiplication etc. But, I learnt from the Eigen website, that it's not efficient for matrix products.
My code eagerly evaluates any matrix products. The trouble is, while I can write expressions such as A * B
, A * (B+C)
and (A+B)*C
, I am unable to write (A+B)*(C+D)
, that is where both the left- and right- operands are AddExp
objects.
The compiler fails to find the right signature, even though I have add the relevant method definition.
Could someone take a look at my code, and help me - what definition should I add, to write expressions of the type (A+B)*(C+D)
?
Here is the code:
Lazy evaluation using expression templates
#include <array>
#include <iostream>
#include <cassert>
/**
* Compile time fixed-size matrix.
*/
template <typename LHS, typename RHS, typename T>
class SubExp;
template <typename T, int ROWS, int COLS>
class 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) const { 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);
}
template <typename R>
SubExp<AddExp<LHS, RHS, T>, R, T> operator-(R& exp) {
return SubExp<AddExp<LHS, RHS, T>, R, T>(*this, exp);
}
template <int ROWS, int COLS>
Matrix<T, ROWS, COLS> operator*(Matrix<T, ROWS, COLS>& exp) {
Matrix<T, ROWS, COLS> result;
assert(result.getRows() == exp.getCols());
assert(result.getCols() == exp.getRows());
int kMax = exp.getRows();
for (int i{}; i < ROWS; ++i)
{
for (int j{}; j < COLS; ++j)
{
for (int k{}; k < kMax; ++k)
{
result(i, j) += (*this)(i, k) * exp(k, j);
}
//std::cout << "result(" << i << "," << j << ")" << result(i, j) << "\n";
}
}
return result;
}
template <int ROWS, int COLS, typename L, typename R>
Matrix<T, ROWS, COLS> operator*(const AddExp<L, R, T>& exp2) {
Matrix<T, ROWS, COLS> result;
assert(getRows() == exp2.getCols());
assert(getCols() == exp2.getRows());
int kMax = exp2.getRows();
for (int i{}; i < ROWS; ++i)
{
for (int j{}; j < COLS; ++j)
{
for (int k{}; k < kMax; ++k)
{
result(i, j) += (*this)(i, k) * exp2(k, j);
}
//std::cout << "result(" << i << "," << j << ")" << result(i, j) << "\n";
}
}
return result;
}
LHS& getLHS() { return m_lhsExp; }
RHS& getRHS() { return m_rhsExp; }
int getRows() const { return m_lhsExp.getRows(); }
int getCols() const { return m_lhsExp.getCols(); }
private:
LHS& m_lhsExp;
RHS& m_rhsExp;
};
template <typename LHS, typename RHS, typename T>
class SubExp {
public:
SubExp(LHS& lhsExp, RHS& rhsExp) : m_lhsExp{ lhsExp }, m_rhsExp{ rhsExp } {}
T operator()(int i, int j) const { return m_lhsExp(i, j) - m_rhsExp(i, j); }
template <typename R>
SubExp<SubExp<LHS, RHS, T>, R, T> operator-(R& exp) {
return SubExp<SubExp<LHS, RHS, T>, R, T>(*this, exp);
}
template <typename R>
AddExp<SubExp<LHS, RHS, T>, R, T> operator+(R& exp) {
return AddExp<SubExp<LHS, RHS, T>, R, T>(*this, exp);
}
LHS& getLHS() { return m_lhsExp; }
RHS& getRHS() { return m_rhsExp; }
int getRows() const { return m_lhsExp.getRows(); }
int getCols() const { return m_lhsExp.getCols(); }
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(const Matrix& m) : m_rows{ m.m_rows }, m_cols{ m.m_cols }, data{ m.data } {}
template <typename RHS>
Matrix(const RHS& exp) : m_rows{ ROWS }, m_cols{ COLS } {
for (int i{}; i < ROWS; ++i) {
for (int j{}; j < COLS; ++j) {
(*this)(i, j) = exp(i, j);
}
}
}
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);
}
template <typename L, typename R>
Matrix<T, ROWS, COLS> operator*(const AddExp<L, R, T>& exp) {
Matrix<T, ROWS, COLS> result;
assert(result.m_rows == exp.getCols());
assert(result.m_cols == exp.getRows());
int kMax = exp.getRows();
for (int i{}; i < m_rows; ++i)
{
for (int j{}; j < m_cols; ++j)
{
for (int k{}; k < kMax; ++k)
{
result(i, j) += (*this)(i, k) * exp(k, j);
}
//std::cout << "result(" << i << "," << j << ")" << result(i, j) << "\n";
}
}
return result;
}
template <typename L, typename R>
Matrix<T, ROWS, COLS> operator*(const SubExp<L, R, T>& exp) {
Matrix<T, ROWS, COLS> result;
assert(result.m_rows == exp.getCols());
assert(result.m_cols == exp.getRows());
int kMax = exp.getRows();
for (int i{}; i < m_rows; ++i)
{
for (int j{}; j < m_cols; ++j)
{
for (int k{}; k < kMax; ++k)
{
result(i, j) += (*this)(i, k) * exp(k, j);
}
}
}
return result;
}
template <int P>
Matrix<T, ROWS, P> operator*(const Matrix<T, COLS, P>& exp) {
Matrix<T, ROWS, COLS> result;
assert(result.m_rows == exp.getCols());
assert(result.m_cols == exp.getRows());
int kMax = exp.getRows();
for (int i{}; i < m_rows; ++i)
{
for (int j{}; j < m_cols; ++j)
{
for (int k{}; k < kMax; ++k)
{
result(i, j) += (*this)(i, k) * exp(k, j);
}
//std::cout << "result(" << i << "," << j << ")" << result(i, j) << "\n";
}
}
return result;
}
auto getData() { return data; }
int getRows() const { return m_rows; }
int getCols() const { return m_cols; }
private:
std::array<T, ROWS* COLS> data;
int m_rows;
int m_cols;
};
template <typename T, int ROWS, int COLS>
std::ostream& operator<<(std::ostream& stream, Matrix<T, ROWS, COLS>& m)
{
stream << "Matrix<" << m.getRows() << "," << m.getCols() << ">" << "[ \n";
for (int i{ 0 }; i < ROWS; ++i)
{
for (int j{ 0 }; j < COLS; ++j)
{
stream << " " << m(i, j) << " ";
}
stream << "\n";
}
stream << " ]";
return stream;
}
template <typename LHS, typename RHS, typename T>
std::ostream& operator<<(std::ostream& stream, const AddExp<LHS, RHS, T>& m)
{
stream << "Matrix<" << m.getRows() << "," << m.getCols() << ">" << "[ \n";
for (int i{ 0 }; i < m.getRows(); ++i)
{
for (int j{ 0 }; j < m.getCols(); ++j)
{
stream << " " << m(i, j) << " ";
}
stream << "\n";
}
stream << " ]";
return stream;
}
int main() {
Matrix<int, 3, 3> A{ {1, 0, 0}, {0, 1, 0}, {0, 0, 1} };
Matrix<int, 3, 3> B{ {1, 0, 0}, {0, 1, 0}, {0, 0, 1} };
Matrix<int, 3, 3> C = A + B;
Matrix<int, 3, 3> D = C * (A + B);
// Matrix<int, 3, 3> D = (C+C)*(A+B); //does not compile
std::cout << D;
return 0;
}