What happens when an rvalue reference is captured by copy?

Viewed 122

If I have

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

What is the type of x inside lambda: std::function<int()>, std::function<int()>& or std::function<int()>&&? And if the first, is the captured x initialized by the move constructor, or by the copy one (and I need [y = x(), x = std::move(x)] to move it instead)?

3 Answers

What is the type of x inside lambda

x is captured by-copy, and the type of data member declared in the closure type would be std::function<int()>. [expr.prim.lambda.capture]/10

(emphasis mine)

For each entity captured by copy, an unnamed non-static data member is declared in the closure type. ... The type of such a data member is the referenced type if the entity is a reference to an object, ...

And

is the captured x initialized by the move constructor, or by the copy one (and I need [y = x(), x = std::move(x)] to move it instead)?

Yes, you have to write x = std::move(x) explicitly, otherwise x would be copy-constructed since the captured object x is an lvalue itself.

And about why decltype(x) leads to the type std::function<int()>&&, because x here refers to the local variable x defined before the lambda. [expr.prim.lambda.capture]/11

(emphasis mine)

Every id-expression within the compound-statement of a lambda-expression that is an odr-use of an entity captured by copy is transformed into an access to the corresponding unnamed data member of the closure type.

[Note 7: An id-expression that is not an odr-use refers to the original entity, never to a member of the closure type. However, such an id-expression can still cause the implicit capture of the entity. — end note]

BTW (x) is not an id-expression and x here refers to the member of the closure type. Then decltype((x)) leads to type const std::function<int()> & (because (x) is an lvalue expression and operator() of the lambda is const-qualified; for mutable lambda it would be std::function<int()> &).

x is captured by value, using the copy constructor.

The "type of x" is a bit moot. The class member of the lambda class has type std::function<int()>, so not a reference. The expression x has type const std::function<int()>, and is an lvalue. (It's const because we're inside of a const-qualified operator(). It's not a reference because expressions, unlike variables, can't have reference types.)

Example:

#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 agree with the first assertion, while GCC thinks that the type is int &. I believe this to be a GCC bug in decltype.

All three compilers agree with the second assertion. Since all three compilers were tested on a platform where sizeof(void*) > sizeof(int), it means all three of them copied the variable instead of storing a reference.

I'm not able to language lawyer this, but here is some playground code. It looks like clang and gcc assume an rvalue reference while msvc assumes a value without reference qualifiers.

However, in all cases, your object will be copy constructed.

Related