Не совсем уверен, что он подходит для вашего варианта использования, но я столкнулся с довольно похожей проблемой при создании шаблонной оболочки C для функций C ++.Демонстрация показывает, как создавать (во время компиляции) и повторно использовать std::array
указателей на функции.
Давайте представим, что у вас есть базовая функция C ++, которая вычисляет сумму квадратов
template <std::size_t N>
double sum2_stat(const double* p)
{
double s = 0;
for (size_t i = 0; i < N; i++)
{
s += p[i] * p[i];
}
return s;
}
по соображениям эффективности N - это размер stat ic (известный во время компиляции), который должен позволить компилятору выполнять сложные оптимизации (векторизация цикла ...).
Теперь у нас также есть dyn amic fallback, когда N слишком велико или неизвестно во время компиляции
double sum2_dyn(const double* p, const std::size_t n)
{
double s = 0;
for (size_t i = 0; i < n; i++)
{
s += p[i] * p[i];
}
return s;
}
Теперь вы хотите создать C API.Наивным подходом было бы определить что-то вроде:
extern "C" {
double sum2_naive(const double* p, const std::size_t n)
{
assert(n >= 0);
switch (n)
{
case 0:
return sum2_stat<0>(p);
case 1:
return sum2_stat<1>(p);
case 2:
return sum2_stat<2>(p);
case 3:
return sum2_stat<3>(p);
case 4:
return sum2_stat<4>(p);
case 5:
return sum2_stat<5>(p);
case 6:
return sum2_stat<6>(p);
case 7:
return sum2_stat<7>(p);
case 8:
return sum2_stat<8>(p);
default:
return sum2_dyn(p, n);
}
}
}
Однако этот подход утомителен, потому что вам приходится многократно повторяться, и вы не можете автоматически изменить значение Nmax=8
.
Сейчасболее элегантное решение, которое я предлагаю.Сначала определите несколько помощников для автоматического создания во время компиляции статического массива указателей функций:
template <std::size_t... I>
constexpr auto sum2_call_helper(std::index_sequence<I...>)
{
return std::array<double (*)(const double* p), sizeof...(I)>({&sum2_stat<I>...});
}
template <std::size_t N, typename Indices = std::make_index_sequence<N>>
constexpr auto sum2_call_helper()
{
return sum2_call_helper(Indices());
}
Затем определите ваш API C:
extern "C" {
double sum2(const double* p, const std::size_t n)
{
constexpr auto N_Max = 8;
constexpr auto indirections = sum2_call_helper<N_Max + 1>();
assert(N_Max >= 0);
if (n <= N_Max)
{
return indirections[n](p);
}
return sum2_dyn(p, n);
}
}
Есть очевидные преимущества, у вас естьчистый код, и вы можете легко изменить значение Nmax
без дальнейших изменений кода.Также обратите внимание, что вы используете std::array
и не используете std::function
, которые сводят к минимуму риск снижения производительности.
Я надеюсь, что это частично ответит на ваш вопрос.Чтобы приспособить его к вашей проблеме, вы должны индексировать a()
функции (a<0>(), a<1>(), ...)
следующим образом:
template <std::size_t INDEX>
... a(...)
, а не (ваш пример)
... a0(...)
... a1(...)
... a2(...)
Если это не такБоюсь, что вам придется написать код для клея, как вы упомянули в своем вопросе:
a[0] = a0; a[1] = a1;
Полный рабочий пример:
#include <array>
#include <cassert>
#include <iostream>
#include <utility>
#include <vector>
template <std::size_t N>
double sum2_stat(const double* p)
{
double s = 0;
for (size_t i = 0; i < N; i++)
{
s += p[i] * p[i];
}
return s;
}
template double sum2_stat<10>(const double*);
double sum2_dyn(const double* p, const std::size_t n)
{
double s = 0;
for (size_t i = 0; i < n; i++)
{
s += p[i] * p[i];
}
return s;
}
template <std::size_t... I>
constexpr auto sum2_call_helper(std::index_sequence<I...>)
{
return std::array<double (*)(const double* p), sizeof...(I)>({&sum2_stat<I>...});
}
template <std::size_t N, typename Indices = std::make_index_sequence<N>>
constexpr auto sum2_call_helper()
{
return sum2_call_helper(Indices());
}
extern "C" {
double sum2(const double* p, const std::size_t n)
{
constexpr auto N_Max = 8;
constexpr auto indirections = sum2_call_helper<N_Max + 1>();
assert(N_Max >= 0);
if (n <= N_Max)
{
return indirections[n](p);
}
return sum2_dyn(p, n);
}
double sum2_naive(const double* p, const std::size_t n)
{
assert(n >= 0);
switch (n)
{
case 0:
return sum2_stat<0>(p);
case 1:
return sum2_stat<1>(p);
case 2:
return sum2_stat<2>(p);
case 3:
return sum2_stat<3>(p);
case 4:
return sum2_stat<4>(p);
case 5:
return sum2_stat<5>(p);
case 6:
return sum2_stat<6>(p);
case 7:
return sum2_stat<7>(p);
case 8:
return sum2_stat<8>(p);
default:
return sum2_dyn(p, n);
}
}
}
int main()
{
std::vector<double> buffer(100, 2);
std::cout << "\n" << sum2(buffer.data(), 5);
std::cout << "\n" << sum2(buffer.data(), 10);
std::cout << "\n" << sum2_naive(buffer.data(), 5);
std::cout << "\n" << sum2_naive(buffer.data(), 10);
}