C ++ (функция) шаблон, который возвращает свой уникальный аргумент, не копируя его для определенных типов - PullRequest
0 голосов
/ 20 марта 2019

У меня есть значение функции (x), которое перегружено для многих типов, таких как:

double value(double x) { return x; }
double value(MyType x) { return x.value(); }
SomeContainer<double> value(SomeContainer<double> x) { return x; }
SomeContainer<double> value(SomeContainer<MyType> x) { ... }

где MyType на самом деле представляет собой число с вектором градиента относительно набора параметров.

для использования в общих (шаблонных) программах.

Я хочу определить:

Matrix<double> value(Matrix<double>)
Matrix<double> value(Matrix<MyType>)

Я использую собственные матрицы, и это моя текущая реализация первой функции:

template < typename Derived,
    typename std::enable_if< std::is_floating_point< typename Derived::Scalar >::value, int >::type = 0 >
Derived value( const Eigen::MatrixBase< Derived >& matrix )
{
    return matrix;
}

Проблема в том, что это кажется неэффективным, если только в возможных случаях, когда компилятор может выяснить, что результат / аргумент не изменяется, и ускользнуть от копии. Я также не могу вернуть ссылку на аргумент, поскольку он является локальным / временным.

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

Что можно сделать, чтобы избежать копирования?

РЕДАКТИРОВАТЬ 22 марта 2019 г .:

Если заменить тип возврата на

const Derived &

Я получаю следующее предупреждение GCC:

warning: returning reference to temporary [-Wreturn-local-addr]

в следующем коде:

Matrix33 m;
m << 1, 2, 3,
     4, 5, 6,
     7, 8, 9;
std::cout << m << std::endl;
std::cout << value(m) << std::endl;

и значение (m) распечатывается как мусор.

Также я все больше думаю о том, что было бы плохой идеей «вернуть сам объект», потому что он будет использоваться в универсальном коде:

 auto m = value(Mx)

где Mx - матрица X (параметр шаблона), а m - двойная матрица.

Наличие другой семантики (кража объекта в случае двойного X и создание отдельного объекта в других случаях) может привести ко многим ошибкам программирования.

Другая возможность - возвращать прокси-объекты.

Лучше всего, однако, чтобы компилятор видел, когда копия не нужна, потому что ничего не меняется. Однако, похоже, это не так: мой тест для сравнения

  Matrix<double,3,3> M = ...
  norm(M)

и

  Matrix<double,3,3> M = ...
  norm(value(M))

показывает, что вторая версия немного медленнее в (оптимизированной) сборке.

1 Ответ

1 голос
/ 22 марта 2019

Вам необходимо вернуть matrix.derived(), если вы хотите иметь ссылку на инкапсулированный тип:

template < typename Derived,
    typename std::enable_if< std::is_floating_point< typename Derived::Scalar >::value, int >::type = 0 >
const Derived& value( const Eigen::MatrixBase< Derived >& matrix )
{
    return matrix.derived();
}

Что касается value(Matrix<MyType> const&), вы можете реализовать это таким образом, чтобы он возвращал просто представление, по сути, как это (требуется C ++ 14 или еще немного работы по реализации):

template < typename Derived,
    typename std::enable_if< std::is_same< typename Derived::Scalar, MyType >::value, int >::type = 0 >
auto value( const Eigen::MatrixBase< Derived >& matrix )
{
    // If `x.value()` returns by value, better use `unaryExpr` instead of `unaryViewExpr`:
    return matrix.unaryViewExpr([](const MyType& x){ return x.value();} );
}

Godbolt-Link: https://godbolt.org/z/qcYKnw Предупреждения не генерируются, и foo1 и foo2 просто вызывают функцию norm. У foo3 есть небольшие накладные расходы, которые, вероятно, были бы оптимизированы, если бы norm был встроен.

...