elechris
elechris

Reputation: 473

Generate const array of pointers to callback functions

I'd like to generate an array of N pointers to callbacks so I don't have to type them explicitly (LOC is not the issue here). I use C++17.

Here is what I have:

using Callback = void(*)();
auto constexpr N = 2;

const Callback callbacks[N] = {
 [](){ auto i = 0; std::cout<<"callback " << i << "\n";},
 [](){ auto i = 1; std::cout<<"callback " << i << "\n";}
};

callbacks[0]();
callbacks[N-1]();

Here is what I want:

const auto callbacks = generate_callbacks<N>();  // or generate_callbacks(N)
callbacks[i](); // cout<<"callback " << i << "\n";

I tried various ways, but I keep running into the problems with constant parameters even when they are from a constexpr function or variadic template.

If I try this:

Callback callbacks[N] = { };

for(int i=0;i<N;++i)
{
callbacks[i] = [i](){ std::cout<<"callback " << i << "\n";};
}

for(int i=0;i<N;++i)
{
callbacks[i]();
}

I get the following error:

main.cpp:91:66: error: cannot convert ‘main()::’ to ‘Callback {aka void (*)()}’ in assignment
        callbacks[i] = [i](){ std::cout<<"callback " << i << "\n";};

If I make i static and leave out the capture it only uses the last value of i:

callback 2
callback 2

This is odd to me as capturing should be done at construction. Are the lambdas constructed after the loop exits?

As for the purpose. I want to apply this technique to generating interrupt handlers for microcontrollers. I can put the function pointers in the interrupt vector table directly. These functions have no parameters and I don't know a clean way to detect which interrupt source called the handler. I can write a handler for each interrupt, but I don't like repeating this code 6 times:

void handler0()
{
  do_something(0);
}

Typing it as a lambda and/or using a template makes it a little cleaner, but I still have to type something N times. And if N changes I have to change multiple lines of code. This is not elegant.

Upvotes: 4

Views: 417

Answers (2)

max66
max66

Reputation: 66230

Off Topic Suggestion: don't use, when you can, C-styles arrays but C++ std::array.

For example: the following line

const auto callbacks = generate_callbacks<N>(); 

can't works if you want that callbacks is a C-style array (a function can't return that type) but works when generate_callback() return a std::array<Callback, N> instance.

End of Off Topic Suggestion.

In this particular case, given that N is a constexpr value, I propose the use of template meta-programming.

So I suggest the following generate_callbacks() function, that just create a sequence of template values from zero to N-1 and call an helper function

template <std::size_t N>
auto generate_callbacks ()
{ return gc_helper(std::make_index_sequence<N>{}); }

and a simple helper function that uses the template values and create the callbacks lambdas without capturing them (so remaining convertible to function pointers)

template <std::size_t ... Is>
std::array<Callback, sizeof...(Is)> gc_helper (std::index_sequence<Is...>)
{ return {{ []{ auto i = Is; std::cout<<"callback " << i << "\n"; }... }}; }

If you can use C++20, using template lambdas you can avoid the external gc_helper() function and make all inside generate_callbacks() as follows

template <std::size_t N>
auto generate_callbacks ()
{
  return []<std::size_t ... Is>(std::index_sequence<Is...>)
    -> std::array<Callback, N>
  { return {{ []{ std::cout<<"callback " << Is << "\n"; }... }}; }
  (std::make_index_sequence<N>{});
}

The following is a full compiling C++17 C++14 example

#include <iostream>
#include <utility>
#include <array>

using Callback = void(*)();
auto constexpr N = 2;

template <std::size_t ... Is>
std::array<Callback, sizeof...(Is)> gc_helper (std::index_sequence<Is...>)
{ return {{ []{ auto i = Is; std::cout<<"callback " << i << "\n"; }... }}; }

template <std::size_t N>
auto generate_callbacks ()
{ return gc_helper(std::make_index_sequence<N>{}); }

int main()
{
  const auto callbacks = generate_callbacks<N>();

  for ( auto ui = 0 ; ui < N ; ++ui )
    callbacks[ui]();
}

Upvotes: 1

Krzysiek Karbowiak
Krzysiek Karbowiak

Reputation: 1954

The following compiles fine in both gcc and clang in C++17 mode. It uses some simple template metaprogramming to generate the sequence of callbacks.

#include <array>
#include <iostream>

using cb = void (*)();

template<int N>
inline auto fun()
{
    std::cout << "callback: " << N << '\n';
}

template<int N>
void init(cb * arr)
{
    arr[N] = &fun<N>;
    init<N-1>(arr);
}

template<>
void init<0>(cb * arr)
{
    arr[0] = &fun<0>;
}

template<int N>
struct callbacks
{
    callbacks()
    {
        init<N>(cbs.data());
    }
    
    std::array<cb, N> cbs;
};

int main()
{
    auto foo = callbacks<4>();
    for (auto x = 0; x < 4; ++x)
    {
        foo.cbs[x]();
    }
}

Upvotes: 1

Related Questions