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?
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):
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With