std::线程使用 ref arg 获取 lambda 无法编译
std::thread taking lambda with ref arg fails to compile
我正在阅读C++并发的运行。 第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 的编译失败而不是函子的编译失败?
编辑:如何使用通用 lambda 实现函子正在做的事情(分配给引用)?
同一主题的另一个变体,即模板参数推导不会查看转化。
f<int>
operator()
是
void operator() (int& result);
当你将reference_wrapper<int>
传递给它时,转换函数(operator int &
)被调用,产生一个可以绑定到result
的引用。
您的通用 lambda 的operator()
是
template<class T> void operator() (T& result) const;
如果传递了一个reference_wrapper
的左值,它将推断T
为reference_wrapper
,然后无法在赋值上编译。(对reference_wrapper
赋值会重新拔插"引用",而不会影响值。
但它甚至在此之前就失败了,因为该标准要求你传递给std::thread
的内容必须可以用 prvalues 调用 - 并且非常量左值引用不会绑定到 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; };
或者,无论出于何种原因,您必须使用通用 lambda,那么您可以按值(或 const 引用)获取reference_wrapper
,然后使用 get()
将其解开包装:
auto g = [](auto result) { result.get() = 1; };
或者添加一个std::bind
,它将解开reference_wrapper
s,让模板参数推导做正确的事情(帽子提示 @Casey):
std::thread(std::bind(g, std::ref(x)));
或者也许放弃这种reference_wrapper
废话,而编写您的 lambda 以取一个非拥有指针:
auto g = [](auto* result) { *result = 1; };
std::thread(g, &x);
通过"INVOKE(...)"系列函数传递参数涉及各种各样的问题,std::async
、std::bind
、std::thread::thread
。如果你想使用重载的函数名称,或者传递一个左值引用,或者天堂禁止通过引用传递右值,你会很难过。你会来到SO,我们中的一位学会了相关咒语的人会把它传给你。希望下次出现时你会记住它。
我认为自 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++最好避免它。
这是一个解决您问题的函数。 它接受一个函数对象,并返回一个函数对象,该对象将在将 std::reference_wrapper
s 传递给内部函数对象之前解压缩。
#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
。
问题是传递给您的 lambda 的std::reference_wrapper<T>&&
导致它尝试推断出一个U
,使得 U&
= std::reference_wrapper<T>&&
,并且不存在。
的限制(它不考虑转换),因为在同一步骤中混合转换和模板类型推导会让每个人都感到沮丧。
上面的代码从底层 lambda 闭包(或函数对象)中隐藏了 std::reference_wrapper
s。 它以最小的开销这样做。
- 为什么在直接初始化和赋值中传递 lambda 而不是在复制初始化中传递 lambda 时会编译?
- 组合 lambda 捕获时出现编译错误
- 为什么 GCC 和 clang 之间编译的 c++17 lambda 存在差异?
- 将 lambda 函数转换为另一个编译单元中的普通函数会缩短编译时间吗?
- lambda deleter for Unique_ptr导致编译错误与函数
- 为什么使用递归lambda时会遇到编译错误
- lambda 和映射,引用参数 - 编译错误
- 当命名的 lambda 用作模板类参数或构造函数参数时,类模板无法编译
- 有没有办法在编译时检测是否可以使用一组给定的参数类型成功调用通用 lambda?
- 如何编译C 库在AWS Lambda中使用
- std::绑定到 lambda: 编译错误
- lambda 编译错误
- 在编译时获取捕获 lambda 的类型
- 可以用参数包编译C 17 lambda继承的编译器
- 如果对QObject::connect()使用lambda函数,则使用gcc编译失败
- 嵌套的lambda表达式在用Visual C++编译和生成巨大的对象文件时速度非常慢
- std::线程使用 ref arg 获取 lambda 无法编译
- Lambda被编译成什么类型
- 在头文件中使用 lambda 函数时出现编译错误
- c++11嵌套lambda编译segfault