Why is lambda's capture a unicorn?

1,277 views
Skip to first unread message

picpps...@gmail.com

unread,
Mar 19, 2019, 4:11:41 AM3/19/19
to ISO C++ Standard - Discussion
Hello,

recently I was reading about the lambdas in detail and one thing made me wonder, specifically,

why in this case:
{
  int a=42;
  {int a = a; // unknown a value}
}

but in case of the lambda

int x=42;
[x=x]{x; // here x is 42}

First thing I thought, that [x=x] is purely some kind of a declaration, after all the declaration
is :

Declarations generally specify how names are to be interpreted.

so de definition would match, but none of the syntaxes matches the one of lambdas capture.

Second thing I thought, that I should find that point of declaration for the lambda capture is at the beginning of the compound statement, but in the

http://eel.is/c++draft/basic.scope.pdecl

I found nothing.

So returning back to the lambdas I found:

http://eel.is/c++draft/expr.prim.lambda#capture-6

specifically:


An init-capture behaves as if it declares and explicitly captures a variable of the form “auto init-capture ;” whose declarative region is the lambda-expression's compound-statement, except that:



 behaves as if it declares


which basically makes it a unicorn.

Question is. Why the lambda capture cannot be the declaration and must be the unicorn instead?

Thanks!
best regards,
Dawid

Richard Smith

unread,
Mar 19, 2019, 4:46:07 AM3/19/19
to std-dis...@isocpp.org
One of the primary goals of init-captures was to enable capture by move:

[x = std::move(x)] { ... }

If that meant a self-move, that'd defeat the purpose (and it'd always
be ill-formed because the init-capture declares an auto-typed
variable). Likewise, there is a desire for an [x] capture to be
syntactic sugar for [x=x] to the extent possible, and for [&x] to be
syntactic sugar for [&x=x] to the extent possible, neither of which
would be true if init-captures were in scope in their own
initializers.

To fix these problems, the init-capture is not in scope in its own initializer.

Dawid Pilarski

unread,
Mar 19, 2019, 5:40:57 AM3/19/19
to std-dis...@isocpp.org
I understand the behavior and motivation, but wouldn't it be the same if

 - [x] was a usual declaration of the x name with autodeduced type
 - the declaration point for the [x] would be the beginning of the compound statement ([x=x] would be fine and would refer to the
  variable from the outer scope), and the declarative region would be the compound statement.

And then the lambda's capture could be a regular declaration and not some "thing".

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.

Balog Pal

unread,
Mar 19, 2019, 9:07:33 AM3/19/19
to ISO C++ Standard - Discussion


2019. március 19., kedd 9:11:41 UTC+1 időpontban picpps...@gmail.com a következőt írta:

recently I was reading about the lambdas in detail and one thing made me wonder, specifically,

why in this case:
{
  int a=42;
  {int a = a; // unknown a value}
}

but in case of the lambda

int x=42;
[x=x]{x; // here x is 42}


The bland answer is certainly "because it is defined that way", but I guess you look after the why behind that.
Note that you are not looking at really similar things. The first declares the object a and initialize that. The name takes effect right on. This is how it is from beginning of C and likely its parent languages.

The lambda is a different beast. It toes not create the object. Instead it is just a recipe to create one. Somewhat similar to 'struct' that does not create an instance. I teach to think of lambda as syntax sugar to make a struct with members for the captures and an operator () with the body part. With implicit capture it just picks names from use. With C++11 explicit captures you could not really change the name, only prevent accidental pickups. So if you had [x], it mapped to

struct _c_ { auto x;  _c_() : x(outer_x);   };  with compiler filling in the blanks.

For C++14 Richard observed that it is quite easy to extend to cover many new use cases: why not allow to supply the name and the init expression. So if you look where the elements go, it should be natural to see that in [x=x] the first delegates the name for internal use and the right is the init expression belonging to the environment.

Note anther interesting case with constructors:

struct S {
  S(int x) : x(x)  // x in scope is the param, but the init list looks only members
  {
     x *= 2;  // doubles the param
    assert( this->x * 2 == x ); // the member is hidden but can be accessed explicitly
  }
int x;
  };

Also note that declaration of x in the struct can appear past its use. it is nothing similar to the declaration of objects.

Dawid Pilarski

unread,
Mar 19, 2019, 9:21:03 AM3/19/19
to std-dis...@isocpp.org
Ok, I get it. lambda capture is not defined as declaration because simply it does not behave like one.

Thank you guys.

Andrey Semashev

unread,
Mar 19, 2019, 9:31:22 AM3/19/19
to std-dis...@isocpp.org
On 3/19/19 4:07 PM, Balog Pal wrote:
>
> The lambda is a different beast. It toes not create the object. Instead
> it is just a recipe to create one. Somewhat similar to 'struct' that
> does not create an instance.

It's shorter syntax to describe a class with operator() and data
members, yes. But it does create an object of that class.

Balog Pal

unread,
Mar 19, 2019, 9:52:56 AM3/19/19
to std-dis...@isocpp.org
Well, sorry for the poor phrasing, I meant the discussed part. Like

struct S { /* content */  } s;

The "content" part is not involved with creation.

Now as we speak of it I'm not even sure if the object creation is
mandatory or not. My best guess is NO by the recent rules.

Just having:

[x = foo() ]{};

where foo() has visible side effects, is it called?

by 7.5.5p2 lambda is a prvalue expression and I would not expect it
materialized without a reason.

If Richard is still lurking he may confirm the theory. :)












Ville Voutilainen

unread,
Mar 19, 2019, 10:12:05 AM3/19/19
to std-dis...@isocpp.org
The theory is disproven by
http://eel.is/c++draft/expr.prim.lambda.capture#15, which specifies
that
the members corresponding to the captures are initialized when the
lambda-expression is evaluated.

Balog Pal

unread,
Mar 19, 2019, 10:56:44 AM3/19/19
to std-dis...@isocpp.org
Hm, I'd expected that "evaluated" state "materialized" instead to fit
the logic as I understand it.

Though right, non-materialization only applies to intermediary copies.
The discarded-value is still materialized at the final point that was
not changed. (6.6.6 p2 2.6)  So behavior-wise it is the same and I was
mistaken in the verdict above.




Reply all
Reply to author
Forward
0 new messages