I am trying to wrap my head around expression templates. In the wikipedia article, an example is given, where an expression template VecSum
stores const references to its two operands. A Vec
is an expression template that wraps an std::vector<double>
. I will first pose my question and then give a complete rundown of the example below.
Can I re-use expressions that use const references to temporaries? And if not, how would I implement light-weight, re-useable expression templates?
For three Vec
s a
, b
, and c
the expression a+b+c
is of type
VecSum<VecSum<Vec, Vec>, Vec>
If I understand correctly, the inner VecSum
is a temporary and the outer VecSum
stores a const reference to the inner VecSum
. I believe the lifetime of the inner VecSum
temporary is guaranteed until the expression a+b+c
gets evaluated. Correct? Does this mean that the expression cannot be reused without the danger of creating dangling references?
auto expr = a + b + c;
Vec v1 = expr; // ok
Vec v2 = expr; // not ok!
If so, how can this example be modified, so that
- the expressions are reusable
- the expressions do not store copies of their operands (at least in situations where it is not necessary)?
Full code example
For completeness - and in case the wikipedia article is updated in the meantime, let me repeat the example code here and give an example in the main
that I believe creates a dangling reference.
#include <cassert>
#include <vector>
template <typename E>
class VecExpression {
public:
double operator[](size_t i) const
{
// Delegation to the actual expression type. This avoids dynamic polymorphism (a.k.a. virtual functions in C++)
return static_cast<E const&>(*this)[i];
}
size_t size() const { return static_cast<E const&>(*this).size(); }
};
class Vec : public VecExpression<Vec> {
std::vector<double> elems;
public:
double operator[](size_t i) const { return elems[i]; }
double &operator[](size_t i) { return elems[i]; }
size_t size() const { return elems.size(); }
Vec(size_t n) : elems(n) {}
// construct vector using initializer list
Vec(std::initializer_list<double> init) : elems(init) {}
// A Vec can be constructed from any VecExpression, forcing its evaluation.
template <typename E>
Vec(VecExpression<E> const& expr) : elems(expr.size()) {
for (size_t i = 0; i != expr.size(); ++i) {
elems[i] = expr[i];
}
}
};
template <typename E1, typename E2>
class VecSum : public VecExpression<VecSum<E1, E2> > {
E1 const& _u;
E2 const& _v;
public:
VecSum(E1 const& u, E2 const& v) : _u(u), _v(v) {
assert(u.size() == v.size());
}
double operator[](size_t i) const { return _u[i] + _v[i]; }
size_t size() const { return _v.size(); }
};
template <typename E1, typename E2>
VecSum<E1, E2>
operator+(VecExpression<E1> const& u, VecExpression<E2> const& v) {
return VecSum<E1, E2>(*static_cast<const E1*>(&u), *static_cast<const E2*>(&v));
}
int main() {
Vec v0 = {23.4,12.5,144.56,90.56};
Vec v1 = {67.12,34.8,90.34,89.30};
Vec v2 = {34.90,111.9,45.12,90.5};
auto expr = v0 + v1 + v2;
Vec v1 = expr; // ok
Vec v2 = expr; // not ok!
}
Edit:
I just realized this might be a duplicate of this question. However the answers to both questions are very different and all usefull.