我最近开始为我正在开发的库添加异步支持,但遇到了一个小问题。我从这样的事情开始(稍后的完整上下文):
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&
,它突然被允许了。但它反而会在其他地方导致问题,因为 getter 不是 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);
而且,由于某种原因,它奏效了。
在这里让我感到困惑的是,它两次使用相同的参数(如果计算非异步变体,则所有三次),并且考虑到this
参数,因为我正在调用的函数是成员函数。
我挖得更深,找到了一些替代解决方案(std::thread
尽管参考),使用std::ref
. 我知道在引擎盖下std::async
运行,所以我挖掘了文档:std::thread
线程函数的参数按值移动或复制。如果需要将引用参数传递给线程函数,则必须对其进行包装(例如,使用
std::ref
orstd::cref
)。 (强调我的)
这是有道理的,并解释了它失败的原因。我认为std::async
也受此限制,并解释了它失败的原因。
但是,挖掘std::bind:
bind 的参数被复制或移动,并且永远不会通过引用传递,除非包裹在
std::ref
or中std::cref
。(强调我的)
我不使用(或者如果我用,std::ref
替换),但至少如果我理解文档正确,这两个都应该无法编译。cppreference.com 上的示例也无需编译(在 Coliru 中使用 Clang 和 C++ 17 进行了测试)。const
std::cref
std::cref
这里发生了什么?
如果重要的话,除了 coliru 环境,我最初在 Docker 中重现了这个问题,运行 Ubuntu 18.04 和 Clang 8.0.1(64 位)。在这两种情况下都针对 C++ 17 编译。