Ros*_*ers 8 c++ lambda gcc variadic-templates c++11
我在使用@R的模板中有一些曲折的代码.Martinho Fernandes的循环技巧在一个可变参数模板中展开一些压缩参数,并在参数列表中的每个参数上调用相同的代码.
但是,似乎 lambdas没有被正确初始化,而是在functor(?)实例之间共享变量,这似乎是错误的.
鉴于此代码:
#include <iostream>
#include <functional>
template<typename... Args>
void foo(Args ... args) {
int * bar = new int();
*bar = 42;
using expand_type = int[];
expand_type{(
args([bar]() {
std::cerr<<std::hex;
std::cerr<<"&bar="<<(void*)&bar<<std::endl;
std::cerr<<" bar="<<(void*)bar<<std::endl;
std::cerr<<" bar="<<*bar<<std::endl<<std::endl;
}),
0) ...
};
};
int main() {
std::function<void(std::function<void()>)> clbk_func_invoker = [](std::function<void()> f) { f(); };
foo(clbk_func_invoker, clbk_func_invoker);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我得到以下输出:
&bar=0x7ffd22a2b5b0
bar=0x971c20
bar=2a
&bar=0x7ffd22a2b5b0
bar=0
Segmentation fault (core dumped)
Run Code Online (Sandbox Code Playgroud)
所以,我相信我看到的是两个仿函数实例共享捕获变量的相同地址bar
,并且在调用第一个仿函数之后,bar
设置为nullptr
,然后第二个仿函数seg'-faults在尝试时取消引用相同的 bar
变量(在完全相同的地址中).
仅供参考,我意识到我可以通过将[bar](){...
仿函数移动到变量std::function
变量然后捕获该变量来解决此问题.但是,我想了解为什么第二个仿函数实例使用完全相同的bar
地址以及它获取nullptr
值的原因.
我使用GNU的g ++运行它与昨天检索和编译的主干版本.
带有 lambda 表达式的参数包往往会适合编译器。避免这种情况的一种方法是将扩展部分和 lambda 部分分开。
template<class F, class...Args>
auto for_each_arg( F&& f ) {
return [f=std::forward<F>(f)](auto&&...args){
using expand_type = int[];
(void)expand_type{0,(void(
f(decltype(args)(args))
),0)...};
};
}
Run Code Online (Sandbox Code Playgroud)
这需要一个 lambdaf
并返回一个对象,该对象将调用f
它的每个参数。
然后我们可以重写foo
来使用它:
template<typename... Args>
void foo(Args ... args) {
int * bar = new int();
*bar = 42;
for_each_arg( [bar](auto&& f){
f( [bar]() {
std::cerr<<std::hex;
std::cerr<<"&bar="<<(void*)&bar<<std::endl;
std::cerr<<" bar="<<(void*)bar<<std::endl;
std::cerr<<" bar="<<*bar<<std::endl<<std::endl;
} );
} )
( std::forward<Args>(args)... );
}
Run Code Online (Sandbox Code Playgroud)
我最初认为这与构造函数有关std::function
。它不是。 一个更简单的例子,没有以std::function
同样的方式崩溃:
template<std::size_t...Is>
void foo(std::index_sequence<Is...>) {
int * bar = new int();
*bar = 42;
using expand_type = int[];
expand_type{(
([bar]() {
std::cerr<<"bar="<<*bar<<'\n';
})(),
(int)Is) ...
};
}
int main() {
foo(std::make_index_sequence<2>{});
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我们可以在不使用 的情况下调用段错误cerr
,从而使反汇编更易于阅读:
void foo<3, 0ul, 1ul>(std::integer_sequence<unsigned long, 0ul, 1ul>)::{lambda()#1}::operator()() const:
pushq %rbp
movq %rsp, %rbp
movq %rdi, -8(%rbp)
movq -8(%rbp), %rax
movq (%rax), %rax
movl $3, (%rax)
nop
popq %rbp
ret
void foo<3, 0ul, 1ul>(std::integer_sequence<unsigned long, 0ul, 1ul>):
pushq %rbp
movq %rsp, %rbp
pushq %rbx
subq $40, %rsp
movl $4, %edi
call operator new(unsigned long)
movl $0, (%rax)
movq %rax, -24(%rbp)
movq -24(%rbp), %rax
movl $42, (%rax)
movq -24(%rbp), %rax
movq %rax, -48(%rbp)
leaq -48(%rbp), %rax
movq %rax, %rdi
call void foo<3, 0ul, 1ul>(std::integer_sequence<unsigned long, 0ul, 1ul>)::{lambda()#1}::operator()() const
movabsq $-4294967296, %rax
andq %rbx, %rax
movq %rax, %rbx
movq $0, -32(%rbp)
leaq -32(%rbp), %rax
movq %rax, %rdi
call void foo<3, 0ul, 1ul>(std::integer_sequence<unsigned long, 0ul, 1ul>)::{lambda()#1}::operator()() const
movl %ebx, %edx
movabsq $4294967296, %rax
orq %rdx, %rax
movq %rax, %rbx
nop
addq $40, %rsp
popq %rbx
popq %rbp
ret
Run Code Online (Sandbox Code Playgroud)
我还没有解析反汇编,但在使用第一个 lambda 时,它显然会破坏第二个 lambda 的状态。