Search code examples
c++lambda

creating a dispatcher of lambda functions


I'm thinking of creating a dispatcher() function for a server by specifying pairs of {ID, LAMBDA} for server to choose from. All IDs are unique. Assuming I have send() and receive() functions supporting all allowed types, how would you write a dispatcher() function that receives ID, selects corresponding lambda function, receives its parameters, invokes it and sends the result? I will have multiple instances of dispatcher() for different instances of a server each with different sets of functions and I don't want switch statements in implementation. Here is a simplified skeleton of such application (compiler explorer demo):

#include <utility>
#include <vector>

template<class T>
auto receive() { return -1; } // for exposition only

void send(auto&& ... values);

template<std::pair...functions>
void dispatcher()
{
    for(auto function_id = 0; function_id != -1;)
    {
        function_id = receive<int>();
        // select function corresponding to function_id
        // receive function arguments
        // invoke function and send return value
    }
}

int main()
{
    dispatcher<{100, []{ return 100;}},
    {1000, [](const std::vector<int>& v){ return v; }}>();
}

Solution

  • This solution is based on the following two steps:

    • First, we create a fold-expression (suggested by @Ted Lyngmo) that results in calling executor() function corresponding to the lambda function ID.

    • Second, we use lambda function traits to determine parameter types necessary to receive those parameters, passing them to the corresponding lambda function and sending the result of execution.

    The following code demonstrates this approach (Compiler Explorer Demo):

    template<class Fn> struct lambda_traits;
    
    template< class R, class G, class ... A >
    struct lambda_traits<R(G::*)(A...) const>
    {
        using Args = std::tuple<std::remove_cvref_t<A>...>;
    };
    
    template<class Fn>
    struct lambda_traits : lambda_traits<decltype(&Fn::operator())>{};
    
    template<class Fn>
    using lambda_args = typename lambda_traits<std::remove_cvref_t<Fn>>::Args;
    
    template <std::pair function>
    auto executor() {
        // receive function arguments for function.first here
        std::cout << "Executing function ID " << function.first << '\n';
        
        std::apply([&](auto&&...args)
        {
            (receive(args), ...);
            send(std::invoke(function.second, std::forward<decltype(args)>(args)...));        
        }, lambda_args<decltype(function.second)>{});
    }
    
    template<std::pair...functions>
    void dispatcher()
    {
        for(auto function_id : {10, 100})
        {
            //function_id = receive<int>();
            (void)((functions.first == function_id
                &&
            (executor<functions>(), true)) || ...);
        }
    }
    
    int main()
    {
        dispatcher<{100, []{ return 123;}},
        {10, [](const std::vector<int>& v){ return v; }}>();
    }