0
votes

I'm learning C++, so please be patient with me.

This is my matrix class

template<class T = double> class Matrix {

    using sequence_type = std::vector<T>;

public:

    ... some methods and constructors

    Matrix_ref<T> col(Index i_) { if (i_ < 0 || i_ > r) _error("out_of_bound"); return Matrix_ref<T>(elems.data(), Matrix_slice(i_, r, c), r); }
    Matrix_ref<T> row(Index i_) { if (i_ < 0 || i_ > c) _error("out_of_bound"); return Matrix_ref<T>(elems.data(), Matrix_slice(i_ * c, c, 1), c); }

    const Matrix_ref<T> col(Index i_) const { if (i_ < 0 || i_ > r) _error("out_of_bound"); return Matrix_ref<T>(elems.data(), Matrix_slice(i_, r, c), r); }
    const Matrix_ref<T> row(Index i_) const { if (i_ < 0 || i_ > c) _error("out_of_bound"); return Matrix_ref<T>(elems.data(), Matrix_slice(i_ * c, c, 1), c); }

    Matrix_ref<T> operator[](Index r_) { return row(r_); }


private:
    sequence_type elems;
    Index r;
    Index c;

    ...other methods

};

This is a struct that return correct index of an element in a row (practically it calculate the "stride")

struct Matrix_slice {

    Matrix_slice(Index first_, Index size_, Index stride_) : first(first_), size(size_), stride(stride_) {}

    const Index first;
    const Index size;
    const Index stride;

    Index operator()(Index i) { return first + stride * i; }
    const Index operator()(Index i) const { return first + stride * i; }
};

And this is a "reference" to the matrix. If I use the [] operator with the matrix, I get a matrix_ref.

template<class T = double> class Matrix_ref {

public:
    Matrix_ref(T* elems_, Matrix_slice slice_, Index ref_size_) : elems(elems_), slice(slice_), ref_size(ref_size_) {}

    T& at(Index i) { if (i < 0 || i >= ref_size) _error("out_of_bound"); return elems[slice(i)]; }
    const T& at(Index i) const { if (i < 0 || i >= ref_size) _error("out_of_bound"); return elems[slice(i)]; }

    T& operator[](Index i) { return elems[slice(i)]; }
    const T operator[](Index i) const { return elems[slice(i)]; }

    constexpr Index size() const { return ref_size; }

private:
    T* elems;
    const Matrix_slice slice;
    const Index ref_size;

};

This is the definition of operator*:

template<class T> Matrix<T> operator*(const Matrix<T>& a, const Matrix<T>& b) {
    if (a.cols() != b.rows()) _error("Matrix size mismatch");
    Matrix<T> res(a.rows(), b.cols());
    for (Index i = 0; i < res.rows(); ++i)
        for (Index j = 0; j < res.cols(); ++j)
            res.at(i, j) = a.row(i) * b.col(j);
    return res;
}

The problem is here -> operator*(const Matrix& a, const Matrix& b) If I declare this operator with const Matrix& a and const...b, it doesn't works, but if I declare without const keyword, It works. But I think it's better to use const. How can I fix it? I think the problem is the fact that Matrix_ref has not T* elems declared as const. If I declare it as const, it works, but I can't modify a Matrix. I got the error at the return of the row/col method in Matrix class caused by this line "res.at(i, j) = a.row(i) * b.col(j);" in overload operator* function.

Complete code: https://github.com/H0lm3s/Matrix/tree/master

1
How does it not work? - NathanOliver
oh yes, sorry I get this error <No matching constructor for initialization of 'Matrix_ref<int>'> at the return of the const row/col method in Matrix class. - Sam
Please provide a minimal reproducible example as well as putting the exact error you get into the question itself. - AndyG
@AndyG hum, I don't know how to recreate a similar example but I can give you all the code so that you can compile and see the error. (I edited the question) - Sam

1 Answers

1
votes

As you said the issue is with constness.

const Matrix_ref<T> col(Index i_) const 
{ 
        if (i_ < 0 || i_ > r) _error("out_of_bound"); 
        return Matrix_ref<T>(elems.data(), Matrix_slice(i_, r, c), r);  // <-- 
}

when you call elems.data() from const method vector<T>::data() returns const T*, compiler complains because const T* cannot be assigned to T* - a risk of modifying data. However you know that row and col methods return const object of your proxy class const Matrix_ref<T> so you can use const_cast to remove constness from elemes.data(), an instance of proxy class will keep pointer to T, and because returned object is qualified as const only const methods can be invoked on this object, this prevents from modifying data in Matrix when you are using proxy class. The use of const_cast is safe in this case, so you can change row and col member functions as follows:

const Matrix_ref<T> col(Index i_) const { 
    if (i_ < 0 || i_ > r) _error("out_of_bound"); 
    return Matrix_ref<T>( const_cast<T*>(elems.data()) , Matrix_slice(i_, r, c), r); 
}                      // ^^^