Использование std::bind для захвата пакета параметров "по ходу"
Я пытаюсь реализовать std::async
с нуля, и наткнулся на сбой с аргументами типа только для перемещения. Суть этого в том, что C++14 init-captures позволяет нам захватывать отдельные переменные "по ходу" или "при совершенной пересылке", но они не позволяют нам захватывать пакеты параметров "по ходу" или "при совершенной пересылке" msgstr ", потому что вы не можете захватить пакет параметров с помощью init-capture - только с помощью именованного захвата.
Я нашел то, что кажется обходным путем, используя std::bind
захватить пакет параметров "по ходу", а затем с помощью обертки переместить параметры из хранилища объекта связывания в слоты параметров функции, которую я действительно хочу вызвать. Это даже выглядит элегантно, если вы не слишком много думаете об этом. Но я не могу не думать, что должен быть лучший путь - в идеале тот, который не зависит от std::bind
совсем.
(В худшем случае, я хотел бы знать, сколько std::bind
Я должен был бы переопределить сам, чтобы уйти от этого. Часть этого упражнения состоит в том, чтобы показать, как все реализовано, вплоть до самого дна, таким образом, имея такую сложную зависимость, как std::bind
действительно отстой.)
Мои вопросы:
Как заставить мой код работать, не используя
std::bind
? (То есть, используя только основные языковые функции. Общие лямбды - честная игра.)Мой
std::bind
обходной путь пуленепробиваемый? То есть кто-нибудь может показать пример, где STLstd::async
работает и мойAsync
потерпит неудачу?С благодарностью будут приняты указатели на обсуждение и / или предложения по поддержке захвата пакета параметров в C++1z.
template<typename UniqueFunctionVoidVoid>
auto FireAndForget(UniqueFunctionVoidVoid&& uf)
{
std::thread(std::forward<UniqueFunctionVoidVoid>(uf)).detach();
}
template<typename Func, typename... Args>
auto Async(Func func, Args... args)
-> std::future<decltype(func(std::move(args)...))>
{
using R = decltype(func(std::move(args)...));
std::packaged_task<R(Args...)> task(std::move(func));
std::future<R> result = task.get_future();
#ifdef FAIL
// sadly this syntax is not supported
auto bound = [task = std::move(task), args = std::move(args)...]() { task(std::move(args)...) };
#else
// this appears to work
auto wrapper = [](std::packaged_task<R(Args...)>& task, Args&... args) { task(std::move(args)...); };
auto bound = std::bind(wrapper, std::move(task), std::move(args)...);
#endif
FireAndForget(std::move(bound));
return result;
}
int main()
{
auto f3 = [x = std::unique_ptr<int>{}](std::unique_ptr<int> y) -> bool { sleep(2); return x == y; };
std::future<bool> r3 = Async(std::move(f3), std::unique_ptr<int>{});
std::future<bool> r4 = Async(std::move(f3), std::unique_ptr<int>(new int));
assert(r3.get() == true);
assert(r4.get() == false);
}
1 ответ
Мне предложили в автономном режиме, что другой подход будет заключаться в том, чтобы захватить args
упаковать в std::tuple
, а затем повторно разверните этот кортеж в список аргументов task
используя что-то вроде std::experimental::apply
(скоро к стандартной библиотеке C++17 рядом с вами!).
auto bound = [task = std::move(task), args = std::make_tuple(std::move(args)...)]() {
std::experimental::apply(task, args);
};
Это намного чище. Мы сократили количество библиотечного кода, по сравнению с bind
"просто" tuple
, Но это все еще большая зависимость, от которой я бы хотел избавиться!