Comment #0 by qs.il.paperinik — 2020-06-06T19:10:37Z
Relevant code: https://run.dlang.io/is/DAkfjA
Depending on whether line 12 (front ~= [ ];) is commented in, one of the asserts fail. Neither assert should fail.
The code in line 12 does nothing but potentially reallocate an array.
The problem seems not to occur when int[] is replaced by int.
Comment #1 by simen.kjaras — 2020-06-07T22:38:28Z
A shorter piece of code that demonstrates the issue:
import std.stdio : writeln;
struct S {
int[] front = [0];
bool empty() { return front[0] >= 2; }
void popFront() {
front[0]++;
}
}
void main() {
static foreach (e; S()) {
writeln(e); // Writes [2] 2 times
}
}
What happens is somewhat equivalent to this:
// Happens at compile-time:
S tmp;
auto value1 = tmp.front;
tmp.popFront();
auto value2 = tmp.front;
tmp.popFront();
// Happens at run-time:
writeln(value1);
writeln(value2);
Since there is no .dup anywhere to be found, value1 and value2 refer to the same array, so popFront() modifies both. Since all calls to popFront() happen at compile-time, before any of the values are used, only the final values are available. In fact, a deep duplication would be required for the values to be correct, which could cause other issues where you expect changing a value in one step would carry over to the next.
So, static foreach does not deal well with ranges that modify referenced data, and I'm not sure this is an issue that can be properly fixed. static foreach looks like 'please unroll this loop', but is... something else.
Comment #2 by qs.il.paperinik — 2020-06-09T23:38:19Z
I reduced that issue from a much larger code and was happy to push it under 50 lines of code.
Basically, what would have to happen is this:
// At compile-time:
S tmp;
enum value1 = tmp.front.deepcopy; // Note: enum
tmp.popFront();
enum value2 = tmp.front.deppcopy; // Note: enum
tmp.popFront();
// Happens at run-time:
writeln(value1);
writeln(value2);
I cannot imagine a situation where someone relying on a non-deep copy would not accept that this is not how static foreach works. Everything that is not a deep copy is plainly wrong.
Comment #3 by boris2.9 — 2020-06-10T01:33:54Z
Also happens with pragma:
static foreach (e; S()) {
pragma(msg, e);
}
Comment #4 by simen.kjaras — 2020-06-10T13:29:34Z
> Everything that is not a deep copy is plainly wrong.
It seems you're right. I though this code would cause problems, but it's statically disallowed:
static foreach (e; S()) {
// Change an element that will also be present in the next iteration.
e[0]++; // Error: cannot modify constant 2
writeln(e);
}
If the above would compile, it'd be a clear issue. However, it doesn't, so it seems a deep copy should indeed work.
Comment #5 by robert.schadek — 2024-12-13T19:09:05Z