4

Given

// from an external C api
void f(int i, void (*g)());

const int n = ...
void a0(); void a1(); ...
void (*a[n])();

int main()
{
  a[0] = a0; a[1] = a1; ...
  for (int i = 0; i != n; ++i)
    f(i, a[i]);
  ...
}

I don't want to generate each function a0, a1, ... and assign it to a separately. Instead I want to generate the functions and assign them to a in a loop, something like that (sorry for the hideous code, it won't compile):

for (int i = 0; i != n; ++i)
{
    void b() { cout << i; };
    a[i] = b;
}

Is this possible? How can I do it?

3
  • The most convenient thing might be to generate the functions (and the array) with an external script written in a more text-oriented language. Commented Nov 27, 2018 at 8:36
  • @molbdnilo: My usecase is: the functions have to be written in C++ and not in another language. Commented Nov 27, 2018 at 8:50
  • I mean that you generate C++ code. Does it matter whether you or your computer wrote the code? Commented Nov 27, 2018 at 9:04

2 Answers 2

3

Try like this:

#include <iostream>
#include <vector>
using namespace std;

// from an external C api
void f(int i, void(*g)())
{
    //g();
}

const int n = 100;
using fnType = void(*)();
vector<fnType> a(n);

template <int N>
void func()
{
    cout << N << endl;
}

template <int N>
void init()
{
    a[N - 1] = func<N - 1>;
    init<N - 1>();
}

template <>
void init<0>()
{
}


int main()
{
    init<n>();

    for(int i = 0; i < n; ++i)
        a[i]();
    for(int i = 0; i < n; ++i)
        f(i, a[i]);   
}

Also, you can define

vector<std::function<void()>> funcs(n);

and call it from f:

template <int N>
void func()
{
    //cout << N << endl;
    funcs[N]();
}

So, you can simply define it:

for(int k = 0;k < n;k++)
    funcs[k] = [k](){cout << k << endl;};
Sign up to request clarification or add additional context in comments.

2 Comments

Quite funny: the bigger n is, the longer the building lasts and the bigger the executable file becomes. At my computer n has to be <= 898, otherwise the recursion depth is too big.
Yes, it’s known template’s property
2

No really sure it fits your use case, but I encountered a quite similar problem to create a templated C++ functions C wrapper. The demo shows how to create (at compile time) and reuse a std::array of function pointers.

Let imagine that you have a basic C++ function that computes sum of squares

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;
}

for efficiency reason N is a static size (known at compile time) that should allow compiler to do tricky optimizations (vectorize the loop...).

Now we also have a dynamic fallback, when N is too big or unknown at compile time

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;
}

Now you want to create a C API. A naive approach would be to define something like:

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);
  }
}
}

However this approach is tedious because you have to repeat yourself a lot and you can not automatically change Nmax=8 value.

Now a more elegant solution I suggest. First define some helpers to automatically create, at compile time, a static array of function pointers:

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());
}

Then define your C API:

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);
}
}

There are clear advantages, you have a clean code and you can easily change Nmax value without further modifications of the code. Also note that you use a std::array and do not use std::function which minimize the risk of performance penalties.


I hope this partially answer your question. To adapt it to your problem you must have indexed a() functions (a<0>(), a<1>(), ...) as follows:

template <std::size_t INDEX>
... a(...)

and not (your example)

... a0(...)
... a1(...)
... a2(...)

If it is not the case I fear that you will have to write glue code as you mentioned in your question:

 a[0] = a0; a[1] = a1; 

Complete working example:

#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);
}

2 Comments

Sorry, but I don't see what this has to do with my problem.
@JohannesFlügel I have added precision at the end of my answer. Yes not sure you can adapt the solution, you must have indexed a<index>(...) functions.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.