HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Type erasure and deferred function calls for any function

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
callsanyfunctiontypeerasureforanddeferred

Problem


  1. 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.

  1. 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 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.