Consider a standard use of the CRTP, for some expression template mechanism, which keeps its children by value:
template <typename T, typename>
struct Expr {};
template <typename T>
struct Cst : Expr<T, Cst<T>>
{
Cst(T value) : value(std::move(value)) {}
private:
T value;
};
template <typename T, typename L, typename R>
struct Add : Expr<T, Add<T, L, R>>
{
Add(L l, R r) : l(std::move(l)), r(std::move(r))
private:
L l; R r;
};
etc.
Now, when implementing the operator, we must pass by reference, since the argument is to be downcast to the right type. The problem is that I find myself implementing four (!) versions of operator+
:
template <typename T, typename L, typename R>
Add<T, L, R> operator+(Expr<T, L>&& l, Expr<T, R>&& r)
{
return Add<T, L, R>(
std::move(static_cast<L&>(l)),
std::move(static_cast<R&>(r)));
}
template <typename T, typename L, typename R>
Add<T, L, R> operator+(const Expr<T, L>& l, Expr<T, R>&& r)
{
return Add<T, L, R>(
static_cast<const L&>(l),
std::move(static_cast<R&>(r)));
}
template <typename T, typename L, typename R>
Add<T, L, R> operator+(Expr<T, L>&& l, const Expr<T, R>& r)
{
return Add<T, L, R>(
std::move(static_cast<L&>(l)),
static_cast<const R&>(r));
}
template <typename T, typename L, typename R>
Add<T, L, R> operator+(const Expr<T, L>& l, const Expr<T, R>& r)
{
return Add<T, L, R>(
static_cast<const L&>(l),
static_cast<const R&>(r));
}
Indeed, if the goal is to minimize needless copying, one has to distinguish between temporaries (that can be moved) and lvalues (which must be copied), hence the four overloads.
In C++03, there is "no problem": we use const references and copy all the time, period. In C++11, we can do better, and it is the goal here.
Is there some trick that would allow me to write the addition logic once, or is writing a macro my best option here (since the logic will be repeated for other operators) ?
I'm also open to other suggestions about how to write expression templates with C++11. Just consider that the goal is to minimize copying, since the values stored in terminal nodes may be huge numbers, or matrices (in my precise case, terminal nodes may contain several megabytes of interpolated data, and copy is disabled for these objects -- for other objects, copying is possible).
Here is another approach to writing expression templates that allows the arguments to be passed by value:
template <typename T>
struct Expr : T {
Expr(T value) : T(value) { }
};
template <typename A,typename B>
struct Add {
A a;
B b;
Add(A a,B b) : a(a), b(b) { }
};
template <typename A,typename B>
Expr<Add<A,B> > operator+(Expr<A> a,Expr<B> b)
{
return Expr<Add<A,B> >(Add<A,B>(a,b));
}
There are a lot of implied copies, but I've found that the compiler does an excellent job of removing them.
To also make it convenient to use constants, you can write additional overloads:
template <typename A,typename B>
Expr<Add<Constant<A>,B> > operator+(const A& a,Expr<B> b)
{
return Expr<Add<Constant<A>,B> >(Add<Constant<A>,B>(a,b));
}
template <typename A,typename B>
Expr<Add<A,Constant<B> > > operator+(Expr<A> a,const B& b)
{
return Expr<Add<A,Constant<B> > >(Add<A,Constant<B> >(a,b));
}
where Constant
is a class template, such as:
template <typename T>
struct Constant {
const T& value;
Constant(const T& value) : value(value) { }
};
There are a lot of implied copies, but I've found that the compiler does an excellent job of removing them.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With