Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why capturing by reference in lambda doesn't change the type of the variables?

Tags:

c++

lambda

c++14

I thought that capturing by reference changes the types of the variables. Lets consider the following example:

#include <cassert>
#include <type_traits>

int main()
{
    int x = 0;
    int& x_ref = x;
    const int x_const = x;
    const int& x_const_ref = x_const;

    auto lambda = [&]()
    {
        static_assert(std::is_same<decltype(x), int>::value, "!");
        static_assert(std::is_same<decltype(x_ref), int&>::value, "!");
        static_assert(std::is_same<decltype(x_const), const int>::value, "!");
        static_assert(std::is_same<decltype(x_const_ref), const int&>::value, "!");
    };

    lambda();
}

None of the checks failed, so the type of original variables are preserved. So, how capturing by reference really works? I thought if user captures by reference, new variables with the same name are introduced to the local scope to have reference type of the original variables. But it seems like it is not the case.

Question: what is the motivation for this? Or is it me misunderstanding something?

like image 206
Incomputable Avatar asked Feb 25 '17 21:02

Incomputable


2 Answers

Let's start with something simple:

int x = 0;
auto lambda = [=]{ return x; };

What does return x actually do there? The "obvious" answer is that it returns the value of lambda's member variable named x. But that isn't actually the case. The lambda doesn't have a member named x - the lambda's data members itself are unnamed. What happens it that:

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

When you use x, you're actually referring to the appropriate member variable. So the above expression could be interpreted as:

struct __unnamed {
    int _1;

    __unnamed(int i) : _1(i) { }
    void operator=(__unnamed const&) = delete;
    int operator() const { return _1; }
};

__unnamed lambda(x);

The point of why I'm saying this is... there's only one x here. The int x variable. So when you do decltype(x), that gives you the declared type of x. Which is, of course, int. That doesn't change if you capture by reference, because the capture of your lambda doesn't change the types of the variables your capturing.


Now, if you did want to access those unnamed members of the lambda, you could do that. But instead of decltype(x) you want decltype((x)):

Every occurrence of decltype((x)) where x is a possibly parenthesized id-expression that names an entity of automatic storage duration is treated as if x were transformed into an access to a corresponding data member of the closure type that would have been declared if x were an odr-use of the denoted entity.

That is, while in your example, decltype(x) is int and decltype(x_const) is int const... decltype((x)) is int& and decltype((x_const)) is int const&. This is probably more what you were actually looking for. Because now we're actually referring to the lambda's members, or at least as if we were. But note here that the usual decltype((id)) rules still apply, so you will always get reference types for lvalues.

like image 176
Barry Avatar answered Sep 18 '22 08:09

Barry


I thought if user captures by reference, new variables with the same name are introduced to the local scope to have reference type of the original variables.

The standard says (emphasis mine):

An entity is captured by reference if it is implicitly or explicitly captured but not captured by copy. It is unspecified whether additional unnamed non-static data members are declared in the closure type for entities captured by reference. If declared, such non-static data members shall be of literal type.

Therefore your expectations were wrong. Capturing by reference doesn't mean create a local reference to an object in the outer scope.


As a side note, consider the following:

int x;
auto l = [&x](){ return [&x](){} }();

If your expectations were right, x would have been a reference to a reference within the lambda l, that is something that doesn't exist in C++.

like image 44
skypjack Avatar answered Sep 21 '22 08:09

skypjack