Why do 'for' and 'for_each' result in different functions being generated by iterating through array elements using lambdas?

Abhishek Shivakumar

I'm trying to better understand the interactions of lambda expressions and iterators.

What is the difference between these three snippets of code? onSelect is an std::function that is called when a component is selected.

Example 1 and 3 seem to work quite nicely. Example 2 returns the same index value, regardless of the component clicked.

My intuition is that Example 2 only results in one symbol being generated, and therefore the function only points to the first value. My question is, why would for_each result in multiple function definitions being generated, and not the normal for loop?

components[0].onSelect = [&]{ cout<<0; };
components[1].onSelect = [&]{ cout<<1; };
components[2].onSelect = [&]{ cout<<2; };
components[3].onSelect = [&]{ cout<<3; };
//And so on

vs

for (int i = 0; i < numComponents; ++i)
{
    components[i].onSelect = [&]
    {
        cout<<components[i];
    };
}

vs

int i = 0;
std::for_each (std::begin (components), std::end (components), [&](auto& component)
{
    component.onSelect = [&]{
        cout<<i;
});
Useless

What is the difference between these three snippets of code?

Well, only the first one is legal.

My intuition is that Example 2 only results in one symbol being generated

Each lambda expression generates a unique unnamed class type in the smallest enclosing scope. You have one block scope (inside the for loop) and one lambda expression so yes, there's only one type.

Each instance of that type (one per iteration) could differ in state, because they could all capture different values of i. They don't, though, they all capture exactly the same lexical scope by reference.

and therefore the function only points to the first value

A lambda expression is always a class type, not a function. A lambda expression with an empty capture is convertible to a free function pointer - but you don't have an empty capture. Finally, the lambda didn't capture only the first value - or any value - but an unusable reference to the variable i. Because you explicitly asked to capture by reference ([&]) instead of value.

That is, they all get the same reference to i whatever its particular value at the time they're instantiated, and i will have been set to numComponents and then gone out of scope before any of them can be invoked. So, even if it hadn't gone out of scope, referring to components[i] would almost certainly be Undefined Behaviour. But as it has gone out of scope, it is a dangling reference. This is an impressive density of bugs in a small amount of code.

Compare the minimal change:

for (int i = 0; i < numComponents; ++i) {
    components[i].onSelect = [i, &components]
    {
        cout<<components[i];
    };
}

which captures i by value, which is presumably what you really wanted, and only takes components by reference. This works correctly with no UB.

My question is, why would for_each result in multiple function definitions being generated, and not the normal for loop?

You have two nested lambda expressions in example 3, but we're only concerned with the inner one. That's still a single lambda expression in a single scope, so it's only generating one class type. The main difference is that the i to which it has (again) captured a reference, has presumably not gone out of scope by the time you try calling the lambda.

For example, if you actually wrote (and a minimal reproducible example would have shown this explicitly)

int i = 0;
std::for_each (std::begin (components), std::end (components), [&](auto& component)
{
    component.onSelect = [&]{
        cout<<i;
});
for (i = 0; i < numComponents; ++i)
  components[i].onSelect();

then the reason it would appear to work is that i happens to hold the expected value whenever you call the lambda. Each copy of it still has a reference to the same local variable i though. You can demonstrate this by simply writing something like:

int i = 0;
std::for_each (std::begin (components), std::end (components), [&](auto& component)
{
    component.onSelect = [&]{
        cout<<i;
});
components[0].onSelect();
components[1].onSelect();
i = 2;
components[1].onSelect();

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Iterating through a list while already using for_each (Terraform 0.12)

Iterating through an array of objects using for each or for in

Iterating through data-* elements using JQuery $.children() and $.each()?

iterating through list and using each two elements as function arguments

Cypress - iterating through same elements with different values?

iterating through elements in different index positions

Going through array using for_each loop in cpp

Iterating through array are not give proper result

Iterating a Numpy array through arithmetic functions

Iterating through a JSON array and returning a subset of elements

Why iterating through an array like this is inefficient in C?

Iterating through elements get repeating result on Selenium on Python

How do you subtract an arrays elements from a value while iterating through each element?

(Conceptual) Iterating through an array of ints using a pointer

Iterating through vue array using index

Why do array keys change when iterating through them for the same type?

Iterating through BTreeMap using range() - question on the type being iterated over

Iterating through li elements

Foreach not iterating through elements

Iterating through elements of a matrix

Why isn't this iterating through a json dataset and pushing data into an array using GAS?

How to unpack the result when iterating through cell array?

Why does using a joins and includes with find_each result in an incorrect iterating count

Why is this code not iterating through all dict's elements?

Iterating through list and using remove() doesn't produce desired result

Why do I see a different result when using size() or count()?

Skipping every M elements when iterating through an array in CUDA

Is it possible to select certain elements without iterating through the array?

Sorting List of Arrays by different elements using Java 8 lambdas