Я недавно начал добавлять поддержку асинхронности в библиотеку, над которой я работаю, но у меня возникла небольшая проблема. Я начал с чего-то вроде этого (полный контекст позже):
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. По крайней мере, из того, что я смог найти, мне нужно сделать ее константной функцией, что хорошо для самого геттера, но у меня также есть некоторые сеттеры, означающие, что я не могу с этим согласиться.
В любом случае, я решил попробовать 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
или std::cref
).
(выделение мое)
Это имеет смысл и объясняет, почему это не удалось. Я предполагаю, что std::async
также ограничен этим и объясняет, почему это не удалось.
Однако, выкапывая std :: bind :
Аргументы для привязки копируются или перемещаются, и никогда не передаются по ссылке , если они не заключены в std::ref
или std::cref
.
(выделение мое)
Я не использую std::ref
(или если я заменю на const
, std::cref
) в любом из них, но, по крайней мере, если я правильно понял документацию, оба из них не должны компилироваться. Пример на cppreference.com также компилируется без std::cref
(протестировано в Coliru с Clang и C ++ 17).
Что здесь происходит?
Если это имеет значение, за исключением среды coliru, я изначально воспроизводил проблему в Docker с Ubuntu 18.04 с Clang 8.0.1 (64-битная версия). Скомпилировано против C ++ 17 в обоих случаях.