10

In the following sample code I want to create an Item object from a Component:

struct Component { };

struct Item {
    explicit Item(Component component) : comp(component) {}    
    Component comp;
};

struct Factory {
    static std::future<Item> get_item() {
        std::future<Component> component = get_component();        
        // how to get a std::future<Item> ?
    }

    std::future<Component> get_component();
};

How do I go from std::future<Component> to std::future<Item>?


Update: removed my first idea (which was thread-based) from the question and posted an answer.

StackedCrooked
  • 34,653
  • 44
  • 154
  • 278

3 Answers3

13

Needs moar packaged_tasks!

std::future<Item> get_item() {
    std::packaged_task<Item()> task([]{
        return Item(get_component().get());
    });
    auto future = task.get_future();
    std::thread(std::move(task)).detach();
    return future;
};

In general I recommend forgetting about promises and considering packaged_tasks first. A packaged_task takes care of maintaining a (function, promise, future) triple for you. It lets you write the function in a natural way (i.e. with returns and throws and so on), and propagates the exceptions correctly into the future, which your example neglected (unhandled exceptions in any thread std::terminate your program!).

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • Isn't there a better way? I am wondering why is there another thread needed if all the user is trying is to hide some internal implementation and expose a better interface. – balki Feb 15 '13 at 12:22
  • @balki not with standard stuff. There are plans to add `future.then()`, and that would work. – R. Martinho Fernandes Feb 15 '13 at 12:34
3

It occurred to me that I can use std::async with the deferred launch policy to compose the final object:

std::future<Item> get_item()
{
    // start async creation of component
    // (using shared_future to make it copyable)
    std::shared_future<Component> component = get_component();

    // deferred launch policy can be used for construction of the final object
    return std::async(std::launch::deferred, [=]() {
        return Item(component.get());
    });
}
StackedCrooked
  • 34,653
  • 44
  • 154
  • 278
  • Thanks, this is the answer I was looking for. I needed a way to effectively construct a `std::future` object, but the use of threads would have been completely unnecessary. – void-pointer Jan 17 '14 at 19:14
  • Why is it using `shared_future`? Couldn't it use `future` and move from it instead of copying? – Xeverous Jun 10 '20 at 19:19
  • @Xeverous yeah, that would be better – StackedCrooked Jun 11 '20 at 10:48
  • I find this solution unusable - it is impossible to check whether `get_item()` is ready. Whenever you ask it to `wait_for` with zero time to check if it's ready it will return that it is deferred and never start `get_component()`. If you start it explicitly by `.get()` then you have a blocking call and there is no asynchronous code at all. – Xeverous Jun 13 '20 at 11:32
2

You can also use the then function proposed by Herb Sutter. Here is a slightly modified version of the function. More information about how it was modified and a link to the original talk can be found in this SO question. Your code would boil down to:

return then(std::move(component), [](Component c) { return Item(c); });

The original idea is to have the function then as a member function of std::future<T> and there is some ongoing work of putting it into the standard. The second version of the function is for void futures (essentially just chaining functions asynchronously). As Herb pointed out, you might pay for using this approach by potentially needing an extra thread.

Your code would look like this:

#include <future>
#include <thread>
#include <iostream>


template <typename T, typename Work>
auto then(std::future<T> f, Work w) -> std::future<decltype(w(f.get()))>
{
  return std::async([](std::future<T> f, Work w)
                    { return w(f.get()); }, std::move(f), std::move(w));
}

template <typename Work>
auto then(std::future<void> f, Work w) -> std::future<decltype(w())>
{
  return std::async([](std::future<void> f, Work w) -> decltype(w())
                    { f.wait(); return w(); }, std::move(f), std::move(w));
}

struct Component { };

struct Item {
  Item(Component component) : comp(component) {}
  Component comp;
};


struct Factory {
  static std::future<Item> get_item() {
    std::future<Component> component = get_component();
    return then(std::move(component), [](Component c) { return Item(c); });
  }

  static std::future<Component> get_component()
  {
    return std::async([](){ return Component(); });
  }

};

int main(int argc, char** argv)
{
  auto f = Factory::get_item();
  return 0;
}

The above code compiles fine with clang and libc++ (tested on Mac OS X 10.8).

Community
  • 1
  • 1
Roman Kutlak
  • 2,684
  • 1
  • 19
  • 24