Идеальная пересылка в асинхронную лямбду

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

#include <thread>
#include <future>
#include <utility>
#include <iostream>
#include <vector>

/**
* Function template that does perfect forwarding to a lambda inside an
* async call (or at least tries to). I want both instantiations of the
* function to work (one for lvalue references T&, and rvalue reference T&&).
* However, I cannot get the code to compile when calling it with an lvalue.
* See main() below.
*/
template <typename T>
std::string accessValueAsync(T&& obj)
{

std::future<std::string> fut =
std::async(std::launch::async,
[](T&& vec) mutable
{
return vec[0];
},
std::forward<T>(obj));

return fut.get();
}

int main(int argc, char const *argv[])
{
std::vector<std::string> lvalue{"Testing"};

// calling with what I assume is an lvalue reference does NOT compile
std::cout << accessValueAsync(lvalue) << std::endl;

// calling with rvalue reference compiles
std::cout << accessValueAsync(std::move(lvalue)) << std::endl;

// I want both to compile.

return 0;
}

Для случая некомпиляции, вот последняя строка сообщения об ошибке, которая понятна:

main.cpp|13 col 29| note:   no known conversion for argument 1 from ‘std::vector<std::basic_string<char> >’ to ‘std::vector<std::basic_string<char> >&’

У меня есть чувство, что это может иметь какое-то отношение к тому, как T&& выводится, но я не могу точно определить точную точку отказа и исправить ее. Какие-либо предложения?

Спасибо!

РЕДАКТИРОВАТЬ: я использую gcc 4.7.0 на всякий случай, это может быть проблемой компилятора (вероятно, нет)

11

Решение

Насколько я понимаю, вы не можете использовать функцию через async который ожидает неконстантные lvalue ссылки в качестве аргументов, потому что async всегда будет делать их внутреннюю копию (или перемещать внутри), чтобы гарантировать, что они существуют и действительны в течение всего времени выполнения созданного потока.

В частности, Стандарт говорит о async(launch policy, F&& f, Args&&... args):

(§30.6.8)

(2) Требуется: F и каждый Ti в Args должен удовлетворить требования MoveConstructible. INVOKE(DECAY_COPY (std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...) (20.8.2, 30.3.1.2) должно быть действительным выражением.

(3) Эффекты: […] если политика & launch :: async не равен нулю — вызовы INVOKE(DECAY_COPY (std::forward<F>(f)),DECAY_COPY (std::forward<Args>(args))...) (20.8.2, 30.3.1.2) как будто в новом потоке выполнения, представленном объектом потока с вызовами DECAY_COPY() оценивается в потоке, который вызвал async. Любое возвращаемое значение сохраняется как результат в общем состоянии. Любое исключение, передаваемое из выполнения INVOKE (DECAY_COPY (std :: forward (f))), DECAY_COPY (std :: forward (args)) …), сохраняется как исключительный результат в общем состоянии.
Объект потока хранится в общем состоянии и влияет на поведение любого асинхронного
вернуть объекты, которые ссылаются на это состояние.

К сожалению, это означает, что вы даже не можете заменить ссылку на std::reference_wrapperпотому что последний не может быть построен Я полагаю, используя std::unique_ptr вместо ссылки будет работать (подразумевая, однако, что ваши аргументы функции всегда будут жить в куче).

(РЕДАКТИРОВАТЬ / КОРРЕКТИРОВКА)
Я работал над связанной проблемой, когда понял, что std::reference_wrapper на самом деле позволяет обойти, хотя я утверждал обратное выше.

Если вы определяете функцию, которая оборачивает ссылки lvalue в std::reference_wrapper, но оставляя rvalue ссылки без изменений, вы можете передать T&& аргумент через эту функцию, прежде чем передать его std::async, Я вызвал эту специальную функцию-обертку wrap_lval ниже:

#include <thread>
#include <future>
#include <utility>
#include <iostream>
#include <vector>
#include <type_traits>

/* First the two definitions of wrap_lval (one for rvalue references,
the other for lvalue references). */

template <typename T>
constexpr T&&
wrap_lval(typename std::remove_reference<T>::type &&obj) noexcept
{ return static_cast<T&&>(obj); }

template <typename T>
constexpr std::reference_wrapper<typename std::remove_reference<T>::type>
wrap_lval(typename std::remove_reference<T>::type &obj) noexcept
{ return std::ref(obj); }/* The following is your code, except for one change. */
template <typename T>
std::string accessValueAsync(T&& obj)
{

std::future<std::string> fut =
std::async(std::launch::async,
[](T&& vec) mutable
{
return vec[0];
},
wrap_lval<T>(std::forward<T>(obj)));   // <== Passing obj through wrap_lval

return fut.get();
}

int main(int argc, char const *argv[])
{
std::vector<std::string> lvalue{"Testing"};

std::cout << accessValueAsync(lvalue) << std::endl;

std::cout << accessValueAsync(std::move(lvalue)) << std::endl;

return 0;
}

С этим изменением оба звонка accessValueAsync компилировать и работать. Первый, который использует ссылку lvalue, автоматически оборачивает его в std::reference_wrapper, Последний автоматически преобразуется обратно в ссылку lvalue, когда std::async вызывает лямбда-функцию.

7

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]