我最近开始向正在使用的库中添加异步支持,但是遇到了一个小问题。我从这样的事情开始(稍后是完整的上下文):
return executeRequest<int>(false, d, &callback, false);
那是在添加异步支持之前。我试图将其更改为:
return std::async(std::launch::async, &X::executeRequest<int>, this, false, d, &callback, false);
但是无法编译。
MCVE:
#include <iostream>
#include <future>
int callback(const int& t) {
std::cout << t << std::endl;
return t;
}
class RequestData {
private:
int x;
public:
int& getX() {
return x;
}
};
class X {
public:
template <typename T>
T executeRequest(bool method, RequestData& requestData,
std::function<T(const int&)> parser, bool write) {
int ref = 42;
std::cout << requestData.getX() << std::endl;
return parser(ref);
}
int nonAsync() {
// Compiles
RequestData d;
return this->executeRequest<int>(false, d, &callback, false);
}
std::future<int> getComments() {
RequestData d;
// Doesn't compile
return std::async(std::launch::async, &X::executeRequest<int>, this, false, d, &callback, false);
}
};
int main() {
X x;
auto fut = x.getComments();
std::cout << "end: " << fut.get() << std::endl;
}
它失败并显示:
In file included from main.cpp:2:
In file included from /usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/future:38:
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/functional:1505:56: error: no type named 'type' in 'std::result_of<std::_Mem_fn<int (X::*)(bool, RequestData &, std::function<int (const int &)>, bool)> (X *, bool, RequestData, int (*)(const int &), bool)>'
typedef typename result_of<_Callable(_Args...)>::type result_type;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/future:1709:49: note: in instantiation of template class 'std::_Bind_simple<std::_Mem_fn<int (X::*)(bool, RequestData &, std::function<int (const int &)>, bool)> (X *, bool, RequestData, int (*)(const int &), bool)>' requested here
__state = __future_base::_S_make_async_state(std::__bind_simple(
^
main.cpp:33:25: note: in instantiation of function template specialization 'std::async<int (X::*)(bool, RequestData &, std::function<int (const int &)>, bool), X *, bool, RequestData &, int (*)(const int &), bool>' requested here
return std::async(std::launch::async, &X::executeRequest<int>, this, false, d, &callback, false);
^
In file included from main.cpp:2:
In file included from /usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/future:38:
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.5.0/../../../../include/c++/5.5.0/functional:1525:50: error: no type named 'type' in 'std::result_of<std::_Mem_fn<int (X::*)(bool, RequestData &, std::function<int (const int &)>, bool)> (X *, bool, RequestData, int (*)(const int &), bool)>'
typename result_of<_Callable(_Args...)>::type
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
2 errors generated.
两者之间的唯一实际区别(至少我可以看到)是我需要显式传递this
,因为我引用的是成员函数
我玩了一下,设法找到了,如果我用const RequestData&
代替它,突然被允许了。但这反而会导致其他问题,因为吸气剂不是const。至少从我能找到的东西来看,我需要使其成为一个const函数,这对getter本身很好,但是我也有一些setter,这意味着我不能这样做。
无论如何,我认为我可以尝试使用std::bind
。我将异步调用替换为:
auto func = std::bind(&X::executeRequest<int>, this, false, d, &callback, false);
return std::async(std::launch::async, func);
并且由于某种原因,it worked。
让我感到困惑的是,它两次都使用相同的参数(如果计算非异步变量,则全部使用3次),并且在给定函数I的情况下考虑了this
参数m调用是成员函数。
我更深入地研究,找到了一些使用std::thread
的替代解决方案(尽管引用了std::ref
)。我知道std::async
在引擎盖下运行std::thread
,所以我挖了the documentation:
线程函数的参数按值移动或复制。如果需要将引用参数传递给线程函数,则必须对其进行包装(例如,使用
std::ref
或std::cref
)。 (强调我的)
这很有道理,并解释了失败的原因。我假设std::async
也受此限制,并说明了失败的原因。
但是,挖掘std::bind:
要绑定的参数将被复制或移动,并且除非封装在
std::ref
或std::cref
中,否则绝不会通过引用传递。 (强调我的)
我都不使用std::ref
(或者如果我用const
,std::cref
代替),但是至少如果我理解文档的权利,这两个都应该失败编译。 example on cppreference.com也可以不使用std::cref
进行编译(在Coliru中使用Clang和C ++ 17进行了测试)。
这是怎么回事?
如果重要的话,除了coliru环境之外,我最初是在Docker上重现该问题的,它使用Clang 8.0.1(64位)运行Ubuntu 18.04。在两种情况下均针对C ++ 17进行编译。
答案 0 :(得分:5)
该标准略有不同。对于std::bind
:
要求:
is_constructible_v<FD, F>
为true
。 对于Ti
中的每个BoundArgs
,is_constructible_v<TDi, Ti>
应为true
。INVOKE(fd, w1, w2, …, wN)
([func.require])应该是某些值w1
,w2
,…,wN
的有效表达式,其中N
的值是{ {1}}。 如下所述,呼叫包装器sizeof...(bound_args)
的cv限定词cv不应是volatile或const volatile。返回:参数转发调用包装器
g
([func.require])。g
的效果应为g(u1, u2, …, uM)
INVOKE(fd, std::forward<V1>(v1), std::forward<V2>(v2), …, std::forward<VN>(vN))
,...,v1
具有特定类型。在您的情况下,重要的是与vN
对应的存储变量的类型为d
,类型为std::decay_t<RequestData&>
。在这种情况下,您可以使用左值RequestData
轻松调用executeRequest<int>
。
对RequestData
的要求要强得多:
要求:
std::async
和F
中的每个Ti
必须满足 Cpp17MoveConstructible 要求,并且Args
巨大的区别是decay-copy。对于INVOKE(decay-copy(std::forward<F>(f)),
decay-copy(std::forward<Args>(args))...) // see [func.require], [thread.thread.constr]
,您将获得以下信息:
d
这是对decay-copy(std::forward<RequestData&>(d))
函数的调用(仅博览会),其返回类型为decay-copy
,因此返回std::decay_t<RequestData&>
,这就是编译失败的原因。
请注意,如果您使用RequestData
,则该行为将是不确定的,因为std::ref
的生存期可能在调用d
之前结束。
答案 1 :(得分:2)
让我感到困惑的是,两次都使用相同的参数
但是它两次都转发不同。调用异步版本时,将调用as if by calling:
std::invoke(decay_copy(std::forward<Function>(f)),
decay_copy(std::forward<Args>(args))...);
争论变成了类似于临时的东西!因此,引用RequestData& requestData
无法绑定到其参数。 const引用,右值引用或按值平原参数在这里可以工作(例如在绑定中),但非const左值引用不能。
std::bind
的调用方式不同。它也存储副本,但是"the ordinary stored argument arg is passed to the invokable object as lvalue argument[sic]"具有从bind
对象本身派生的参数的cv限定。由于std::bind
创建了一个非常量绑定对象,因此为requestData
提供了一个非常量左值。引用很高兴地与之绑定。