首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >C++混淆闭包捕获[v] vs [v = v]

C++混淆闭包捕获[v] vs [v = v]
EN

Stack Overflow用户
提问于 2022-07-06 02:54:21
回答 2查看 865关注 0票数 16

在下面的代码中,编译器似乎有时更喜欢调用模板化的构造函数,并且在复制构造函数应该很好的时候无法编译。根据值被捕获为v还是v= v,行为似乎会发生变化,我认为它们应该是完全相同的。我遗漏了什么?

我使用gcc 11.2.0并用"g++ file.cpp -std=C++17“编译它

代码语言:javascript
运行
复制
#include <functional>
#include <iostream>
#include <string>

using namespace std;

template <class T>
struct record {
  explicit record(const T& v) : value(v) {}

  record(const record& other) = default;
  record(record&& other) = default;

  template <class U>
  record(U&& v) : value(forward<U>(v)) {} // Removing out this constructor fixes print1

  string value;
};

void call(const std::function<void()>& func) { func(); }

void print1(const record<string>& v) {
  call([v]() { cout << v.value << endl; }); // This does not compile, why?
}

void print2(const record<string>& v) {
  call([v = v]() { cout << v.value << endl; }); // this compiles fine
}

int main() {
  record<string> v("yo");
  print1(v);
  return 0;
}
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2022-07-06 08:01:18

我不反对康桓瑋的回答,但我觉得有点难理解,所以让我用另一个例子来解释它。

代码语言:javascript
运行
复制
#include <functional>
#include <iostream>
#include <typeinfo>
#include <type_traits>

struct tracer {
  tracer() { std::cout << "default constructed\n"; }
  tracer(const tracer &) { std::cout << "copy constructed\n"; }
  tracer(tracer &&) { std::cout << "move constructed\n"; }
  template<typename T> tracer(T &&t) {
    if constexpr (std::is_same_v<T, const tracer>)
      std::cout << "template constructed (const rvalue)\n";
    else if constexpr (std::is_same_v<T, tracer&>)
      std::cout << "template constructed (lvalue)\n";
    else
      std::cout << "template constructed (other ["
                << typeid(T).name() << "])\n";
  }
};

int
main()
{
  using fn_t = std::function<void()>;

  const tracer t;
  std::cout << "==== value capture ====\n";
  fn_t([t]() {});
  std::cout << "==== init capture ====\n";
  fn_t([t = t]() {});
}

运行时,此程序输出以下内容:

代码语言:javascript
运行
复制
default constructed
==== value capture ====
copy constructed
template constructed (const rvalue)
==== init capture ====
copy constructed
move constructed

这是怎么回事?首先,在这两种情况下,编译器必须实现一个临时lambda对象,以传递给fn_t的构造函数。然后,fn_t的构造函数必须复制lambda对象才能保持它。(通常情况下,std::function可能比传递给其构造函数的lambda更长时间,因此它不能仅通过引用保留lambda。)

在第一种情况下(值捕获),捕获的t的类型恰好是t的类型,即const tracer。因此,您可以将lambda对象的未命名类型看作某种编译器定义的struct,其中包含一个类型为const tracer的字段。让我们给这个结构一个假的LAMBDA_T名称。因此,fn_t构造函数的参数是LAMBDA_T&&类型,因此访问内部字段的表达式是const tracer&&类型,它比实际的复制构造函数更符合模板构造函数的转发引用。(在重载解析中,当rvalue可用时,rvalue比绑定到const lvalue引用更倾向于绑定。)

在第二种情况下(init ),捕获的t = t的类型相当于auto tnew = t这样的声明中的tnew类型,即tracer。因此,现在我们内部LAMBDA_T结构中的字段将是tracer类型而不是const tracer类型,当必须移动复制LAMBDA_T&&类型的参数到fn_t构造函数时,编译器将选择tracer的正常移动构造函数来移动该字段。

票数 8
EN

Stack Overflow用户

发布于 2022-07-06 04:22:27

对于[v],lambda内部成员变量v的类型是const record,所以当您

代码语言:javascript
运行
复制
void call(const std::function<void()>&);

void print1(const record<string>& v) {
  call([v] { });
}

因为[v] {}是一个prvalue,所以当它初始化const std::function&时,v将被const record&&复制,模板构造函数将被选择,因为它不受约束。

为了调用v的复制构造函数,您可以

代码语言:javascript
运行
复制
void call(const std::function<void()>&);

void print1(const record<string>& v) {
  auto l = [v] { };
  call(l);
}

对于[v=v],lambda中的成员变量v的类型是record,所以当prvalue lambda初始化std::function时,它将直接调用record的移动构造函数,因为record&&更匹配。

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72877471

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档