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