Std:: thread lambda с ref arg не компилируется
Я читаю С++ concurrency в действии. В главе 2.4 описывается алгоритм parallell_accumulate.
Я попробовал - как учебный эксперимент - заменить используемый здесь функтор с общей лямбдой.
Я перегонял ошибку компиляции до:
#include <thread>
template <typename T>
struct f {
void operator() (T& result) { result = 1;}
};
int main() {
int x = 0;
auto g = [](auto& result) { result = 1; };
std::thread(f<int>(), std::ref(x)); // COMPILES
std::thread(g, std::ref(x)); // FAILS TO COMPILE
}
Сообщение об ошибке:
In file included from /usr/include/c++/4.9/thread:39:0,
from foo.cpp:1:
/usr/include/c++/4.9/functional: In instantiation of ‘struct std::_Bind_simple<main()::<lambda(auto:1&)>(std::reference_wrapper<int>)>’:
/usr/include/c++/4.9/thread:140:47: required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = main()::<lambda(auto:1&)>&; _Args = {std::reference_wrapper<int>}]’
foo.cpp:13:31: required from here
/usr/include/c++/4.9/functional:1665:61: error: no type named ‘type’ in ‘class std::result_of<main()::<lambda(auto:1&)>(std::reference_wrapper<int>)>’
typedef typename result_of<_Callable(_Args...)>::type result_type;
^
/usr/include/c++/4.9/functional:1695:9: error: no type named ‘type’ in ‘class std::result_of<main()::<lambda(auto:1&)>(std::reference_wrapper<int>)>’
_M_invoke(_Index_tuple<_Indices...>)
^
Моя версия компилятора
$ g++ --version
g++ (Ubuntu 4.9.1-16ubuntu6) 4.9.1
Почему компиляция не выполняется для лямбда, но не для функтора?
РЕДАКТИРОВАТЬ: Как я могу достичь того, что делает функтор (присваивание ref) с помощью общей лямбда?
Ответы
Ответ 1
Еще одна вариация на ту же тему, что вывод аргумента шаблона не просматривает конверсии.
operator()
f<int>
-
void operator() (int& result);
когда вы передаете ему reference_wrapper<int>
, вызывается функция преобразования (operator int &
), дающая ссылку, которая может быть привязана к result
.
operator()
вашей общей лямбды
template<class T> void operator() (T& result) const;
Если он был передан reference_wrapper
lvalue, он выводит T
как reference_wrapper
, а затем не скомпилируется при назначении. (Присвоение a reference_wrapper
повторяет "ссылку", а не влияет на значение.)
Но это не удается даже до этого, потому что стандарт требует, чтобы то, что вы передали в std::thread
, должно быть вызвано с помощью prvalues - и ссылка на не константу lvalue не привязана к значению prvalue. Это ошибка, которую вы видите - result_of
не содержит type
, потому что ваш функтор не может быть вызван для типа аргумента. Если вы попытаетесь сделать g(std::ref(x));
, clang производит довольно явную ошибку:
main.cpp:16:5: error: no matching function for call to object of type '(lambda at main.cpp:11:14)'
g(std::ref(x));
^
main.cpp:11:14: note: candidate function [with $auto-0-0 = std::__1::reference_wrapper<int>] not viable: expects an l-value for 1st argument
auto g = [](auto& result) { result = 1; };
^
Вероятно, вам стоит рассмотреть возможность захвата соответствующей локальной ссылки:
auto g = [&x]() { x = 1; };
Или если по какой-либо причине вы должны использовать общую лямбда, тогда вы можете взять значение reference_wrapper
по значению (или по ссылке const), а затем развернуть его с помощью get()
:
auto g = [](auto result) { result.get() = 1; };
или, возможно, добавьте std::bind
, который разворачивает reference_wrapper
s, что позволяет выводить аргумент шаблона правильно (tip tip @Casey):
std::thread(std::bind(g, std::ref(x)));
или, возможно, отказаться от этой ерунды reference_wrapper
и написать свою лямбду вместо этого вместо него:
auto g = [](auto* result) { *result = 1; };
std::thread(g, &x);
Ответ 2
Есть всевозможные проблемы, связанные с передачей аргументов через "INVOKE (...)" семейство функций std::async
, std::bind
, std::thread::thread
. Если вы хотите использовать перегруженное имя функции или передать ссылку lvalue, или не дай бог передать rvalue по ссылке, вам будет трудно. Вы придете сюда к SO, и один из нас, кто узнал, что соответствующее заклинание передаст вам это. Надеюсь, вы это запомните в следующий раз.
Я думаю, что наилучшая практика, поскольку С++ 14 заключается в том, чтобы избежать того, чтобы аргумент передавал странность, обрабатывая сами аргументы и всегда предоставляя функции INVOKE функтору с нулевым аргументом, который инкапсулирует аргументы, необходимые для действительной целевой функции. Само по себе это позволяет получить именно семантику, которую вы намереваетесь, без необходимости знать каждую причуду и обходной путь и тонкие различия в интерфейсах функций семейства INVOKE. Обобщенный лямбда-захват С++ 14 упрощает инкапсуляцию любой функции и набора аргументов.
В вашем случае этот подход приведет к:
#include <thread>
template <typename T>
struct f {
void operator() (T& result) { result = 1;}
};
int main() {
int x = 0;
auto g = [](auto& result) { result = 1; };
std::thread([&]{ return f<int>{}(x); });
std::thread([&]{ return g(x); });
}
который выполняет точно так, как предполагалось, и является более читаемым.
std::reference_wrapper
был отличным в дни TR1, когда нам нужно было пройти ссылки через std::bind
, но его дни славы прошли, и я думаю, что это лучше всего избегать в современном С++.
Ответ 3
Вот функция для решения вашей проблемы. Он принимает объект функции и возвращает объект функции, который распаковывает std::reference_wrapper
, прежде чем передать его внутреннему функциональному объекту.
#include <utility>
#include <functional>
template<class T>
T&& unref( T&& t ){return std::forward<T>(t);}
template<class T>
T& unref( std::reference_wrapper<T> r ){ return r.get(); }
template<class F>
auto launder_refs( F&& f ) {
return [f = std::forward<F>(f)](auto&&... args){
return f( unref( std::forward<decltype(args)>(args) )... );
};
}
//
auto g = launder_refs([](auto& result) { result = 2; });
живой пример - теперь g
ведет себя так же, как ваш оригинальный g
, за исключением тех случаев, когда передано std::reference_wrapper
, он превращает их в прежде чем передать их в result
внутри.
Ваша проблема заключается в том, что std::reference_wrapper<T>&&
, переданный вашей лямбда, заставляет попытаться вывести U
таким образом, что U&
= std::reference_wrapper<T>&&
, и не существует.
Короче говоря, это ограничение вычитания типа в функциях шаблона (оно не учитывает конверсии), поскольку смешивание обоих преобразований и вычитание типа шаблона на одном и том же шаге приводило бы всех к батти.
Вышеприведенный код скрывает std::reference_wrapper
от основного лямбда-закрытия (или объекта функции). Он делает это и с минимальными накладными расходами.