Or you can use std::function. It's fractionally less efficient but much easier to understand and doesn't all have to go in the header (and only needs C++11):
std::function can potentially allocate memory and hides stuff behind a vtable, e.g. two indirections. That's not what I would call "frationally less efficient".
Perhaps "fractional" is the wrong word because has a literal meaning which probably isn't true if you compare calling a std::function vs calling a function directly within a template (although you still need to call that template from somewhere so you'll probably end up with at least one indirection, but I digress).
But as a fraction of your application's overall runtime, in almost cases it's going to be an absolutely insignificant fraction, like less than a millionth of the runtime. This is certainly true if you're writing a GUI or a network-based server process. For it to be noticeable you'd have to be calling the callback in an extraordinarily tight loop, where the actual function does very little (less than a few pointer indirections!).
Put another way, avoiding std::function is a classic case of premature optimisation.
> This is certainly true if you're writing a GUI or a network-based server process.
But are you writing those in C++ ? C++ is used exactly where you have those tight loops. Execution engines, VMs and interpreters... Besides, as soon as you want to have some real-time behaviour you can't have any call to malloc since it may make performance much less deterministic due to locks in malloc implementations. And there is a lot of real-time code being written in C++.
Personally I doubted about the overhead on nowadays machines. I guess a Electron app with a button definitely goes over more abstraction than several indirection when it is clicked. Should we really care about these overhead, at least on desktop platform?
template<typename CBT> void addListener(CBT cb) { static_assert(is_invocable_r_v<int, CBT, Button*>, "type mismatch"); }