Reputation: 473
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
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
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