Search code examples
c++templatesvariadic-templatesc++17std-future

Generate a promise from a template Callable


I'm trying to create a promise from a template function which accepts a Callable type. But I'm not sure how to do it. I tried using std::invoke_result_t, but that needs the arguments to know the result type, something which I won't know when I construct the promise.

Any way in which I can deduce the return type? I guess it may be impossible in the case of a generic lambda, but does anything work on non generic lambda cases?

template <typename Callable, typename Promise>
class Job {

  public:
    Job(Callable&& c, Promise&& p) 
    : callable(std::move(c))
    , promise(std::move(p))
    {}

    auto getFuture() {
        return /* a std::shared_future */;
    }   

    template<typename... Args>
    void call(Args&&... args) {
        promise.set_value(std::invoke(callable, args...));
    }

  private:
    Callable callable;
    Promise promise;
};

template<typename Callable>
decltype(auto) makeJob(Callable&& c) {
    std::promise</* some type */> promise = /* something */;
    return Job(std::forward<Callable>(c), std::move(promise));
}

int main() {
    f1 = [](int arg){return arg;};
    auto job = makeJob(std::move(f1));
    auto future = job.getFuture();
    job.call(1);
    std::cout << future.get() << '\n';
 }

Solution

  • Any way in which I can deduce the return type? I guess it may be impossible in the case of a generic lambda, but does anything work on non generic lambda cases?

    You tagged C++17 so... std::function and deduction guides are your friends.

    You can detect the return type of the callable with something as

    typename decltype(std::function{c})::result_type;
    

    so your makeJob() function can be written

    template <typename Callable>
    auto makeJob (Callable && c)
     {
       using retT = typename decltype(std::function{c})::result_type;
    
       return Job{std::forward<Callable>(c), std::promise<retT>{}};
     }
    

    The same in the Job class.

    Obviously this works because f1

    auto f1 = [](int arg){return arg;};
    

    has a return type that doesn't depend from the arguments; with a generic lambda

    // ..........vvvv
    auto f1 = [](auto arg){return arg;};
    

    this solution doesn't works anymore and I don't think that you can write something to get the return type without knowing the argument type.

    The following is a full compiling example

    #include <future>
    #include <memory>
    #include <iostream>
    #include <functional>
    
    template <typename C, typename P>
    class Job
     {
       public:
          using retT
             = typename decltype(std::function{std::declval<C>()})::result_type;
    
          Job (C && c, P && p) : callable{std::move(c)}, promise{std::move(p)}
           { }
    
          auto getFuture ()
           { return std::shared_future<retT>{ promise.get_future() }; }   
    
          template <typename ... Args>
          void call (Args && ... args)
           { promise.set_value(std::invoke(callable, args...)); }
    
       private:
          C callable;
          P promise;
     };
    
    template <typename Callable>
    auto makeJob (Callable && c)
     {
       using retT
          = typename decltype(std::function{c})::result_type;
    
       return Job{std::forward<Callable>(c), std::promise<retT>{}};
     }
    
    int main ()
     {
       auto f1 = [](int arg){return arg;};
       auto job = makeJob(std::move(f1));
       auto future = job.getFuture();
    
       job.call(42);
    
       std::cout << future.get() << '\n';
     }