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

标签 c++ c++11 gcc lambda variadic-templates

我在使用 @R. Martinho Fernandes 的模板中有一些曲折的代码在可变参数模板中循环展开一些打包参数并在参数列表中的每个参数上调用相同代码的技巧。

但是,似乎好像 lambda 没有被正确初始化,而是在 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;
}

我得到以下输出:

&bar=0x7ffd22a2b5b0
  bar=0x971c20
  bar=2a

&bar=0x7ffd22a2b5b0
  bar=0
Segmentation fault (core dumped)

所以,我相信我看到的是两个仿函数实例共享捕获变量 bar 的相同地址,并且在调用第一个仿函数 bar 之后> 被设置为 nullptr,然后当第二个仿函数 seg' 尝试取消引用 same 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)...};
  };
}

这需要一个 lambda f 并返回一个对象,该对象将在其每个参数上调用 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)... );
}

live example .

我最初认为它与 std::function 构造函数有关。它不是。 A simpler example没有以相同方式崩溃的 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;
}

we can invoke the segfault without the 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

我还没有解析反汇编,但在玩第一个时,它显然破坏了第二个 lambda 的状态。

关于C++ lambda 在模板的第二次扩展中没有捕获变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39128413/

相关文章:

c++ - 递归宏传播的结果是什么?

c++ - CPP,创建数组,我只看到一个参数

C 程序因大型数组而崩溃

c - 从应用程序 makefile 运行库 makefile

静态成员函数中静态变量的 C++ 作用域

c++ - 是否保证在 block 结束之前不会调用 C++ 析构函数?

c++ - 如何使用 std::log 作为功能?

c++ - 判断模板参数包中 "optimal"公共(public)数值类型

c++ - bitset 是否保证连续性?

debugging - 如何解释内存映射转储输出?