5

I need to pass order of a hundred callback functions to a C library. All my callbacks will be very similar, except that they need to know which "key" it was registered for. The C API doesn't allow an context to be passed back.

In other words this is the API:

void register_for_event(int event_type, void(*callback)());

I came up with this template metaprogramming:

template<int event_type>
void event_handler() {
  std::cout << "Event: " << event_type << std::endl;
}

template <int n = 100>
void register_event_handlers() {
  register_for_event(n, event_handler<n>);
  register_event_handlers<n-1>();
}
template<> void register_event_handlers<0>(){}

But is there a way to write this:

  • easier to read
  • faster to compile (the above adds several seconds to building the file, with gcc)

Any version of C++, up to C++20, would work for me.

I'm trying to avoid macros, though it's not obvious to me how macros would even help here.

I could use code generation from shell script. I may end up doing this if I can't find a better C++-native way. But I'm also interested in what other native C++ ways are possible.

Thomas
  • 4,208
  • 2
  • 29
  • 31
  • I don't see anything in your example code that needed to be discerned at compile time. So a loop and a function that accepted an integer parameter would have worked here. Maybe your example is too simple? – Wyck Nov 12 '20 at 16:51
  • 1
    @Wyck the integer parameter is also a template argument for `event_handler`. – Botje Nov 12 '20 at 16:52
  • Yup. But the event handler is under my control, so if there's another solution that for example could call a common function `event_handler_for_all_events(int event_type)` that would be great. – Thomas Nov 12 '20 at 16:57
  • 2
    If you are willing to accept a dependency on libffi you can use its trampolining functionality to generate those 100 callbacks. I wrote an answer for that some time ago. – Botje Nov 12 '20 at 16:59
  • 1
    Dug it up for you: [C++ “Dynamic” function pointers for C callback functions](https://stackoverflow.com/a/60722596/1548468) – Botje Nov 12 '20 at 17:08
  • @Botje Awesome, Thanks. – Thomas Nov 12 '20 at 17:19
  • @Botje, yes, of course, I see that. But the implementation does not do anything special with it. I realize it's a placeholder implementation. I was just worried that it was a little too simplistic of an example. If it just had `Foo` as the type instead of just `n` and the implementation didn't just pass n to a function like cout. Anyway, like I said, I realize it's just a placeholder. cout << n is exceedingly simple to warrant metaprogramming and wanted to make sure it's not just `std::bind` that is needed here. – Wyck Nov 12 '20 at 17:19
  • @Wyck do you have a solution in mind that would work if it's not `cout << n` but one C++ function (under your control)? I'm not quite following what you're saying. – Thomas Nov 12 '20 at 18:54
  • Before I answer that, are you really using a lame API that asks you for a C-style function pointer but doesn't also take a `void*` argument for the context of that callback? If so then you are stuck with generating all the code as you have done. But if it takes a context argument then just provide the bound arguments via the context. This is why any modern C api uses `void register_for_event(int event_type, void(*callback)(void* context), void* context)` or in C++, takes a `std::function` as the callback type. Your `register_for_event` API suffers from that shortcoming. :( – Wyck Nov 12 '20 at 20:30
  • @Wyck yes I am stuck with this API. If a context is available this would be much easier. – Thomas Nov 12 '20 at 21:08

1 Answers1

5

Here is a C++17 answer:

template <std::size_t... I>
void register_via_pack(std::index_sequence<I...>) {
    ( (register_for_event(I, event_handler<I>)),... );
}

register_via_pack(std::make_index_sequence<100>{});

It expands a template parameter pack using a C++17 fold expression. Not sure if it compiles faster in your scenario, but it is a good 4 times faster here under g++ -O3. Even under -Og it produces unrolled code instead of a 100-deep recursion.

You can use the trick here to do the same under C++11/C++14.

You can get rid of the extra function by using an immediately-invoked variadic templated lambda function, but it will not be more readable ;)

Botje
  • 26,269
  • 3
  • 31
  • 41
  • I suspect this is as good as it gets. Clang (and I think GCC?) has special compiler support for make_index_sequence, which hugely cuts down compile time versus manual recursive instantiation – Sneftel Nov 12 '20 at 17:00