badstoms
badstoms

Reputation: 217

When is a variable reference in a C++11 lambda expression resolved?

I have a (hopefully) simple question about lambda expressions:

#include <vector>
#include <algorithm>
//----------------------------------------------------------------
void DoSomething()
//----------------------------------------------------------------
{
  std::vector<int> elements;
  elements.push_back(1);
  elements.push_back(2);

  int ref = 1;
  auto printhit = [=](int iSomeNumber)
  {
    if (ref == iSomeNumber)
    {
      printf("Hit: %d\n", iSomeNumber);
    }
    else
    {
      printf("No Hit: %d\n", iSomeNumber);
    }
  };

  ref = 2;
  std::for_each(elements.begin(), elements.end(), printhit);   
}

Now, my question is: When I define printhit with capture [=], it prints "Hit: 1". If I pass it by reference [&], it prints "Hit: 2". I somehow expected, that the substitution is done within for_each, so that "Hit: 2" is printed no matter how I grant access to "ref".

Can anyone explain this to me?

Thanks, Markus

Upvotes: 2

Views: 199

Answers (3)

Tony Delroy
Tony Delroy

Reputation: 106116

What would be the point of having them both operate the same way? The point of [=] is to support capture by copy instead of by reference.

Imagine if [=] wasn't available: if you know a runtime value at the point in the code where the lambda's defined and want the lambda to use it ever after, how could that value be made available to the lambda code? While DoSomething() is running by-ref [&] access to its local ref variable might serve, but what if you want to have the lambda's lifetime outlive the local scope in DoSomething() that contains it, or want to change the value of ref without that affecting future calls to the lambda? Conceptually, you could have the language forbid all these things (use after ref is changed or changes to ref or calls of the lambda after ref is changed or out of scope), or the programmer could go to elaborate lengths to put the value of ref somewhere for the lambda to use (e.g. on the heap, with the need to manage deallocation, or in some static buffer with re-entrance and thread-safety issues), but to make it convenient the language provides [=]. The compiler-generated lambda effectively takes responsibility for storing and destructing/deallocating the copy of ref.

Upvotes: 1

Spook
Spook

Reputation: 25927

I guess, that the following parts of the C++ standard apply:

5.1.2.14:
An entity is captured by copy if it is implicitly captured and the capture-default is = or if it is explicitly captured with a capture that does not include an &. For each entity captured by copy, an unnamed nonstatic data member is declared in the closure type. The declaration order of these members is unspecified. The type of such a data member is the type of the corresponding captured entity if the entity is not a reference to an object, or the referenced type otherwise. [ Note: If the captured entity is a reference to a function, the corresponding data member is also a reference to a function. —end note ]
5.1.2.21:
When the lambda-expression is evaluated, the entities that are captured by copy are used to direct-initialize each corresponding non-static data member of the resulting closure object. (For array members, the array elements are direct-initialized in increasing subscript order.) These initializations are performed in the (unspecified) order in which the non-static data members are declared. [ Note: This ensures that the destructions will occur in the reverse order of the constructions. —end note ]

Upvotes: 1

Benjamin Lindley
Benjamin Lindley

Reputation: 103713

The capture happens at the location where you declare the lambda. Just like if you were to create a class object at that point and pass ref to its constructor.

Your example is equivalent to this:

class Functor
{
public:
    Functor(int r) :ref(r) {}

    void operator()(int iSomeNumber) const
    {
        if (ref == iSomeNumber)
        {
            printf("Hit: %d\n", iSomeNumber);
        }
        else
        {
            printf("No Hit: %d\n", iSomeNumber);
        }    
    }
private:
    int ref;
};

void DoSomething()
//----------------------------------------------------------------
{
  std::vector<int> elements;
  elements.push_back(1);
  elements.push_back(2);

  int ref = 1;
  Functor printhit(ref);

  ref = 2;
  std::for_each(elements.begin(), elements.end(), printhit);   
}

Upvotes: 5

Related Questions