【问题标题】:What happens when an rvalue reference is captured by copy?当一个右值引用被副本捕获时会发生什么?
【发布时间】:2021-05-25 15:26:13
【问题描述】:

如果我有

std::function<int()> &&x = [] { return 1; };
auto lambda = [y = x(), x] { 
    return y * x();
};

lambda 内部的x 是什么类型:std::function&lt;int()&gt;std::function&lt;int()&gt;&amp;std::function&lt;int()&gt;&amp;&amp;?如果是第一个,捕获的x 是由移动构造函数初始化还是由副本初始化(我需要[y = x(), x = std::move(x)] 来移动它)?

【问题讨论】:

  • 看起来像来自强制编译器错误的const std::function&lt;int ()&gt; - 实时 - godbolt.org/z/Pfh5z9K8G
  • 我错了还是实际上是std::function&lt;int()&gt;&amp;&amp;Wandbox.
  • 1.它将是std::function&lt;int()&gt;。 2. 是的,你需要明确写x = std::move(x)
  • @2b-t 在下面回答。
  • @songyuanyao 谢谢,我已经投了赞成票。玩弄一个简单的例子,我已经发现那里的decltype 实际上返回了本地variable,因为通过引用捕获变量不会做任何事情。非常感谢您的详细解释!

标签: c++ lambda c++14


【解决方案1】:

lambda里面的x是什么类型

x 被复制捕获,闭包类型中声明的数据成员类型为std::function&lt;int()&gt;[expr.prim.lambda.capture]/10

(强调我的)

对于通过副本捕获的每个实体,一个未命名的非静态数据成员是 在闭包类型中声明。 ... 这样一个数据成员的类型就是被引用的类型 如果实体是对对象的引用,...

捕获的x 是由移动构造函数初始化还是由副本初始化(而我需要[y = x(), x = std::move(x)] 来移动它)?

是的,您必须明确写出x = std::move(x),否则x 将被复制构造,因为捕获的对象x 本身就是一个左值。

以及为什么decltype(x)会导致std::function&lt;int()&gt;&amp;&amp;类型,因为这里的x指的是在lambda之前定义的局部变量x[expr.prim.lambda.capture]/11

(强调我的)

a 的复合语句中的每个 id 表达式 lambda 表达式是对复制捕获的实体的 odr 使用 转化为对相应未命名数据成员的访问 闭包类型。

[注 7:不是 odr-use 的 id-expression 指的是 原始实体,绝不是闭包类型的成员。 但是,这样的 id-expression 仍然可以导致实体的隐式捕获。 — 尾注]

顺便说一句,(x) 不是 id 表达式x 这里指的是闭包类型的成员。然后decltype((x)) 导致const std::function&lt;int()&gt; &amp; 类型(因为(x) 是一个左值表达式,而lambda 的operator() 是常量限定的;对于mutable lambda,它将是std::function&lt;int()&gt; &amp;)。

【讨论】:

    【解决方案2】:

    x 使用复制构造函数按值捕获。

    x 的类型”有点没有实际意义。 lambda 类的类成员的类型为std::function&lt;int()&gt;,因此不是引用。表达式x 的类型为const std::function&lt;int()&gt;,是一个左值。 (它是 const,因为我们在一个 const 限定的 operator() 内部。它不是引用,因为表达式与变量不同,can't have reference types。)

    例子:

    #include <type_traits>
    
    int main()
    {
        int &&x = 42;
        auto lambda = [x]
        {
            static_assert(std::is_same<decltype((x)), const int&>::value, "");
        };
    
        static_assert(sizeof lambda == sizeof(int), "");
    }
    

    Clang & MSVC 同意第一个断言,而 GCC 认为类型是int &amp;。我相信这是decltype 中的一个 GCC 错误。

    所有三个编译器都同意第二个断言。由于所有三个编译器都在 sizeof(void*) &gt; sizeof(int) 所在的平台上进行了测试,这意味着它们都复制了变量而不是存储引用。

    【讨论】:

    • decltype((x))(不像decltype(x))不会总是返回lvalue吗?
    • @2b-t 如果您的意思是“左值引用”,那么答案是否定的。左值返回&amp;,xvalues返回&amp;&amp;,prvalues返回非引用类型。
    • 我的意思是lvalue reference。我不确定decltype((x))此时应该给你什么信息......在这种情况下x不仅仅是像songyuanyao指出的那样是lambda之外的变量x吗?这意味着在您的情况下,无论xint&amp;&amp; 还是int,它总是会返回一个lvalue reference
    • @2b-t (x) 应该引用捕获,而不是原始引用。 decltype((x)) 应该给你什么信息” 它告诉我x 是常量。如果x 被引用捕获,它将是非常量。
    • @2b-t x是一个id-expression,所以它指的是原始实体,而(x)不是一个id-expression。所以decltype(x)decltype((x)) 给出不同的结果(std::function&lt;int()&gt;&amp;&amp; vs const std::function&lt;int()&gt; &amp;)。顺便说一句,mutable lambda decltype((x)) 将是 std::function&lt;int()&gt; &amp;
    【解决方案3】:

    我不能用语言律师这个,但here 是一些操场代码。看起来 clang 和 gcc 假定了一个右值引用,而 msvc 假定了一个没有引用限定符的值。

    但是,在所有情况下,您的对象都将被复制构造。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-03-24
      • 2023-03-25
      • 2022-06-27
      • 2011-01-24
      • 1970-01-01
      • 1970-01-01
      • 2016-01-10
      • 2013-06-09
      相关资源
      最近更新 更多