Deferred caller(or delegator.. whatever you name it) implementation with variadic template

Deferred caller, delegator implementation with variadic template

Recently I started playing with a small toy project – tiny web server with C++. The first requirement I wanted to implement was the ‘static type routing handler’ as shown from the crow project.

Which means, users should be able to register its routing handlers as the following manner:

In order to do that, two things come up to the list:

  1. constexpr string parser to determine the parameter type (e.g <int>, <string>…)
  2. being able to register lambda which can have variadic template arguments

Regarding #2 issue, people would agree it should have been called ‘delegator’ or ‘deferred caller’. Its concept is basically same. Accepts a functor, saves it and calls it later. Then, how can I save functor which can have variadic number and types without using variant?

I got a hint from the Codeproject article, and implemented as follows. The routing_delegator structure is a container owns a pointer to the routing_record interface pointer.


struct routing_delegator
{
routing_delegator() {} // default
routing_delegator(std::vector<std::string> r) : ruleset(r) {}
// register functor handler
template <typename F>
void to(F&& f)
{
routing_data = std::make_unique<typed_routing_recored<decltype(&F::operator())>>(f);
}
// invoke handler
template <typename… Args>
reply handle(Args&&… args)
{
auto tuple = std::make_tuple<Args…>(args…);
return routing_data->handle(&tuple);
}
std::unique_ptr<routing_record> routing_data = nullptr;
std::vector<std::string> ruleset;
};

‘to(F&& f)’ is lambda subscribing function. If user tries to pass a lambda to the function, you can figure out the lambda’s parameters by specifying operator() functor signature to a specialized template below. (refer to this SO link)

The typed_routing_recored is the template class, and lambda signature will be deduced, and the typed figured out all will be saved to the std::function.


struct routing_record
{
virtual reply handle(void*) abstract;
};
template <typename T>
struct typed_routing_recored;
template <typename RetType, typename classTy, typename… ArgType>
struct typed_routing_recored<RetType(classTy::*)(ArgType…) const> : public routing_record
{
std::function<RetType(ArgType…)> stdfn;
typed_routing_recored(RetType(*pfn)(ArgType…))
: stdfn(pfn) {}
virtual reply handle(void* args) override
{
static const std::size_t typesize = sizeof…(ArgType);
auto tuple = static_cast<std::tuple<typename std::decay<ArgType>::type…>*> (args);
return _handle(*tuple, std::index_sequence_for<ArgType…>());
}
template <std::size_t… Is>
RetType _handle(const std::tuple<ArgType…>& tuple, std::index_sequence<Is…>)
{
return (stdfn)(std::get<Is>(tuple)…);
}
};

How about calling the saved functors? The routing_record is an interface and declares one abstract function – which is ‘handle(void*)’. As you might guess from its name, void* is the key point to enable accept variadic sized, typed arguments.

If user calls ‘handle’ with parameters, routing_delegator::handle(Args&&… args) wconverterts those parameters into tuple and passes it to the routing_record::handle(void*) with its address

And finally child class’s handle(void*) implementation will casts into tuple again and delivers it to the actual std::function functor.

You can check out the all code here, https://github.com/heejune/tinyweb-cppserver/blob/develop/router.h

Please be warned, it’s still in development and all experimental code. Thanks!

Heejune.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s