#c #templates #recursion #eigen #tail-recursion
Вопрос:
Вполне возможно создать рекурсивную функцию, используя ‘int N’ в качестве параметра шаблона для базового случая функции, и завершить ее, когда N = 0. Но как это сделать для функций, в которых неявно выводятся другие типы?
#include <iostream>
#include <Eigen/Dense>
using Mat = Eigen::MatrixXd;
using Vec = Eigen::VectorXd;
using Eigen::MatrixBase;
template <int N, typename D0, typename D1, class Enable = void>
inline Vec recursive(const MatrixBase<D0> amp;A,
const MatrixBase<D1> amp;b)
{
return (N % 2) ? recursive<N-1>(A, A * b) : recursive<N-1>(A, A.ldlt().solve(b));
}
template <int N, typename D0, typename D1>
inline Vec recursive <N, D0, D1, typename std::enable_if<N == 0>>(const MatrixBase<D0> amp;A,
const MatrixBase<D1> amp;b)
{
return b;
}
int main()
{
Mat A(2, 2);
A(0, 0) = 10.0;
A(1, 1) = 10.0;
Vec b(2);
b(0) = 1.0;
b(1) = 5.0;
Vec res(2);
res = recursive<10>(A, b);
std::cout << res << std::endl;
}
В следующем примере D0 и D1 являются типами, полученными на основе входных данных, приведенных в main. Функции будут сгенерированы для Vec b и Mat A, но также будут учитывать типы результирующих выражений из рекурсивной функции. (Типы выражений A*b и A. ldlt().решить(b))
Цель состоит в том, чтобы компилятор в конечном итоге преобразовал функцию в цикл, поэтому альтернативные реализации приветствуются.
Вывод ошибки компилятора (-std=c 17):
EigenRecursive.cpp:16:45: error: non-class, non-variable partial specialization recursive<N, D0, D1, std::enable_if<(N == 0), void> >’ is not allowed
16 | const MatrixBase<D1> amp;b)
Изменить: Реализация предложения yosefs решила проблему частичной специализации:
#include <iostream>
#include <Eigen/Dense>
using Mat = Eigen::MatrixXd;
using Vec = Eigen::VectorXd;
using Eigen::MatrixBase;
template <int N>
struct Recursive
{
template <typename D0, typename D1>
static inline Vec recursive(const MatrixBase<D0> amp;A,
const MatrixBase<D1> amp;b)
{
return Recursive<N-1>::recursive(A, A * b);// : Recursive<N-1>::recursive(A, A.ldlt().solve(b));
}
};
template<>
struct Recursive<0>
{
template <typename D0, typename D1>
static inline Vec recursive(const MatrixBase<D0> amp;A,
const MatrixBase<D1> amp;b)
{
return b;
}
};
int main()
{
Mat A(2, 2);
A(0, 0) = 10.0;
A(1, 1) = 10.0;
Vec b(2);
b(0) = 1.0;
b(1) = 5.0;
Vec res(2);
res = Recursive<10>::recursive(A, b);
std::cout << res << std::endl;
}
Условный оператор вводит экспоненциально увеличивающееся статическое время компиляции, что делает текущее решение невыполнимым.
Комментарии:
1. в первом
recursive
отсутствуетreturn
2. также
recursive(A, A * b, N - 1)
есть 3 параметра, но нет перегрузки с 3 аргументами. Ты это имел в видуrecursive<N-1>(A,A*b)
?3. Та же ошибка относится и к текущему исправлению
4. да, эти ошибки не были связаны с ошибкой, о которой вы спрашиваете.
5. Понятно, что первоначальный вопрос состоял в том, чтобы найти эффективную специализацию частичного типа, но я не был уверен в том, как я разделю эти две проблемы. Вы могли бы сказать, что условное ветвление операторов было избыточным для проблемы, и что проблема была решена с помощью решения Юсефа. Однако я также добавил решение для условного оператора
Ответ №1:
Вы часто можете избежать:
ошибка: неклассовая, непеременная частичная специализация
вложив любую функцию, которую вы хотите реализовать в качестве метода, в какой-нибудь фиктивный класс.
Например, скажем, я хочу реализовать foo() со специализацией:
template<int First, int Second>
void foo<First, Second>(){}
template<int First>
void foo<First, 0>(){}
int main(){
foo<4,20>();
}
Приведенный выше простой пример приведет к ошибке «неклассовая, непеременная частичная специализация».
Но наш код можно заменить эквивалентным:
template <int First, int Second>
struct Foo{
static void foo(){}
};
template <int First>
struct Foo<First, 0>{
static void foo(){}
};
int main()
{
Foo<4,20>::foo();
}
И второй пример компилируется и запускается с g 7.4.0.
Ответ №2:
Частичная специализация с помощью структур позволяет решить проблему частичной специализации, но все еще наблюдалось экспоненциально увеличивающееся время компиляции из-за условного оператора. Замена условного оператора логикой параметров шаблона с помощью std::enable_if решила эту проблему:
#include <iostream>
#include <Eigen/Dense>
using Mat = Eigen::MatrixXd;
using Vec = Eigen::VectorXd;
using Eigen::MatrixBase;
template <int N, class Enable = void>
struct Recursive
{
template <typename D0, typename D1>
static inline Vec recursive(const MatrixBase<D0> amp;A,
const MatrixBase<D1> amp;b)
{
return Recursive<N-1>::recursive(A, A * b);
}
};
template <int N>
struct Recursive<N,class std::enable_if<(N%2)>>
{
template <typename D0, typename D1>
static inline Vec recursive(const MatrixBase<D0> amp;A,
const MatrixBase<D1> amp;b)
{
return Recursive<N-1>::recursive(A, A.ldlt().solve(b));
}
};
template<class Enable>
struct Recursive<0, Enable>
{
template <typename D0, typename D1>
static inline Vec recursive(const MatrixBase<D0> amp;A,
const MatrixBase<D1> amp;b)
{
return b;
}
};
int main()
{
Mat A(2, 2);
A(0, 0) = 10.0;
A(1, 1) = 10.0;
Vec b(2);
b(0) = 1.0;
b(1) = 5.0;
Vec res(2);
res = Recursive<20>::recursive(A, b);
std::cout << res << std::endl;
}
Ответ №3:
Поскольку C 17 существует, если constexpr, который, на мой взгляд, делает рекурсию в шаблонах намного более читабельной, код шаблона будет читать что-то вроде этого (не проверено, у меня не установлен Eigen):
template<typename T>
struct MatrixBase
{
};
template<unsigned int N, typename D0, typename D1>
inline auto recursive(const MatrixBase<D0>amp; a, const MatrixBase<D1>amp; b)
{
if constexpr (N == 0)
{
return b;
}
else
{
if constexpr (N % 2 == 1)
{
return recursive<N-1>(b,a);
}
else
{
return recursive<N-1>(a, b); // modified my code for testing here!
}
}
}
int main()
{
MatrixBase<int> a;
MatrixBase<int> b;
recursive<8>(a, b);
}
Комментарии:
1. Нужно ли это завернуть в структуру? вы не пытаетесь частично специализироваться на чем-либо
2. @Caleth слишком много копипаста, спасибо за предупреждение. Сейчас должно быть лучше.