Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::list and std::for_each: where is my end?

Consider the following minimal example:

#include <functional>
#include <algorithm>
#include <list>

int main() {
    std::list<std::function<void()>> list;
    list.push_back([&list](){ list.push_back([](){ throw; }); });
    std::for_each(list.cbegin(), list.cend(), [](auto &&f) { f(); });
}

It compiles and throws an exception at run-time.
My guess was that only the first lambda is executed by the std::for_each, but apparently I was wrong: if I append another lambda at the end of the list, the iteration reaches also that lambda.

Let's revert the example (push_front instead of push_back and crbegin/crend instead of cbegin/cend):

#include <functional>
#include <algorithm>
#include <list>

int main() {
    std::list<std::function<void()>> list;
    list.push_front([&list](){ list.push_front([](){ throw; }); });
    std::for_each(list.crbegin(), list.crend(), [](auto &&f) { f(); });
}

Because of the previous example, I expected this to compile and crash as well.
Instead it compiles and doesn't crash. This time, the function pushed to the front of the list is not executed.

The question is quite simple: is this correct?
Why are so counterintuitive the two examples?

In the first case I was expecting something different and I was wrong, that's not a problem.
Anyway, I would have expected coherence between the two loops. I mean, the second function is executed in one case and it is not executed in the other case, but I'm iterating from a begin to an end in both cases.
What's wrong in my reasoning?

like image 209
skypjack Avatar asked Nov 21 '16 13:11

skypjack


1 Answers

To be honest, the results you get seem to be what I expected. Let's walk through you first example:

1.

list.push_back([&list](){ list.push_back([](){ throw; }); });

List state:

+-- list.begin() (not necessarily what has been passed to for_each)
v
[lambda]----[end]

2​. start iterating over the list

Iteration 1:

List state:

+-- list.begin() (not necessarily what has been passed to for_each)
v
[lambda]----[end]
^
+-- current

f() calls list.push_back([](){ throw; });

List state:

+-- list.begin() (not necessarily what has been passed to for_each)
v
[lambda]----[inner_lambda]----[end]
^
+-- current

Iteration 2: (++current)

List state:

+-- list.begin() (not necessarily what has been passed to for_each)
v
[lambda]----[inner_lambda]----[end]
            ^
            +-- current

f() calls throw;

end



Now let's do the other direction.

First of all, look at how reverse iterators are actually represented - this is important (image from cppreference): reverse iterators

The important part is: reverse end points to normal begin. But the problem is, with a list, one can insert something before begin, but it's not possible after end. That invariant is broken with reverse iterators.

1.

list.push_front([&list](){ list.push_front([](){ throw; }); });

List state:

+-- list.begin() (not necessarily what has been passed to for_each)
|
|+-- list.rend().base()
||
||          +-- list.rbegin().base()
vv          v
[lambda]----[end]

2​. start iterating over the list

Iteration 1:

List state:

+-- list.begin() (not necessarily what has been passed to for_each)
|
|+-- list.rend().base()
||
||          +-- list.rbegin().base()
vv          v
[lambda]----[end]
    ^           ^
    |           +---- current
    |
    +--------- passed list.rend()

*current yields [lambda].

f() calls list.push_front([](){ throw; });

List state:

+-- list.begin() (not necessarily what has been passed to for_each)
|
|+-- list.rend().base()
||
||                            +-- list.rbegin().base()
vv                            v
[inner_lambda]----[lambda]----[end]
                      ^           ^
                      |           +---- current
                      |
                      +--------- passed list.rend().base()

Note that the passed list.rend().base() has not changed - but it doesn't point to the first (past the last reversed) element anymore.

Iteration 2: (++current)

+-- list.begin() (not necessarily what has been passed to for_each)
|
|+-- list.rend().base()
||
||                            +-- list.rbegin().base()
vv                            v
[inner_lambda]----[lambda]----[end]
                      ^  ^
                      |  +---- current
                      |
                      +--------- passed list.rend().base()

current == passed list.rend().base()

the end



Now let's try the other one by my mistake this part is relevant for forward iterating over the list:

1.

list.push_front([&list](){ list.push_front([](){ throw; }); });

List state:

+-- list.begin() (not necessarily what has been passed to for_each)
v
[lambda]----[end]

2​. start iterating over the list

Iteration 1:

List state:

+-- list.begin() (not necessarily what has been passed to for_each)
v
[lambda]----[end]
^
+-- current

f() calls list.push_front([](){ throw; });

Iterator to current is not invalidated and/or made to point somewhere else than it already was pointing.

List state:

+-- list.begin() (not necessarily what has been passed to for_each)
v
[inner_lambda]----[lambda]----[end]
                  ^
                  +-- current

Iteration 2: (++current)

List state:

+-- list.begin() (not necessarily what has been passed to for_each)
v
[inner_lambda]----[lambda]----[end]
                              ^
                              +-- current

end

like image 155
krzaq Avatar answered Nov 18 '22 20:11

krzaq