Search code examples
c++c++11lambdalanguage-lawyer

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?


Solution

  • 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...

    Since C++14 you can 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.