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 от основного лямбда-закрытия (или объекта функции). Он делает это и с минимальными накладными расходами.