how is this array subtyping inside struct (bug?) possible?
Steven Schveighoffer
schveiguy at gmail.com
Mon Aug 10 19:52:19 UTC 2020
On 8/10/20 3:39 PM, mw wrote:
> On Monday, 10 August 2020 at 19:30:18 UTC, Steven Schveighoffer wrote:
>> On 8/10/20 2:36 PM, mw wrote:
>>> On Monday, 10 August 2020 at 18:29:56 UTC, mw wrote:
>>>> and where in this doc on range:
>>>>
>>>> https://tour.dlang.org/tour/en/basics/ranges
>>>>
>>>> it is mentioned that: the length property of the range will be
>>>> changed after the range is "consumed" by foreach loop?
>>>>
>>>> ```
>>>> foreach (element; range)
>>>> {
>>>> // Loop body...
>>>> }
>>>> it's internally rewritten similar to the following:
>>>>
>>>> for (auto __rangeCopy = range;
>>>> !__rangeCopy.empty;
>>>> __rangeCopy.popFront())
>>>> {
>>>> auto element = __rangeCopy.front;
>>>> // Loop body...
>>>> }
>>>> ```
>>>
>>> And wait, ... did I saw "__rangeCopy" here? it should be a *copy*?!
>>>
>>>
>>
>> In your code, the type of "range" is SharedArray!(string) which is a
>> class or *reference type*.
>>
>> So let's follow along what happens:
>>
>> SharedArray!(string) fns;
>> for(auto __rangeCopy = fns;
>>
>> // the above makes a copy of a *class reference*, which means it does
>> not make a copy of the *array data* or even the array reference. It's
>> like copying a pointer to the array.
>>
>> !__rangeCopy.empty;
>>
>> // SharedArray(T) does not have empty member, so this forwards to the
>> array, it's like saying:
>> // !__rangeCopy.array.empty
>>
>> __rangeCopy.popFront())
>>
>> // This is equivalent to __rangeCopy.array.popFront, which alters the
>> array inside the ONE SHARED class instance.
>
>
> This still doesn't explain why the underlying array.length is modified
> after the range to consumed; too much black magic is happening here.
Indeed there is a lot of magic. It's ordinary every-day D magic though ;)
string[] arr = ["abc"];
arr.popFront; // ufcs function defined in std.range.primitives
assert(arr.length == 0); // reduces the length
If we inlined this fully with the definition of
std.range.primitives.popFront, the line:
__rangeCopy.popFront()
is really doing:
__rangeCopy.array = __rangeCopy.array[1 .. $];
This is how you iterate an array as a range.
>> How to fix? extract the array before iterating:
>>
>> writeln(s0.fns.array);
>
> This defeats the purpose, i.e. the convenience that subtyping mechanism
> supposed to provide.
You are subtyping but inadvertently have turned a forward range (array)
into an input range (iterate only once) by changing it into a class.
forward ranges aren't supposed to technically be valid if you don't
*save* them, but in practice it's totally fine for arrays.
What might work (and I haven't tried this) is to @disable front,
popFront, empty, and I think what will then happen is the compiler will
try slicing instead (which should work) and iterate a copy of the array.
e.g.:
class SharedArray!T
{
T[] array;
alias array this;
@disable:
front();
popFront();
empty();
}
-Steve
More information about the Digitalmars-d
mailing list