Построение матричных выражений времени компиляции

Я только начал использовать Eigen и прочитал в их документации, что наилучшая производительность достигается за счет ленивой оценки матричных выражений. Следовательно, такие выражения очень эффективны после вычисления:

Eigen::Matrix<float, 3, 1> a;
a << 0, 1, 2;

Eigen::Matrix<float, 3, 1> b;
b << 3, 4, 5;

Eigen::Matrix<float, 3, 1> c;
c << (a + b).sum(),
     (a - b).sum(),
     a.sum();

std::cout << c << std::endl;

Я столкнулся с проблемой при построении матриц, количество столбцов которых зависит от параметра шаблона. Например:

template <std::size_t w>
auto buildGradient() {
  Eigen::Matrix<float, 3, w> matrix;
  matrix << /* ? */;
  return matrix;
}

Моим первым желанием было использовать рекурсивный шаблон С++ для этого.

template <std::size_t w, typename Functor>
auto buildGradientExpr(Functor functor) {
  if constexpr (w == 0) {
    return;
  } else if constexpr (w == 1) {
    return functor();
  } else {
    return functor(), buildGradientExpr<w - 1, Functor>(functor);
  }
}

Но использование этого приводит к ошибкам времени выполнения, выдаваемым Eigen, поскольку выражение имеет только один инициализатор.

template <std::size_t w>
auto buildGradient() {
  Eigen::Matrix<float, 3, w> gradient;
  /* Emits an error about too few coefficients being passed to the initializer. */
  gradient << buildGradientExpr<w>([]() { /* Return 3x1 matrix */ });
  return gradient;
}

Вот полный работающий пример.

#include <Eigen/Dense>

#include <iostream>

#include <cstddef>

namespace {

template <std::size_t w, typename Functor>
auto buildGradientExpr(Functor functor) {
  if constexpr (w == 0) {
    return;
  } else if constexpr (w == 1) {
    return functor(w);
  } else {
    return functor(w), buildGradientExpr<w - 1, Functor>(functor);
  }
}

template <std::size_t w, typename Functor>
auto buildGradient(Functor functor) {
  Eigen::Matrix<float, 3, w> gradient;
  gradient << buildGradientExpr<w>(functor);
  return gradient;
}

} // namespace

int main() {

  constexpr std::size_t gradient_width = 10;

  auto gradient_functor = [](std::size_t w) {
    return Eigen::Matrix<float, 3, 1>::Constant(float(w) / gradient_width);
  };

  auto gradient = buildGradient<gradient_width>(gradient_functor);

  std::cout << gradient << std::endl;

  return 0;
}

Есть ли способ построить матрицы с размерами, которые зависят от параметров шаблона, не прибегая к циклу for? Ничего против циклов for, это то, что я использую в настоящее время. Я просто хотел бы знать, есть ли способ инициализировать матричное выражение с помощью циклов шаблонов.

Изменить:* я обновил пример, потому что функтор градиента должен был возвращать вектор, а не скаляр. Однако возникает проблема с образцом.


person tay10r    schedule 15.03.2020    source источник
comment
Когда-то я компилировал построение временного массива, возможно, это можно сделать матрицами.   -  person miszcz2137    schedule 15.03.2020
comment
@ miszcz2137 Вы говорите о собственных массивах или массивах С++?   -  person tay10r    schedule 15.03.2020
comment
Массивы С++, сильно ли отличается массив Eigen?   -  person miszcz2137    schedule 15.03.2020
comment
@miszcz2137 Да, к сожалению. Массивы шаблонов C++ довольно тривиальны в создании, но массивы Eigen несколько запутаны, поскольку они инициализируются с помощью оператора запятой.   -  person tay10r    schedule 15.03.2020
comment
Вы можете инициализировать с помощью std::initializer_list<std::initializer_list<float> > (требуется основная ветка Eigen). Хотя не уверен, что это действительно что-то упрощает.   -  person chtz    schedule 18.03.2020
comment
@chtz Я вижу, что std::initializer_list равно constexpr, начиная с С++ 14. Это кажется хорошим выбором. На самом деле это не было целью упростить что-либо, это означало улучшить оптимизацию времени компиляции для инициализации матрицы. Честно говоря, это, вероятно, преждевременная оптимизация. В любом случае, если вы хотите опубликовать это как ответ, я приму это.   -  person tay10r    schedule 18.03.2020
comment
@chtz Возможно, тоже приведи пример. Думая об этом немного больше, я не совсем уверен, что это действительно возможно.   -  person tay10r    schedule 18.03.2020


Ответы (1)


Если я правильно понимаю вашу проблему здесь, и вы можете использовать С++ 11, возможным решением может быть следующее:

(a) расширить класс Eigen::Matrix, как описано здесь, чтобы включить конструктор из std::initializer_list<T>:

// File "Eigen_plugin.h"
template<class T>
Matrix(std::initializer_list<T> elems) : Base()
{
    Base::_check_template_params();

    int idx = 0;
    for(auto el : elems)
        coeffRef(idx++) = el;
}

(b) определите свою функцию buildGradient, используя вариативные шаблоны:

// File main.cpp
#include <iostream>
#include <eigen3/Eigen/Dense>

template<class... Args>
Eigen::Matrix<double, 3, sizeof...(Args)/3> buildGradient(Args&&... args)
{
    static_assert(sizeof...(Args) % 3 == 0, "Wrong number of elements");
    return Eigen::Matrix<double, 3, sizeof...(Args)/3>{ args... };
}

int main()
{
    std::cout << buildGradient(1.0,2.0,3.0) << std::endl;
    std::cout << buildGradient(1.0,2.0,3.0,4.0,5.0,6.0) << std::endl;
}

Примечание

Как объяснено в первой ссылке, чтобы выполнить эту компиляцию, вам нужно определить переменную препроцессора EIGEN_MATRIX_PLUGIN, чтобы указать абсолютный путь к вашему файлу плагина:

g++ -std=c++11 main.cpp -D EIGEN_MATRIX_PLUGIN=\"path-to-Eigen_plugin.h\"
person Davide    schedule 25.03.2020
comment
Спасибо, что нашли время ответить на этот вопрос. В итоге я просто использовал С++ 14, который позволяет использовать constexpr for-loops. - person tay10r; 30.07.2020