c++templatesexpression-templates

Designing a Matrix class - expression template issues


I would like to design a Matrixclass 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;
}

Solution

  • I see two problems: assignment vs initialization and const-correctness.

    assignment vs initialization

    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;
    

    const-correctness

    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.