Some background on what I try to do: I am trying to implement a library doing quantum mechanics. As quantum mechanics is basically just linear algebra, I'm using the armadillo linear algebra library underneath. Armadillo uses lazy evaluation to do some smart tricks with matrices, which gives a pretty good abstraction from what is actually going on and looks close to matlab code.
I want to do something similar, but I also want to be able to use auto
, which is not possible with armadillo (or eigen).
I have been looking around a little, and this answer contains what I think is the typical way of implementing this: https://stackoverflow.com/a/414260/6306265
The problem with this approach is that when you write
auto C = A+B;
you get a C
that is a matrix_add
, not a matrix
. Even if matrix_add
behaves similarly enough to matrix
, the fact that matrix_add
contains references to A
and B
makes it awkward to carry around. E.g.
auto A = matrix(2,2,{0,1,0,1});
auto B = matrix(2,2,{1,0,1,0});
auto C = A+B;
C.printmatrix(); // 1,1 ; 1,1
but
auto A = matrix(2,2,{0,1,0,1});
auto B = matrix(2,2,{1,0,1,0});
auto C = A+B;
A(0,0) = 1;
C.printmatrix(); // 2,1 ; 1,1
which is counter-intuitive. As mathematically intuitive behaviour is what I want to achieve, that is a problem.
Even worse is when I do
auto sumMatrices(const matrix& A, const matrix& B)
{
return A+B;
}
which returns a matrix_add
with references to local memory.
I would really like to be able to have the nice, overloaded behaviour but also be able to use auto
. My idea was to make a wrapper that can hold either a reference or an instance:
template<class T>
class maybe_reference
{
public:
maybe_reference(const T& t):
ptr_(std::make_unique<T>(t)),
t_(*ptr_)
{}
maybe_reference(std::reference_wrapper<const T> t):
t_(t.get())
{}
const T& get(){return t_;}
private:
unique_ptr<T> ptr_;
const T& t_;
}
It may not be implemented exactly this way, but the general idea is to have two constructors that can be clearly distinguished to ensure that get()
returns either the referenced object or the one in the unique_ptr
.
Modified matrix_add
:
class matrix_add {
public:
friend matrix_add operator+(const matrix& A, const matrix& B);
matrix_add(matrix_add&& other): A_(other.A_.get()), B_(other.B_.get()){}
private:
matrix_add(const matrix& A, const matrix& B): A_(std::ref(A)), B_(std::ref(B)){}
maybe_reference<matrix> A_;
maybe_reference<matrix> B_;
};
I have left out all the parts that make matrix_add
behave like a matrix
. The idea is to have the object refer to the outside objects A&B as long as it was constructed with A+B, but when it is move-constructed, it would own copies.
My question is basically: does this work?
I have been thinking that the move-constructor may be elided in some or all cases, which might be devastating.
Also, is there an alternative to achieve the same thing? I have been looking, but it seems that for linear algebra at least its either lazy or auto.
EDIT: Thanks to being reminded of the term "expression templates", my google search was a lot more fruitful. I found this reddit-post: https://www.reddit.com/r/cpp/comments/4puabu/news_about_operator_auto/
and the referenced papers, which allow specification of "casts" to auto. That would be the feature that really would make all of this work.