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