Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambda capture list: capturing object's member field by value not possible without capturing the whole object?

The following code

void CMainWindow::someMethod(const CLocationsCollection& parentItem)
{
    auto f = [this, parentItem.displayName](){};
}

gives me an error:

error C2143: syntax error : missing ']' before '.'

If I wanted to catch parentItem.displayName by ref, I'd make a non-dependent alias identifier for it:

const QString& name = parentItem.displayName;
auto f = [this, &name](){}; // Or should it be [this, name] ?

But I need to capture it by value, and I don't want to capture the whole parentItem because it's heavy. Any solution?

P. S. Names in the capture list must be identifiers. Isn't parentItem.displayName (as a whole) an identifier? Why can't it be parsed properly by the compiler?

like image 672
Violet Giraffe Avatar asked Jun 14 '14 14:06

Violet Giraffe


People also ask

How do you capture a member variable in lambda?

To capture the member variables inside lambda function, capture the “this” pointer by value i.e. std::for_each(vec. begin(), vec. end(), [this](int element){ //.... }

Does lambda capture reference by value?

The mutable keyword is used so that the body of the lambda expression can modify its copies of the external variables x and y , which the lambda expression captures by value. Because the lambda expression captures the original variables x and y by value, their values remain 1 after the lambda executes.

What is a lambda capture list?

The capture list defines what from the outside of the lambda should be available inside the function body and how. It can be either: a value: [x] a reference [&x] any variable currently in scope by reference [&]

What is capture in lambda function?

Captures default to const value. By default, variables are captured by const value . This means when the lambda is created, the lambda captures a constant copy of the outer scope variable, which means that the lambda is not allowed to modify them.


1 Answers

Note: All standard references in this post are taken from the C++ Standard Draft n3337.


Introduction

The standard states that a capture must be &, =, this, an identifier, or an identifier preceeded by &.

5.1.2 Lambda expressions [expr.prim.lambda]

capture-list:
  capture ..._opt
  capture-list , capture ..._opt

capture:
  identifier
  & identifier
  this

Since parentItem.displayName is not an identifier, but a "class member access expression". the compiler is correct when rejecting your snippet.

5.2.5p1 Class member access [expr.ref]

A postfix expression followed by a dot . or an arrow ->, optionally follow by the keyword template (14.2), and then followed by an id-expression, is a postfix expression. The postfix expression before the dot or arrow is evaluated;66 the result of that evaluation, together with the id-expression, determines the result of the entire postfix expression.


If only there was a way to initialize captures with expressions...

In C++14 you will be able to use an init-capture to circumvent the issue at hand, looking as the snippet below. It will create a capture with the name display_name, initialized with the value of parentItem.displayName.

[display_name = parentItem.displayName](){ ... };

What about C++11?

Sadly such a feature is not available in C++11. The solution to the problem is therefore to make a local reference to the the data-member, and then capture that reference when creating the lambda.

auto& lmb_display_name = parentItem.displayName;

[lmb_display_name](){ ... }; // the lambda will have a copy of `parentItem.displayName`


Note: Your original post seems to imply that the name of a reference R in a lambda's capture-list would make the lambda contain a reference to that which R refers to, something which isn't true, as can be seen by this snippet.

like image 199
Filip Roséen - refp Avatar answered Oct 21 '22 19:10

Filip Roséen - refp