Рекурсивная оптимизация, частичная специализация на функциях с неявным выводом типов

#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 слишком много копипаста, спасибо за предупреждение. Сейчас должно быть лучше.