Вложенность подвыражений в шаблоны выражений - PullRequest
1 голос
/ 16 июня 2019

Мы пишем библиотеку шаблонов выражений для обработки операций со значениями с вектором разреженного градиента (автоматическое дифференцирование первого порядка).Я пытаюсь выяснить, как сделать возможным вложение вложенных выражений по ссылке или по значениям в зависимости от того, являются ли выражения временными или нет.

У нас есть класс Scalar, который содержит значение и разреженный градиентвектор.Мы используем шаблоны выражений (например, Eigen), чтобы предотвратить создание и выделение слишком многих временных объектов Scalar.Таким образом, у нас есть класс Scalar, унаследованный от ScalarBase<Scalar> (CRTP).

Бинарная операция (например, +, *) между объектами типа ScalarBase< Left > и ScalarBase< Right > возвращает объект ScalarBinaryOp<Left, Right,BinaryOp>, которыйнаследуется от ScalarBase< 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.

В настоящее время это всегда постоянная ссылка.В настоящее время это работает для наших тестовых случаев, но кажется неправильным или небезопасным хранить ссылку на временные подвыражения.

Очевидно, мы также не хотим, чтобы объект 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 изначально ссылками lvalue или rvalue в типах Left и Right, которые параметризуют ScalarBinaryOp вместо использования 2 дополнительных логических параметров, и как получить эту информацию.

Я должен поддерживать gcc 4.8.5, который в основном совместим с c ++ 11.

1 Ответ

1 голос
/ 16 июня 2019

Вы можете кодировать информацию lvalue / rvalue в типы Left и Right.Например:

ScalarBinaryOp<Left&&, Right&&> operator+(
    ScalarBase<Left>&& left, ScalarBase<Right>&& right)
{
    return ...;
}

с ScalarBinaryOp примерно таким образом:

template<class L, class R>
struct ScalarBinaryOp
{
    using Left = std::remove_reference_t<L>;
    using Right = std::remove_reference_t<R>;

    using My_left = std::conditional_t<
        std::is_rvalue_reference_v<L>, Left, const Left&>;
    using My_right = std::conditional_t<
        std::is_rvalue_reference_v<R>, Left, const Right&>;

    ...

    My_left left_;
    My_right right_;
};

В качестве альтернативы, вы можете быть явным и хранить все по значению, кроме Scalars.Чтобы иметь возможность хранить Scalar по значению, вы используете класс обертки:

x + Value_wrapper(f(y))

Оболочка проста:

struct Value_wrapper : Base<Value_wrapper> {
    Value_wrapper(Scalar&& scalar) : scalar_(std::move(scalar)) {}

    operator Scalar() const {
        return std::move(scalar_);
    }

    Scalar&& scalar_;
};

RefTypeSelector имеет специализацию для Value_wrapper:

template<> struct RefTypeSelector<Value_wrapper> {
    using Type = Scalar;
};

Определение бинарного оператора остается прежним:

template<class Left, class Right>
ScalarBinaryOp<Left, Right> operator+(const Base<Left>& left, const Base<Right>& right) {
    return {static_cast<const Left&>(left), static_cast<const Right&>(right)};
}

Полный пример: https://godbolt.org/z/sJ3NfG

(я использовал некоторые функции C ++ 17 только вышедля упрощения обозначений.)

...