patterncppMinor
Type erasure and deferred function calls for any function
Viewed 0 times
callsanyfunctiontypeerasureforanddeferred
Problem
- Description
1.1. Functionality
The objective of these types is to provide type erasure for any function while maintaining the ability to provide a return value through a combination of
std::promise and its associated std::future.1.2. Motivation
This is useful for any sort of dispatch manager, a thread pool that you can submit tasks to being the actual target; where a task is any function with any parameters and return type.
- Implementation
2.1. deferred_invoker.h
This is the main functionality. Type erasure is provided through the
invoker_base base class, which simply has a virtual member function that is called when the function has to be invoked.The specializations will then know how to deal with the invocation and whether they have to save a result. They also take care of ensuring the correct initialization arguments required for the call are initialized and saved in a tuple. This applies for template parameter
F which is any function.A simple tag dispatch system takes care of
void return types. I've omitted a third implementation of deferred_invoker for lambda and functor types to focus the review. It's basically the same style as the other two (possible design issue?), but it takes a copy of the lambda/functor and the arguments, instead of a pointer to the function.```
#ifndef OAG_DEFERRED_INVOKER_H
#define OAG_DEFERRED_INVOKER_H
#include
#include "function_traits.h"
namespace oag
{
template
struct bare_type_tuple;
template
struct bare_type_tuple>
{
using type = std::tuple...>;
};
template
using bare_type_tuple_t = typename bare_type_tuple::type;
}
namespace oag
{
class invoker_base
{
public:
virtual ~invoker_base() {}
virtual void invoke() = 0;
};
template
::is_member_fn
>
class deferred_invoker;
/*
FREE FUNCTION SPECIALIZATION
*/
template
class deferred_invoker : public invoker_base
{
public:
Solution
Well, the
BTW: You should take more care with the rule-of-3.
Also, the manager-class in the second example is leaking, use
A demo on coliru: http://coliru.stacked-crooked.com/a/08d85370e66675cf
deferred_invoker can be radically simplified and generified, use Lambdas, std::ref, a maker-function, and one intermediate type-erasure-step for best flexibility:#include
#include
#include
#include
struct invokable
{
virtual ~invokable() = default;
virtual void invoke() = 0;
protected:
invokable() = default;
invokable(invokable&&) = default;
invokable& operator=(invokable&&) noexcept = default;
};
template
struct deferred_invoker final : deferred_invoker
{
deferred_invoker(L&& l) : lambda(std::move(l)) {}
deferred_invoker(const L& l) : lambda(l) {}
deferred_invoker(deferred_invoker&&) = default;
deferred_invoker& operator=(deferred_invoker&&) = default;
void invoke() override {
try {
this->invoke_helper((R*)0);
} catch(...) {
this->result.set_exception(std::current_exception());
}
}
private:
L lambda;
template auto invoke_helper(ret*)
-> std::enable_if_t::value>
{ this->lambda(); this->result.set_value(); }
template auto invoke_helper(ret*)
-> std::enable_if_t::value>
{ this->result.set_value(this->lambda()); }
};
template
struct deferred_invoker : invokable
{
using f_result = R;
std::future get_future() { return this->result.get_future(); }
protected:
std::promise result;
};
template
auto make_deferred_invoker(F&& f, A1&& a1, ARGS&&... args) {
auto lambda = [=]{ return std::ref(f)(a1, args...); };
return deferred_invoker,
std::decay_t >{std::move(lambda)};
}
// Avoid re-packaging any callable
template
auto make_deferred_invoker(F&& lambda) {
return deferred_invoker,
std::decay_t>{std::move(lambda)};
}BTW: You should take more care with the rule-of-3.
Also, the manager-class in the second example is leaking, use
std::unique_ptrs or some such.A demo on coliru: http://coliru.stacked-crooked.com/a/08d85370e66675cf
Code Snippets
#include <exception>
#include <future>
#include <type_traits>
#include <utility>
struct invokable
{
virtual ~invokable() = default;
virtual void invoke() = 0;
protected:
invokable() = default;
invokable(invokable&&) = default;
invokable& operator=(invokable&&) noexcept = default;
};
template<class R, class L = void>
struct deferred_invoker final : deferred_invoker<R>
{
deferred_invoker(L&& l) : lambda(std::move(l)) {}
deferred_invoker(const L& l) : lambda(l) {}
deferred_invoker(deferred_invoker&&) = default;
deferred_invoker& operator=(deferred_invoker&&) = default;
void invoke() override {
try {
this->invoke_helper((R*)0);
} catch(...) {
this->result.set_exception(std::current_exception());
}
}
private:
L lambda;
template <class ret> auto invoke_helper(ret*)
-> std::enable_if_t<std::is_same<void, ret>::value>
{ this->lambda(); this->result.set_value(); }
template <class ret> auto invoke_helper(ret*)
-> std::enable_if_t<!std::is_same<void, ret>::value>
{ this->result.set_value(this->lambda()); }
};
template<class R>
struct deferred_invoker<R> : invokable
{
using f_result = R;
std::future<R> get_future() { return this->result.get_future(); }
protected:
std::promise<R> result;
};
template<class F, class A1, class... ARGS>
auto make_deferred_invoker(F&& f, A1&& a1, ARGS&&... args) {
auto lambda = [=]{ return std::ref(f)(a1, args...); };
return deferred_invoker< std::decay_t<decltype(lambda())>,
std::decay_t<decltype(lambda)> >{std::move(lambda)};
}
// Avoid re-packaging any callable
template<class F>
auto make_deferred_invoker(F&& lambda) {
return deferred_invoker<std::decay_t<decltype(lambda())>,
std::decay_t<decltype(lambda)>>{std::move(lambda)};
}Context
StackExchange Code Review Q#107224, answer score: 2
Revisions (0)
No revisions yet.