【发布时间】:2019-06-16 14:15:56
【问题描述】:
我们正在编写一个表达式模板库来处理具有稀疏梯度向量(一阶自动微分)的值的操作。我试图弄清楚如何根据表达式是否是临时的,通过引用或值来嵌套子表达式。
我们有一个 Scalar 类,它包含一个值和一个稀疏梯度向量。我们使用表达式模板(如 Eigen)来防止构造和分配过多的临时 Scalar 对象。因此我们有类Scalar 继承自ScalarBase<Scalar> (CRTP)。
ScalarBase< Left > 和 ScalarBase< Right > 类型的对象之间的二元运算(例如 +、*)返回一个继承自 ScalarBase< ScalarBinaryOp<Left, Right,BinaryOp> > 的 ScalarBinaryOp<Left, Right,BinaryOp> 对象:
template< typename Left, typename Right >
ScalarBinaryOp< Left, Right, BinaryAdditionOp > operator+(
const ScalarBase< Left >& left, const ScalarBase< Right >& right )
{
return ScalarBinaryOp< Left, Right, BinaryAdditionOp >( static_cast< const Left& >( left ),
static_cast< const Right& >( right ), BinaryAdditionOp{} );
}
ScalarBinaryOp 必须持有一个值或对Left 和Right 类型的操作数对象的引用。持有者的类型由RefTypeSelector< Expression >::Type的模板特化定义。
目前这始终是一个 const 引用。它目前适用于我们的测试用例,但持有对临时子表达式的引用似乎不正确或不安全。
显然我们也不希望复制包含稀疏梯度向量的Scalar 对象。如果x 和y 是Scalar,则表达式x+y 应该保持对x 和y 的常量引用。但是,如果f 是从Scalar 到Scalar 的函数,则x+f(y) 应该持有对x 的常量引用和f(y) 的值。
因此我想传递有关子表达式是否是临时的信息。我可以将它添加到表达式类型参数中:
ScalarBinaryOp< typename Left, typename Right, typename BinaryOp , bool LeftIsTemporary, bool RightIsTemporary >
和RefTypeSelector:
RefTypeSelector< Expression, ExpressionIsTemporary >::Type
但是我需要为每个二元运算符定义 4 种方法:
ScalarBinaryOp< Left, Right, BinaryAdditionOp, false, false > operator+(
const ScalarBase< Left >& left, const ScalarBase< Right >& right );
ScalarBinaryOp< Left, Right, BinaryAdditionOp, false, true > operator+(
const ScalarBase< Left >& left, ScalarBase< Right >&& right );
ScalarBinaryOp< Left, Right, BinaryAdditionOp, true, false > operator+(
ScalarBase< Left >&& left, const ScalarBase< Right >& right );
ScalarBinaryOp< Left, Right, BinaryAdditionOp, true, true > operator+(
ScalarBase< Left >&& left, ScalarBase< Right >&& right )
我希望能够通过完美的转发来实现这一点。但是我不知道如何在这里实现这一点。首先,我不能使用简单的“通用参考”,因为它们几乎可以匹配任何东西。我想可能将通用引用和 SFINAE 结合起来只允许某些参数类型,但我不确定这是要走的路。另外我想知道我是否可以对有关 Left 和 Right 最初是左值还是右值引用的信息进行编码,这些信息在 Left 和 Right 类型中参数化 ScalarBinaryOp 而不是使用 2 个额外的布尔参数以及如何检索该信息。
我必须支持 gcc 4.8.5,它主要符合 c++11。
2019/08/15 更新:实施
template < typename Expr >
class RefTypeSelector
{
private:
using Expr1 = typename std::decay<Expr>::type;
public:
using Type = typename std::conditional<std::is_lvalue_reference<Expr>::value, const Expr1&,Expr1>::type;
};
template< typename Left, typename Right, typename Op >
class ScalarBinaryOp : public ScalarBase< ScalarBinaryOp< Left, Right, Op > >
{
public:
template <typename L, typename R>
ScalarBinaryOp( L&& left, R&& right, const Op& op )
: left_( std::forward<L>(left) )
, right_( std::forward<R>(right) ))
, ...
{
...
}
...
private:
/** LHS expression */
typename RefTypeSelector< Left >::Type left_;
/** RHS expression */
typename RefTypeSelector< Right >::Type right_;
...
}
template< typename Left, typename Right,
typename Left1 = typename std::decay<Left>::type,
typename Right1 = typename std::decay<Right>::type,
typename std::enable_if<std::is_base_of<ScalarBase<Left1>, Left1>::value,int>::type = 0,
typename std::enable_if<std::is_base_of<ScalarBase<Right1>, Right1>::value,int>::type = 0 >
ScalarBinaryOp< Left, Right, BinaryAdditionOp > operator+(
Left&& left, Right&& right )
{
return ScalarBinaryOp< Left, Right, BinaryAdditionOp >( std::forward<Left>( left ),
std::forward<Right>( right ), BinaryAdditionOp{} );
}
【问题讨论】:
标签: c++ c++11 crtp perfect-forwarding expression-templates