I am currently writing a simple algebra library in C++. The library has a Matrix
class defined as follows:
template<typename T>
class Matrix {
private:
size_t n, m;
T **const tab;
public:
Matrix(const size_t n, const size_t m);
Matrix(const Matrix&);
~Matrix(void);
RowVector<T> operator[](const int i);
const RowVector<T> operator[](const int i) const;
...
};
The code is smart, it not only lets you access some specific element of a matrix (like M[2][3]
), but it also allows you to use M[2]
as a vector.
However, as an optimization, the data is not copied to the vector. Vector
constructed by operator[]
points to memory inside the Matrix
. This has to be done, if the operator[]
call would construct a new vector every time, then accessing one matrix element would be very ineffective.
This also makes it possible to assign matrix elements like this:
M[5][7] = 12;
However, I have two problems with this solution:
operator[]
is an rvalue and it's not possible to set a whole row of the matrix like this:Matrix M(2, 3);
RowVector v(3);
M[0] = v;
v = M[7];
I want RowVector
's copy constructor, not move constructor, to be called. But operator[]
's return value is an rvalue and the compiler will call the move constructor on it.
In conclusion:
RowVector
is an rvalue, but it contains a pointer to Matrix
's internal data, and I want it to be treated as an lvalue reference.
Is this possible in C++? If not, could someone suggest some refactoring of this code to achieve what I want?
EDIT:
RowVector
declaration:
template<typename T>
class Vector {
protected:
const size_t len;
T *const tab;
bool malloced;
public:
Vector(const size_t len);
Vector(T *const tab, const size_t len);
virtual ~Vector(void);
T &operator[](const int i);
const T &operator[](const int i) const;
};
template<typename T>
class RowVector : public Vector<T> {
public:
RowVector(const size_t len);
RowVector(T *const tab, const size_t len);
~RowVector(void) override;
template<typename U> friend
std::ostream &operator<<(std::ostream&,
const RowVector<U>&);
};
Vector
definition:
template<typename T>
Vector<T>::Vector(const size_t len) :
len(len),
tab(new T[len]),
malloced(true)
{
}
template<typename T>
Vector<T>::Vector(T *const tab, const size_t len) :
len(len),
tab(tab),
malloced(false)
{
}
template<typename T>
Vector<T>::~Vector(void)
{
if (malloced)
delete tab;
}
template<typename T>
T &Vector<T>::operator[](const int i)
{
return tab[i];
}
template<typename T>
const T &Vector<T>::operator[](const int i) const
{
return tab[i];
}
Matrix<T>::operator[]()
:
template<typename T>
RowVector<T> Matrix<T>::operator[](int i)
{
return RowVector(tab[i], m);
}
template<typename T>
const RowVector<T> Matrix<T>::operator[](int i) const
{
return RowVector(tab[i], m);
}
Let me start by saying that I agree with @StoryTeller, this would be best split into a RowVector and RowReference class. However, I'll still go ahead and answer your original question.
Okay, so let's go over this piece by piece.
- Return value of operator[] is an rvalue and it's not possible to set a whole row of the matrix like this:
Matrix M(2, 3);
RowVector v(3);
M[0] = v;
That's actually not the issue here. It's completely valid to assign to the temporary return value of a function if it's a class type. However, the reason that you're likely running into issues is that you have const members in your RowVector class definition, namely tab
and len
. In C++ the default assignment operator =
is equivalent to saying something like
FOR member IN this
member = other.member
END
With this in mind, trying to do len = other.len
is invalid since len is const
. For that reason, C++ disables the assignment operator for any classes with const
members. If you want to define your own assignment operator, you're completely free to do so. I imagine you'd be looking at something like
RowVector<T>& operator=(const RowVector<T>& other){
if (len != other.len)
throw std::runtime_error("Cannot assign rows with different lengths!");
std::copy(other.tab, other.tab + len, tab);
return *this
}
That should allow you to set an entire row.
- When I type something like this:
v = M[7];
I want RowVector's copy constructor, not move constructor, to be called. But operator[]'s return value is an rvalue and the compiler will call the move constructor on it.
I don't believe the move constructor will come into play at all here. Default move constructors are automatically disabled for any class that defines a custom copy constructor, copy assignment operator, move constructor, move assignment operator, or destructor. Since RowVector defines a custom destructor, there should be no default move constructor defined and it looks like you didn't define one yourself. This issue probably comes back to the deletion of the assignment operator which would once again be fixed by defining a custom assignment operator.
On a slightly tangential note, storing matrix data as a double pointer T**
isn't really the most efficient way to create a matrix class. If you're going for efficiency, then you always want to aim for contiguous memory allocation, which in this case would be a std::vector
of size n x m, and you'd take care of indexing logic in the operator[]
. Of course, this would also mess up your existing vector classes, so I'll leave it up to you if you want to go that route.