考虑到某些表达式模板机制,标准使用 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编写表达式模板的其他建议。只需考虑最小化复制的目标,因为存储在终端节点中的值可能是巨大的数字或矩阵(在我的情况下,终端节点可能包含数百兆字节的插值数据,并禁用此类对象的复制 - 对于其他对象,复制是可能的)。
+
(并将它们移动到构造函数调用中)。 - GrizzlyExpr<T, U>
的实际类型是U
(U
总是继承Expr<T, U>
):这就是 CRTP 的全部意义。@Grizzly:对象移动廉价,复制昂贵。 - Alexandre C.template<typename Lhs, typename Rhs> expression<operators::plus, Lhs, Rhs> operator+(Lhs&&, Rhs&&);
的东西,这样做的好处是如果其中一个操作数不是表达式,则不会构造表达式包装器(在这里是expression
类型)。operators::plus
是一个常见的多态函数对象。 (operator+
像往常一样通过ADL找到,但仍然受到SFINAE的约束,即其操作数中至少有一个是表达式包装器。) - Luc DantonT
继承expr<T>
的问题,非常优雅地解决方法是反过来:expr<T>
继承T
,一切都像魔术般顺利运作)。 - Alexandre C.