4

考虑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 编写表达式模板的其他建议。只需考虑目标是最小化复制,因为存储在终端节点中的值可能是巨大的数字或矩阵(在我的精确情况下,终端节点可能包含几兆字节的插值数据,并且这些对象的复制被禁用 - 对于其他对象,复制是可能的)。

4

2 回答 2

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

有很多隐含的副本,但我发现编译器在删除它们方面做得很好。

于 2012-07-29T18:30:11.140 回答
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))
于 2012-07-29T18:30:39.593 回答