I recently started adding async support to a library I'm working on, but I hit a slight problem. I started off with something like this (full context later):
return executeRequest<int>(false, d, &callback, false);
That was before adding async support. I attempted to change it to:
return std::async(std::launch::async, &X::executeRequest<int>, this, false, d, &callback, false);
But it failed to compile.
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;
}
And it fails with:
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.
The only actual difference between the two (at least that I can see visibly) is that I need to explicitly pass this
, because I'm referencing a member function
I played a little around with it, and managed to find that if I replace it with a const RequestData&
, it's suddenly allowed. But it instead results in issues elsewhere, because the getter isn't const. At least from what I could find, I need to make it a const function, which is fine for the getter itself, but I also have some setters meaning I can't go with that.
Anyway, I figured I could try std::bind
instead. I replaced the async call with:
auto func = std::bind(&X::executeRequest<int>, this, false, d, &callback, false);
return std::async(std::launch::async, func);
And, for some reason, it worked.
The thing that confuses me here, is that it uses the same arguments both times (all three times if you count the non-async variant), and takes the this
argument into consideration, given the function I'm calling is a member function.
I dug deeper, and found some alternative solutions (referencing std::thread
though), that used std::ref
. I know std::async
runs std::thread
under the hood, so I dug up the documentation:
The arguments to the thread function are moved or copied by value. If a reference argument needs to be passed to the thread function, it has to be wrapped (e.g. with
std::ref
orstd::cref
). (emphasis mine)
That makes sense, and explains why it failed. I assume std::async
is limited by this as well, and explains why it failed.
However, digging up std::bind:
The arguments to bind are copied or moved, and are never passed by reference unless wrapped in
std::ref
orstd::cref
. (emphasis mine)
I don't use std::ref
(or if I replace with a const
, std::cref
) in either, but at least if I understood the documentation right, both of these should fail to compile. The example on cppreference.com also compiles without std::cref
(tested in Coliru with Clang and C++ 17).
What's going on here?
If it matters, aside the coliru environment, I originally reproduced the issue in Docker, running Ubuntu 18.04 with Clang 8.0.1 (64 bit). Compiled against C++ 17 in both cases.