使用ref arg获取lambda的std :: thread无法编译

时间:2015-01-26 16:32:16

标签: c++ generics lambda c++14

我正在阅读C++ concurrency in action。第2.4章描述了parallell_accumulate算法。

作为一个学习实验,我尝试用一​​般的lambda替换那里使用的仿函数。

我已将编译错误提炼为:

#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

为什么编译失败的lambda而不是functor?

编辑:如何使用通用lambda实现仿函数所做的工作(分配给参考)?

3 个答案:

答案 0 :(得分:18)

同一主题的另一个变体是模板参数推断不能通过转换查看。

operator()的{​​{1}}是

f<int>

当您将void operator() (int& result); 传递给它时,会调用转换函数(reference_wrapper<int>),从而产生可绑定到operator int &的引用。

您的通用lambda的result

operator()

如果传递了template<class T> void operator() (T& result) const; 左值,它会将reference_wrapper推导为T,然后无法编译分配。 (分配给reference_wrapper重新设置“引用”而不是影响该值。)

但它甚至在此之前就失败了,因为标准要求传递给reference_wrapper的内容必须可以使用prvalues调用 - 并且非const左值引用不会绑定到prvalue。这是您看到的错误 - std::thread不包含result_of,因为您的仿函数不能为参数类型调用。如果您尝试执行type,请将produces更明确的错误:

g(std::ref(x));

您应该考虑通过引用来捕获相关的本地:

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; };         
    ^

或者,无论出于何种原因,您必须使用通用lambda,那么您可以使用auto g = [&x]() { x = 1; }; 值(或通过const引用),然后使用reference_wrapper打开它:

get()

或者可能添加一个 auto g = [](auto result) { result.get() = 1; }; 来打开std::bind,这样可以让模板参数推断做正确的事(帽子提示@Casey):

reference_wrapper

或者可能免除这个 std::thread(std::bind(g, std::ref(x))); 废话并写下你的lambda来取代非拥有指针:

reference_wrapper

答案 1 :(得分:5)

通过“INVOKE(...)”系列函数std::asyncstd::bindstd::thread::thread传递参数会涉及到各种各样的问题。如果你想使用一个重载的函数名,或者传递一个左值引用,或者天堂禁止通过引用传递一个右值,你将会遇到困难。你会来到这里,我们其中一个已经学会相关咒语的人会把它传给你。希望你下次出现时能记住它。

我认为自C ++ 14以来的最佳实践是通过自己处理参数来避免参数传递奇怪,并且总是给INVOKE函数一个零参数函子,它封装了实际目标函数所需的参数。通过自己动手,您可以获得您想要的语义,而无需了解每个怪癖和变通方法以及INVOKE系列函数接口的细微差别。 C ++ 14广义lambda捕获使得封装任何类型的函数和参数集非常简单。

在您的情况下,这种方法会导致:

#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传递参考时,但它的辉煌岁月已过去,我认为现代C ++中最好避免使用它。

答案 2 :(得分:1)

这是一个解决您问题的功能。它接受一个函数对象,并返回一个函数对象,该函数对象在将它传递给内部函数对象之前将解包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; });

live example - 现在g的行为与您原来的g一样,除非传递std::reference_wrapper,否则在将它们传递给{{1}之前将其转为引用在里面。

您的问题是,传递给您的lambda的result会导致它尝试推导出std::reference_wrapper<T>&& U = U&,而且不存在。

简而言之,这是模板函数中类型推导的限制(它不考虑转换),因为在同一步骤中混合转换和模板类型推导会使每个人都感到沮丧。

上面的代码隐藏了基础lambda闭包(或函数对象)中的std::reference_wrapper<T>&& s。它的开销也很小。