Home > Net >  Why do 'for' and 'for_each' result in different functions being generated by ite
Why do 'for' and 'for_each' result in different functions being generated by ite

Time:09-09

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;
});

CodePudding user response:

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();
  • Related