我有一个功能模板,我希望完美转发到我在另一个线程上运行的lambda。这是一个可以直接编译的最小测试用例:
#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以防这可能是编译器问题(可能不是)答案 0 :(得分:7)
我理解它的方式你不能通过async
使用一个函数来预期非const左值引用作为参数,因为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)影响:[...]如果政策&amp; launch :: async非零 - 调用
INVOKE(DECAY_COPY (std::forward<F>(f)),DECAY_COPY (std::forward<Args>(args))...)
(20.8.2,30.3.1.2),好像在一个新的执行线程中,由一个线程对象表示,对DECAY_COPY()
的调用在调用异步的线程。任何返回值都作为结果存储在共享状态中。从执行INVOKE(DECAY_COPY(std :: forward(f)),DECAY_COPY(std :: forward(args))...)传播的任何异常都作为异常结果存储在共享状态中。
线程对象存储在共享状态中,并影响任何异步的行为 返回引用该状态的对象。
不幸的是,这意味着您甚至无法用std::reference_wrapper
替换引用,因为后者不是可移动构造的。我想使用std::unique_ptr
而不是引用会起作用(但是,暗示你的函数参数将永远存在于堆中)。
(适用EDIT /校正)强>
当我意识到std::reference_wrapper
实际上启用了一种解决方法时,我正在研究一个相关的问题,尽管我声称上面的反面。
如果定义一个在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
都可以编译并运行。第一个使用左值引用,自动将其包装在std::reference_wrapper
中。当std::async
调用lambda函数时,后者会自动转换回左值引用。