Использование boost::future с продолжениями и boost::when_all

Я хотел бы использовать boost::future с продолжениями и boost::when_all/boost::when_any.

Boost trunk — не 1.55 — включает реализации для последнего (по образцу предложения здесь, ожидается для C++ 14/17 и Boost 1.56).

Это то, что у меня есть (и не компилируется):

#include <iostream>

#define BOOST_THREAD_PROVIDES_FUTURE
#define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION
#define BOOST_THREAD_PROVIDES_FUTURE_WHEN_ALL_WHEN_ANY
#include <boost/thread/future.hpp>

using namespace boost;

int main() {
   future<int> f1 = async([]() { return 1; });
   future<int> f2 = async([]() { return 2; });

   auto f3 = when_all(f1, f2);

   f3.then([](decltype(f3)) {
      std::cout << "done" << std::endl;
   });

   f3.get();
}

Clang 3.4 выручает это — вот выдержка:

/usr/include/c++/v1/memory:1685:31: error: call to deleted constructor of 'boost::future<int>'
::new((void*)__p) _Up(_VSTD::forward<_Args>(__args)...);

Я делаю это неправильно или это ошибка?


person oberstet    schedule 26.03.2014    source источник
comment
Я думаю, вам нужно get вернуть future из then вместо f3, т. е. auto f4 = f3.then(...); f4.get();   -  person Casey    schedule 26.03.2014
comment
Не помогает :( gist.github.com/oberstet/9785088   -  person oberstet    schedule 26.03.2014
comment
when_all принимает итераторы, если я правильно прочитал исходный код. Вы не используете итераторы. Вы пытались поместить фьючерсы в вектор и вместо этого вызвать when_all(vec.begin(), vec.end())?   -  person stefan    schedule 26.03.2014
comment
Точно такая же проблема (вызов удаленного конструктора) с gist.github.com/oberstet/9785331 Кроме того, по крайней мере, в связанном предложении (бумаге) упоминаются 2 перегруженные версии when_all .. одна с позиционными аргументами, другие итераторы.   -  person oberstet    schedule 26.03.2014
comment
Вы передаете фьючерсы в when_all по значению, которое хочет их скопировать. Поскольку фьючерсы доступны только для движения, вселенная взрывается. Переместите их: auto f3 = when_all(std::move(f1), std::move(f2)); или опустите названные временные файлы: auto f = when_all(async([]{return 1;}), async([]{return 2;})); Кроме того, я считаю N3857 — самая последняя версия этого предложения.   -  person Casey    schedule 26.03.2014
comment
Хе-хе. Ты заставляешь меня читать. Спасибо за это. Но сейчас нет времени этим заниматься :(   -  person sehe    schedule 26.03.2014
comment
gist.github.com/oberstet/9785331 имеет ту же проблему, потому что конструктор std::vector из std::intializer_list пытаясь скопировать фьючерсы.   -  person Casey    schedule 26.03.2014
comment
@Casey Это помогает! Это работает: gist.github.com/oberstet/9785686. Если вы опубликуете ответ, я принимать! Я читал документ с предложением, и в образце кода есть ошибки по этому поводу (насколько я понимаю).   -  person oberstet    schedule 26.03.2014
comment
@oberstet Да, я заметил ошибочный пример и сообщил об этом в (надеюсь) подходящем месте.   -  person Casey    schedule 26.03.2014


Ответы (1)


Проблема в том, что when_all можно вызывать только с rvalue future или shared_future. Из N3857:

template <typename... T> 
see below when_all(T&&... futures); 

Требуется: T имеет тип future<R> или shared_future<R>.

Благодаря правилам свертывания ссылок передача lvalue приводит к тому, что T преобразуется в future<T>& в нарушение заявленного требования. Реализация boost не проверяет это предварительное условие, поэтому вы получаете ошибку глубоко в коде шаблона, где то, что должно быть перемещением будущего rvalue, превращается в попытку скопировать будущее lvalue.

Вам нужно либо переместить фьючерсы в параметры when_all:

auto f3 = when_all(std::move(f1), std::move(f2));

или не называйте их в первую очередь:

auto f = when_all(async([]{return 1;}),
                  async([]{return 2;}));

Кроме того, вы должны get будущее, возвращенное из then, вместо промежуточного будущего:

auto done = f.then([](decltype(f)) {
  std::cout << "done" << std::endl;
});

done.get();

так как будущее, которое вы называете then, перемещается в параметр продолжения. Из описания then в N3857:

Постусловие:

  • Объект future перемещен в параметр функции продолжения

  • valid() == false на исходном объекте future сразу после его возвращения

Согласно 30.6.6 [futures.unique_future]/3:

Эффект от вызова любой функции-члена, отличной от деструктора, оператора перемещения-присваивания или valid для объекта future, для которого valid() == false не определен.

Вы могли бы избежать большинства этих проблем в С++ 14, вообще избегая именования фьючерсов:

when_all(
  async([]{return 1;}),
  async([]{return 2;})
).then([](auto&) {
  std::cout << "done" << std::endl;
}).get();
person Casey    schedule 26.03.2014
comment
Единственное, что мне интересно, это то, почему мне разрешено дважды перемещать будущее ... см. код, связанный прямо выше. Он перемещает f2 в when_all, что приводит к f12, и во второй раз в when_all, что приводит к f23. Не могли бы вы объяснить, почему это разрешено/работает? Огромное спасибо! - person oberstet; 26.03.2014
comment
@oberstet Перемещенный объект не является недействительным, он находится в допустимом, но неуказанном состоянии. Вы можете многократно перемещаться от объектов, но результат этого зависит от реализации. - person Casey; 26.03.2014
comment
@oberstet Незначительное исправление к вышесказанному, которое обычно верно для объектов стандартной библиотеки, если не указано иное: из 30.6.6 [futures.unique_future]/3 Эффект вызова любой функции-члена, отличной от деструктора, оператора перемещения-присваивания или valid для объекта future, для которого valid() == false не определено. - person Casey; 26.03.2014
comment
Большое спасибо! Это бесценно.. со всем этим боролся 2 часа. - person oberstet; 26.03.2014
comment
@oberstet Исправлен ответ: я был прав в отношении решения, но не совсем прав в отношении проблемы. - person Casey; 27.03.2014
comment
Использование auto& в качестве параметра для .then не работает даже с clang 3.4 и -std=c++1y (error: 'auto' not allowed in function prototype). Тогда у транка Boost есть проблемы и с c++1y (/usr/include/c++/v1/cstdio:156:9: error: no member named 'gets' in the global namespace). Какой компилятор вы используете? - person oberstet; 28.03.2014
comment
@oberstet Я не пытался скомпилировать версию C++14, так как у меня нет доступа к магистрали Clang и Boost на одной платформе. Я отмечаю, что Clang r198621 на Coliru правильно компилирует общую лямбду. - person Casey; 28.03.2014
comment
Спасибо за ответ! Я предполагаю, что это особенность clang 3.5. Хотя вроде бы удобно.. жду с нетерпением. - person oberstet; 28.03.2014
comment
@oberstet упомянул, что он не может скомпилироваться. На самом деле код неверный, должно быть then([](auto&&) вместо then([](auto&). Это связано с разрушением ссылки, о которой говорил Скотт Мейерс. - person Asier Gutierrez; 16.03.2017