C++ lambda没有捕获模板中第二次扩展的变量?

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 ++运行它与昨天检索和编译的主干版本.

Yak*_*ont 3

带有 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 的状态。