CRTP and perfect forwarding

Consider the standard use of CRTP for some expression template engine that saves 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; }; 

and etc.

Now, when implementing the operator, we must follow the link, since the argument must be disabled to the desired type. The problem is that I found that I am 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)); } 

In fact, if the goal is to minimize unnecessary copying, you need to distinguish between temporary (which can be moved) and lvalues ​​(which should be copied), therefore, four overloads.

In C ++ 03, β€œno problem”: we use const references and copy all the time, period. In C ++ 11, we can do better, and that is the goal here.

Is there any trick that would allow me to write add logic once, or write a macro my best option is here (since the logic will be repeated for other operators)?

I am also open to other suggestions on how to write expression patterns with C ++ 11. Just think that the goal is to minimize copying, since the values ​​stored in terminal nodes can be huge numbers or matrices (in my exact In this case, the terminal nodes may contain several megabytes of interpolated data, and copying is disabled for these objects - for other objects, copying is possible).

+4
source share
2 answers

Here's another approach to writing expression patterns that lets you pass arguments 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 many implied copies, but I found that the compiler does a great job of removing them.

To make it also 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 the class template, for example:

 template <typename T> struct Constant { const T& value; Constant(const T& value) : value(value) { } }; 

There are many implied copies, but I found that the compiler does a great job of removing them.

+5
source

Since objects are cheap, to jump to comments, I would make the operator+ argument an argument by value and let the compiler determine how many copies it can be avoided on callsite. To avoid slicing, this means that operator+ must work on derived types (which leads to a somewhat excessive binding of operator+ ). To get this under control, you probably need to use std::enable_if to give you something like the following:

 template <typename T, typename U> struct Expr { typedef T expr_type;//added for getting T in the enable_if. Could probably also behandled with a custom type trait }; template <typename L, typename R> typename std::enable_if<std::is_base_of<Expr<typename L::expr_type, L>, L>::value && std::is_base_of<Expr<typename L::expr_type, R>, R>::value, Add<typename L::expr_type, L, R>>::type operator+(L l, R r) { return Add<typename L::expr_type, L, R>(std::move(l), std::move(r)); } 

Of course, when you use it more often, a clause about the condition in the trait is a good idea, giving you something like this:

 template <typename L, typename R, typename T> struct AreCompatibleExpressions { static constexpr bool value = std::is_base_of<Expr<T, L>, L>::value && std::is_base_of<Expr<T, R>, R>::value; }; template <typename L, typename R> typename std::enable_if<AreCompatibleExpressions<L, R, typename L::expr_type>::value, Add<typename L::expr_type, L, R>>::type operator+(L l, R r) { return Add<typename L::expr_type, L, R>(std::move(l), std::move(r)); } 

For even more brevity, you can write your own EnableIfCompatibleExpressions , but that seems a bit redundant imo.

As a note: you have a constructor error for Add . It should be

 Add(L left, R right) : l(std::move(left)), r(std::move(right)) 
+2
source

All Articles