Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CRTP and perfect forwarding

Tags:

c++

c++11

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).

like image 332
Alexandre C. Avatar asked Feb 19 '23 16:02

Alexandre C.


1 Answers

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.

like image 136
Vaughn Cato Avatar answered Feb 27 '23 19:02

Vaughn Cato