Как написать общую оболочку для вызова функции Фортрана в C ++ 14 (вызов по ссылке -> вызов по значению) - PullRequest
5 голосов
/ 19 апреля 2019

Часто мне приходится вызывать некоторую подпрограмму на Фортране из моего кода C ++.В моем случае заголовок C всегда доступен и содержит такие подписи, как

double fFortran(int* a, int* b, double* someArray, int* sizeOfThatArray)

Мой вопрос: возможно ли написать обобщенную оболочку C ++ 14 fortranCall (возможно, с использованием шаблонного метапрограммирования)он берет адреса там, где это необходимо, а затем вызывает функцию fortran, такую ​​как

double someArray[2] = {1, 4};
double result = fortranCall(fFortran, 4, 5, someArray,
    sizeof(someArray) / sizeof(someArray[0]));

, которая должна быть эквивалентна

double someArray[2] = {1, 4};
int sizeOfSomeArray = sizeof(someArray) / sizeof(someArray[0]);
int a = 4;
int b = 5;
double result = fFortran(&a, &b, someArray, &sizeOfSomeArray);

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

1 Ответ

3 голосов
/ 19 апреля 2019

Для этого ответа я сделаю следующие предположения:

  • Все параметры функций FORTRAN передаются как указатели
  • адреса указателей должны быть получены из переданных параметровк функции fortranCall.
  • За параметрами указателя массива всегда будет следовать указатель на размер массива
  • Мы хотим сохранить порядок параметров.

Примеры вызовов:

// So, given function signature
double fFortran(int* a, int* b, double* someArray, int* sizeOfThatArray);
// we would like to call with:
fortranCall(fFortran, 4, 5, someArray);

// Likewise, given
fFortranTwoArrays(double* arrayA, int* size_of_A, double* arrayB, int* size_of_B);
// we would like to call with
fortranCall(fFortranTwoArrays, someArray, some_other_Array);

Следующая программа будет выполнять вызовы, как показано выше:

#include <tuple>
#include <type_traits>

// Functions to call eventually
double fFortran(int* a, int* b, double* someArray, int* sizeOfThatArray)
{ 
    return 0.0; 
}

double fFortranTwoArrays(double* arrayA, int* size_of_A, double* arrayB, int* size_of_B)
{ 
    return 0.0; 
}

// If T is an array 
// then make a std::tuple with two parameters
//   pointer to first of T and 
//   pointer to extent of T
template<
    typename T,
    typename std::enable_if <
        std::is_array<T>{},
        int
    >::type Extent = std::extent<T>::value,
    typename Ptr = typename std::decay<T>::type
>
auto make_my_tuple(T& t)
{
    static auto extent = Extent;
    Ptr ptr = &t[0];
    return std::make_tuple(ptr, &extent);
}

// If T is not an array 
// then make a std::tuple with a single parameter
//   pointer to T
template<typename T,
    typename std::enable_if <
        !std::is_array<T>{},
        int
    >::type = 0 
>
auto make_my_tuple(T& t)
{
    return std::make_tuple(&t);
}

template<typename F, typename... Targs>
auto fortranCall(F& f, Targs&& ... args)
{
    // Make a single tuple with all the parameters.
    auto parameters = std::tuple_cat(make_my_tuple(args)...);

    // Arrays were each expanded to 
    // two pointer parameters(location and size).
    // Other parameters will pass as a single pointer
    return std::apply(f,parameters);
}

int main()
{
    double someArray[2] = {1, 4};
    double result = fortranCall(fFortran, 4, 5, someArray);

    double some_other_Array[] = {6,7,8,9,10};
    auto result2 = fortranCall(fFortranTwoArrays, someArray, some_other_Array);
}

std :: apply isC ++ 17.Если вы хотите, чтобы это работало в C ++ 14, используйте пример реализации из https://en.cppreference.com/w/cpp/utility/apply

namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>)
{
    return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...);
}
}  // namespace detail

template <class F, class Tuple>
constexpr decltype(auto) apply(F&& f, Tuple&& t)
{
    return detail::apply_impl(
        std::forward<F>(f), std::forward<Tuple>(t),
        std::make_index_sequence<std::tuple_size<std::remove_reference_t<Tuple>>::value>{});
}

и используйте invoke из бэкпорта от Martin Moene (https://github.com/martinmoene/invoke-lite)

...