Iterators in C++ are, essentially, just raw pointers that you hope will point into the memory owned by your container. Modifying that container might break the assumption that those pointers belong to your container, so in C++ we have to worry a lot about iterator invalidation. In Julia, we don’t usually deal with raw pointers, and instead with have a nice iterate
interface that results in, roughly, the behavior of a C++ loop like for (int i = 0; i < v.size(); i++) { do something with v[i]...}
, but without having to keep track of the index variable manually.
So, for example, iterating over a vector while appending to it in Julia is fine:
julia> v = [1, 2, 3]
f3-element Array{Int64,1}:
1
2
3
julia> for x in v
@show x
if x == 1
push!(v, 4)
end
end
x = 1
x = 2
x = 3
x = 4
because the code expands to:
julia> let
next = iterate(v)
while next !== nothing
x, state = next
@show x
if x == 1
push!(v, 4)
end
next = iterate(v, state)
end
end
x = 1
x = 2
x = 3
x = 4
which still checks (by checking if the result of iterate(v, state)
is nothing
) if we’ve reached the end of v
, even after modification of v
. In essence, any questions about what is safe or valid to do with an iterator in Julia should be answerable by falling back to that while
loop construct, as documented in Interfaces · The Julia Language
The equivalent C++ code, on the other hand, produces garbage because modifying v
causes the iterators to silently start pointing to completely invalid memory:
C++ > #include <iostream>
true
C++ > std::vector<int> v = {1, 2, 3};
C++ > for (auto iter = v.begin(); iter != v.end(); ++iter) {
std::cout << "x: " << *iter << std::endl;
if (*iter == 1) {
v.push_back(4); // oops, `iter` now points to garbage. All bets are off.
}
}
x: 1
x: 0
x: 3
x: 0
x: 956330853
x: 13616
x: 513
x: 0
x: 2020515472