CRTP和完美的转发

时间:2012-07-29 18:06:05

标签: c++ c++11

考虑对CRTP的标准用法,对于某些表达式模板机制,它通过值保存其子节点:

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;
};

现在,在实现运算符时,我们必须通过引用传递,因为该参数将被向下转换为正确的类型。问题是我发现自己实现了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));
}

实际上,如果目标是最小化不必要的复制,则必须区分临时(可以移动)和左值(必须复制),因此四个重载。

在C ++ 03中,“没有问题”:我们使用const引用并复制所有时间,句点。在C ++ 11中,我们可以做得更好,这就是目标。

是否有一些技巧可以让我编写一次加法逻辑,或者在这里写一个宏我最好的选择(因为逻辑会为其他运算符重复)?

我也对其他有关如何使用C ++ 11编写表达式模板的建议持开放态度。只考虑目标是最小化复制,因为存储在终端节点中的值可能是巨大的数字,或矩阵(在我的精确情况下,终端节点可能包含几兆字节的内插数据,并且对这些对象禁用复制 - 对于其他对象,可以复制。)

2 个答案:

答案 0 :(得分:6)

这是编写表达式模板的另一种方法,它允许通过值传递参数:

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));
}

有很多隐含的副本,但我发现编译器可以很好地删除它们。

为了方便使用常量,你可以编写额外的重载:

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));
}

其中Constant是一个类模板,例如:

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

有很多隐含的副本,但我发现编译器可以很好地删除它们。

答案 1 :(得分:2)

由于根据注释移动对象的价格便宜,我会按值operator+获取参数,并让编译器计算出它在调用点可以避免多少复制。为了避免切片,这意味着operator+需要对派生类型起作用(导致有点过于紧密的绑定operator+)。为了控制这一点,您可能需要使用std::enable_if为您提供以下内容:

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));
}

当然,为了更频繁地使用它,在特质中封装条件是一个好主意,给你这样的东西:

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));
}

为了更简洁,你可以编写自己的EnableIfCompatibleExpressions,但这似乎有点矫枉过正了。

作为旁注:您在Add的构造函数中有一个错误。它应该是

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