In the past, when I wanted a callback as a function parameter, I usually decided to use std::function. In rare cases where I definitely never use captures, I used a typedef for a function declaration instead.
So, usually my declaration with a callback parameter looks something like this:
struct Socket
{
void on_receive(std::function<void(uint8_t*, unsigned long)> cb);
}
However, as far as I know, std::function is actually doing a little work at runtime due to having to resolve the lambda with it's captures to the std::function template and move/copy it's captures (?).
Reading about the new C++ 20 features I figured I might be able to make use of concepts to avoid using std::function and use a constrained parameter for any viable functor.
And this is where my problem comes up: Since I want to work with the callback functor objects sometime in the future, I have to store them. Since I have no definitive type for my callback, my initial thought was to copy (eventuallty move at some point) the functor to heap and use a std::vector<void*> to note where I left them.
template<typename Functor>
concept ReceiveCallback = std::is_invocable_v<Functor, uint8_t*, unsigned long>
&& std::is_same_v<typename std::invoke_result<Functor, uint8_t*, unsigned long>::type, void>
&& std::is_copy_constructible_v<Functor>;
struct Socket
{
std::vector<void*> callbacks;
template<ReceiveCallback TCallback>
void on_receive(TCallback const& callback)
{
callbacks.push_back(new TCallback(callback));
}
}
int main(int argc, char** argv)
{
Socket* sock;
// [...] inialize socket somehow
sock->on_receive([](uint8_t* data, unsigned long length)
{
// NOP for now
});
// [...]
}
While this works well enough, when implementing the method that is supposed to call the functor, I noticed that I have just postponed the issue of the unknown/missing type. As far as my understanding goes, casting a void* to a function pointer or some similar hack should yield UB - How would the compiler know, that I am actually trying to call operator() of a class that is completely unknown?
I thought about storing the (copied) functor along with the function pointer to it's operator() definition, however I have no idea how I could inject the functor as this inside the function, and without it I doubt that captures would work.
Another approach I had was to declare a pure virtual interface that declares the required operator() function. Unfortunately my compiler forbid me to cast my functor to the interface and I don't think there is a legal way to let the lambda derive from it either.
So, is there a way to work this out or am I possibly just misusing the template requirements/concepts feature?