Создание будущего из промежуточных фьючерсов?

В следующем примере кода я хочу создать объект Item из 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();
};

Как перейти от std::future<Component> в std::future<Item>?


Обновление: удалила мою первую идею (которая была основана на потоках) из вопроса и отправила ответ.

Ответ 1

Мне пришло в голову, что я могу использовать std::async с отложенной политикой запуска для создания конечного объекта:

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());
    });
}

Ответ 2

Требуется 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;
};

В общем, я рекомендую забыть о promises и сначала рассмотреть packaged_tasks. A packaged_task заботится о том, чтобы поддерживать (функцию, обещание, будущее) тройку для вас. Это позволяет вам писать функцию естественным образом (т.е. С помощью возвратов и бросков и т.д.) И правильно распространять исключения в будущем, которые ваш пример игнорировал (необработанные исключения в любом потоке std::terminate вашей программы!).

Ответ 3

Вы также можете использовать функцию then, предложенную Herb Sutter. Вот немного измененная версия функции. Более подробную информацию о том, как он был изменен, и ссылку на исходный разговор, можно найти в этом вопросе SO. Ваш код будет сжиматься до:

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

Первоначальная идея состоит в том, чтобы функция then была как функция-член от std::future<T>, и есть некоторая работа по ее включению в стандарт. Вторая версия функции предназначена для фьючерсов void (по сути, просто асинхронно цепочки). Как отметил Херб, вы можете заплатить за использование этого подхода, потенциально нуждаясь в дополнительном потоке.

Ваш код будет выглядеть так:

#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;
}

Вышеупомянутый код отлично компилируется с помощью clang и libС++ (протестирован в Mac OS X 10.8).