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;
}
Note: the following solution uses the implementation from my previous answer.
Determining the dimensions of a
AddExpris hard to do without some modifications resulting in problems with determining the dimensions of the multiplication expression. (The dimensions need to be a compile time constant.) The only way of determining the dimensions would be to recursively inspect the rhs and lhs template params of the expressions, but simply keeping the info as static constexpr member variables as suggested in my previous answer makes it much easier to do this.As stated in the answer it would be best to implement the operators at namespace scope which allows you to implement them for all types of matrix-like operands only once: