On 9/27/20 4:43 PM, Per Nordlöw wrote:
On Sunday, 27 September 2020 at 14:23:11 UTC, H. S. Teoh wrote:
No.  Modifying a container while iterating over it is, in general, a bad idea (unless the container is designed to be used that way, but even then, such removal is generally restricted), because it often leads to highly counterintuitive results.  In the case of AA's, removing an element may lead to a rehashing, which reorders elements, and your iteration may miss some elements or repeat some elements or terminate prematurely. Even without a rehashing, you may encounter inconsistent behaviours, like some elements going "missing".

I believe it's high time we start thinking about detecting these violations at compile-time. I recall it's in the spec somewhere so we should start a deprecation process at least for AAs.

So the major problem of removing keys while iterating is that the AA can decide to rehash and shrink. If it does this, your iterator is invalidated -- you might skip elements that are actually in the AA, and you might encounter elements that have already been iterated. Essentially, the ordering has completely been shuffled. Aside from that, there's the performance aspect that you have to re-run the hash lookup for no reason (you literally have a pointer to the element you are iterating).

How do you "detect" this at compile time?

One could write a specific function to iterate and remove. I did it for dcollections (this was actually a very important functionality that I missed from C++ std::map, which is why I created dcollections in the first place).

I don't think it's worth worrying about this at compile time, or even at runtime. Just identify that if you do it, you are subject to peculiarities.

Then, we can introduce a specific mechanism to do this (total strawman, have no idea what the best thing looks like):

auto r = aa.byKeyValuePurging;
while(!r.empty)
{
   if(shouldPurge(r.front.key, r.front.value)) r.popFrontAndDelete;
   else r.popFront;
}

where popFrontAndDelete will move to the next element, remove the prior element, and ensure that a rehash does not occur.

Again, this is not a formal proposal. Just something like this could be possible.

In dcollections, I used a trick of opApply to provide the boolean of whether to remove as a parameter to foreach:

foreach(k, v, ref doPurge; &container.purge)
   doPurge = shouldPurge(k, v);

-Steve

Reply via email to