www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - how is this array subtyping inside struct (bug?) possible?

reply mw <mingwu gmail.com> writes:
Hi,

I want to share an array among a number of structs, with 
subtyping, I tried this, and find some strange behavior:

```
class SharedArray(T) {
   public T[] array;
   alias array this;  // subtyping
}

alias Filenames = SharedArray!(string);

struct S {
   Filenames fns;
   void alloc() {
     fns = new Filenames();
   }
}

void main(string[] args) {
   S s0;
   s0.alloc();
   s0.fns ~= "abc";

   foreach (i; 0..3) {
     S* s1 = new S();
     *s1 = s0;   //  copy the value from s0 to *s1
     writeln(s0.fns);
   }
}
```

program output:
```
["abc"]
[]
[]
```

why s0.fns changed after copy the value from s0 to *s1?

Is this a bug?
Aug 09 2020
parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 9 August 2020 at 18:45:07 UTC, mw wrote:
 Hi,

 I want to share an array among a number of structs, with 
 subtyping, I tried this, and find some strange behavior:

 ```
 class SharedArray(T) {
   public T[] array;
   alias array this;  // subtyping
 }

 alias Filenames = SharedArray!(string);

 struct S {
   Filenames fns;
   void alloc() {
     fns = new Filenames();
   }
 }

 void main(string[] args) {
   S s0;
   s0.alloc();
   s0.fns ~= "abc";

   foreach (i; 0..3) {
     S* s1 = new S();
     *s1 = s0;   //  copy the value from s0 to *s1
     writeln(s0.fns);
   }
 }
 ```

 program output:
 ```
 ["abc"]
 []
 []
 ```

 why s0.fns changed after copy the value from s0 to *s1?

 Is this a bug?
No, it's not a bug, it's just a weird quirk of how `alias this` interacts with reference types like classes. When you pass a range to `writeln`, it will iterate the range using `front` and `popFront` in order to print each of the elements. Doing this consumes the range. Normally, that's not a problem, because writeln takes its arguments by value, so any range you pass to it will be copied, and only the copy will be consumed. However, because you've made your `Filenames` class into an input range using `alias this`, and classes are reference types, consuming a copy of the range also consumes the original. The solution is to use the `save` function from `std.range` to create an independent copy of the range for `writeln` to iterate: import std.range; writeln(s0.fns.save);
Aug 09 2020
parent reply mw <mingwu gmail.com> writes:
On Sunday, 9 August 2020 at 20:30:35 UTC, Paul Backus wrote:

 Normally, that's not a problem, because writeln takes its 
 arguments by value, so any range you pass to it will be copied, 
 and only the copy will be consumed. However, because you've 
 made your `Filenames` class into an input range using `alias 
 this`, and classes are reference types, consuming a copy of the 
 range also consumes the original.
I also tried this: ``` foreach (i; 0..3) { S* s1 = new S(); *s1 = s0; writeln(s0.fns); writeln(s0.fns.array.length); // *directly* access array } ``` The output is: ``` ["abc"] 0 [] 0 [] 0 ``` I'm *directly* access the underlying array, so why its length changed to 0 after writeln? So a plain array *is* also a range? I've thought (an array's) range is a *separate* struct (which wrap the original array). The wrapper range can be consumed, but the underlying array should stay the same. I never seen it's mentioned in the doc: https://dlang.org/spec/arrays.html
Aug 09 2020
parent reply RazvanN <razvan.nitu1305 gmail.com> writes:
On Sunday, 9 August 2020 at 21:12:58 UTC, mw wrote:

 I'm *directly* access the underlying array, so why its length 
 changed to 0 after writeln?
You are accessing the underlying array after it was consumed. The line writeln(s0.fns) passes class, if you want to pass the underlying array you should type `writeln(so.fns.array)` and then it will not consume the array.
 So a plain array *is* also a range? I've thought (an array's) 
 range is a *separate* struct (which wrap the original array). 
 The wrapper range can be consumed, but the underlying array 
 should stay the same.

 I never seen it's mentioned in the doc:

 https://dlang.org/spec/arrays.html
You had a type in your code.
Aug 09 2020
parent reply mw <mingwu gmail.com> writes:
On Monday, 10 August 2020 at 02:38:55 UTC, RazvanN wrote:
 On Sunday, 9 August 2020 at 21:12:58 UTC, mw wrote:

 I'm *directly* access the underlying array, so why its length 
 changed to 0 after writeln?
You are accessing the underlying array after it was consumed. The line writeln(s0.fns) passes class, if you want to pass the underlying array you should type `writeln(so.fns.array)` and then it will not consume the array.
I know that; and that's exactly I call it a subtyping bug: i.e. directly access & access via subtyping behave differently: ``` foreach (i; 0..3) { S* s1 = new S(); *s1 = s0; // copy the value from s0 to *s1 writeln(s0.fns.array); writeln(s0.fns.array.length); //*directly* access the underlying array } ``` output: ``` ["abc"] 1 ["abc"] 1 ["abc"] 1 ``` via subtyping: ``` foreach (i; 0..3) { S* s1 = new S(); *s1 = s0; // copy the value from s0 to *s1 writeln(s0.fns); writeln(s0.fns.length); } ``` output: ``` ["abc"] 0 [] 0 [] 0 ``` And the direct access is the expected behavior that the subtyping mechanism want to provide a convenience for. Since it introduce a surprise to the user, I'd call that a bug.
 So a plain array *is* also a range? I've thought (an array's) 
 range is a *separate* struct (which wrap the original array). 
 The wrapper range can be consumed, but the underlying array 
 should stay the same.

 I never seen it's mentioned in the doc:

 https://dlang.org/spec/arrays.html
You had a type in your code.
What do you mean? can you elaborate?
Aug 09 2020
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 10 August 2020 at 02:50:20 UTC, mw wrote:
 On Monday, 10 August 2020 at 02:38:55 UTC, RazvanN wrote:
 On Sunday, 9 August 2020 at 21:12:58 UTC, mw wrote:

 I'm *directly* access the underlying array, so why its length 
 changed to 0 after writeln?
You are accessing the underlying array after it was consumed. The line writeln(s0.fns) passes class, if you want to pass the underlying array you should type `writeln(so.fns.array)` and then it will not consume the array.
I know that; and that's exactly I call it a subtyping bug: i.e. directly access & access via subtyping behave differently:
You are subtyping a value type (array) with a reference type (class). So when you directly access it, it's passed by value, but when you pass the subtype, it's passed by reference. In other words, using `alias this` does not create a *true* subtype (according to the Liskov substitution principle), because it allows you to change the way the type is copied from by-value to by-reference.
Aug 10 2020
next sibling parent reply Avrina <avrina12309412342 gmail.com> writes:
On Monday, 10 August 2020 at 12:03:06 UTC, Paul Backus wrote:
 On Monday, 10 August 2020 at 02:50:20 UTC, mw wrote:
 On Monday, 10 August 2020 at 02:38:55 UTC, RazvanN wrote:
 On Sunday, 9 August 2020 at 21:12:58 UTC, mw wrote:

 I'm *directly* access the underlying array, so why its 
 length changed to 0 after writeln?
You are accessing the underlying array after it was consumed. The line writeln(s0.fns) passes class, if you want to pass the underlying array you should type `writeln(so.fns.array)` and then it will not consume the array.
I know that; and that's exactly I call it a subtyping bug: i.e. directly access & access via subtyping behave differently:
You are subtyping a value type (array) with a reference type (class). So when you directly access it, it's passed by value, but when you pass the subtype, it's passed by reference. In other words, using `alias this` does not create a *true* subtype (according to the Liskov substitution principle), because it allows you to change the way the type is copied from by-value to by-reference.
This really doesn't excuse why this bug is happening. Why is writeln() using front() and popFront() that modifies the range rather than simply using foreach()? Why is it modifying something it knows to be a class? This is definitely a bug that should be fixed. Not sure why people are trying to explain it away, looking that the writeln implementation it is completely convoluted. It's no wonder bugs like this are going to happen.
Aug 10 2020
next sibling parent reply Kagamin <spam here.lot> writes:
On Monday, 10 August 2020 at 13:44:17 UTC, Avrina wrote:
 This really doesn't excuse why this bug is happening. Why is 
 writeln() using front() and popFront() that modifies the range 
 rather than simply using foreach()? Why is it modifying 
 something it knows to be a class? This is definitely a bug that 
 should be fixed. Not sure why people are trying to explain it 
 away, looking that the writeln implementation it is completely 
 convoluted. It's no wonder bugs like this are going to happen.
When writeln receives a reference type range, are you sure it shouldn't be consumed? How do you decide that?
Aug 10 2020
parent mw <mingwu gmail.com> writes:
On Monday, 10 August 2020 at 15:36:35 UTC, Kagamin wrote:
 On Monday, 10 August 2020 at 13:44:17 UTC, Avrina wrote:
 This really doesn't excuse why this bug is happening. Why is 
 writeln() using front() and popFront() that modifies the range 
 rather than simply using foreach()? Why is it modifying 
 something it knows to be a class? This is definitely a bug 
 that should be fixed. Not sure why people are trying to 
 explain it away, looking that the writeln implementation it is 
 completely convoluted. It's no wonder bugs like this are going 
 to happen.
When writeln receives a reference type range, are you sure it shouldn't be consumed? How do you decide that?
Because `writeln` should be a *view* (i.e read-only) function, it shouldn't *internally* call anything thing that change the data passed in. Only the user can *explicitly* change the data, and let writeln print it: e.g. ``` writeln(data.change_And_Print_The_State_After_The_Change()); ```
Aug 10 2020
prev sibling parent Paul Backus <snarwin gmail.com> writes:
On Monday, 10 August 2020 at 13:44:17 UTC, Avrina wrote:
 This really doesn't excuse why this bug is happening. Why is 
 writeln() using front() and popFront() that modifies the range 
 rather than simply using foreach()?
foreach over a range also uses front() and popFront().
 Why is it modifying something it knows to be a class? This is 
 definitely a bug that should be fixed.
The behavior of writeln in this case is documented under `std.format.formatValue` [1], which is used by `writeln` internally:
 For the class objects which have input range interface,

    * If the instance toString has overridden Object.toString, 
 it is used.
    * Otherwise, the objects are formatted as input range.
So everything is working as intended. However, there is certainly a documentation issue here, since it is not at all obvious from the `writeln` docs that it uses `formatValue`. [1] http://dpldocs.info/experimental-docs/std.format.formatValue.html
Aug 10 2020
prev sibling parent reply mw <mingwu gmail.com> writes:
On Monday, 10 August 2020 at 12:03:06 UTC, Paul Backus wrote:
 On Monday, 10 August 2020 at 02:50:20 UTC, mw wrote:
 On Monday, 10 August 2020 at 02:38:55 UTC, RazvanN wrote:
 On Sunday, 9 August 2020 at 21:12:58 UTC, mw wrote:

 I'm *directly* access the underlying array, so why its 
 length changed to 0 after writeln?
You are accessing the underlying array after it was consumed. The line writeln(s0.fns) passes class, if you want to pass the underlying array you should type `writeln(so.fns.array)` and then it will not consume the array.
I know that; and that's exactly I call it a subtyping bug: i.e. directly access & access via subtyping behave differently:
You are subtyping a value type (array) with a reference type (class). So when you directly access it, it's passed by value, but when you pass the subtype, it's passed by reference. In other words, using `alias this` does not create a *true* subtype (according to the Liskov substitution principle), because it allows you to change the way the type is copied from by-value to by-reference.
https://dlang.org/spec/arrays.html#dynamic-arrays "Dynamic arrays consist of a length and a pointer to the array data." I've thought, D's dynamic array is implemented as: ``` /* this is C code */ struct { T* ptr; size_t length; } ``` And now, it shows the length is changeable via its range interface: so where on this range doc page this behavior on length is mentioned? https://dlang.org/phobos/std_range_primitives.html "It defines the bidirectional and forward range primitives for arrays: empty, front, back, popFront, popBack and save." 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... } ```
Aug 10 2020
parent reply mw <mingwu gmail.com> writes:
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*?!
Aug 10 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
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. So now, what ends up happening is because fns and the __rangeCopy are actually just copies of a pointer, or class reference, when you iterate one, the other is iterated. How to fix? extract the array before iterating: writeln(s0.fns.array); This will make a copy of the array reference itself, and iterate that. This is precisely why classes should never be ranges. -Steve
Aug 10 2020
next sibling parent reply mw <mingwu gmail.com> writes:
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.
 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.
Aug 10 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
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
Aug 10 2020
next sibling parent reply mw <mingwu gmail.com> writes:
 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.
This subtyping loophole should be fixed by the compiler.
 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();
 }
This does not work: ``` Error: no identifier for declarator front() Error: function declaration without return type. (Note that constructors are always named this) Error: no identifier for declarator popFront() Error: function declaration without return type. (Note that constructors are always named this) Error: no identifier for declarator empty() ``` looks like ` disable` expect the full function implementation with body: https://dlang.org/spec/attribute.html#disable BTW, this way of (thinking) fixing the problem is one step closer to Eiffel's mechanism: i.e. member level tailored inheritance. Sigh, this is not done earlier into the language.
Aug 10 2020
next sibling parent reply kinke <noone nowhere.com> writes:
On Monday, 10 August 2020 at 20:13:42 UTC, mw wrote:
 class SharedArray!T
 {
    T[] array;
    alias array this;

     disable:
       front();
       popFront();
       empty();
 }
This does not work:
This does and prints the desired output: class SharedArray(T) { T[] array; alias array this; final disable: T front(); void popFront(); bool empty(); } https://run.dlang.io/is/5ciCue
Aug 10 2020
parent reply mw <mingwu gmail.com> writes:
On Monday, 10 August 2020 at 20:34:33 UTC, kinke wrote:
 This does not work:
This does and prints the desired output: class SharedArray(T) { T[] array; alias array this; final disable: T front(); void popFront(); bool empty(); } https://run.dlang.io/is/5ciCue
Thanks. I will accept this as the proper work-around.
Aug 10 2020
parent Avrina <avrina12309412342 gmail.com> writes:
On Monday, 10 August 2020 at 21:15:58 UTC, mw wrote:
 On Monday, 10 August 2020 at 20:34:33 UTC, kinke wrote:
 This does not work:
This does and prints the desired output: class SharedArray(T) { T[] array; alias array this; final disable: T front(); void popFront(); bool empty(); } https://run.dlang.io/is/5ciCue
Thanks. I will accept this as the proper work-around.
There really shouldn't be a workaround, this just outlines that there's an ordering problem in writeln. It should be using front/popfront as a last resort if it isn't able to use another (better) method.
Aug 11 2020
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 8/10/20 4:13 PM, mw wrote:
 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.
This subtyping loophole should be fixed by the compiler.
No, it is doing exactly what you asked it to do -- turn a non-reference type into a full reference type. The fault here is Phobos for accepting classes as ranges (range classes are IMO an abomination that should never be used).
 
 
 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();
 }
This does not work: ```  Error: no identifier for declarator front()  Error: function declaration without return type. (Note that constructors are always named this)  Error: no identifier for declarator popFront()  Error: function declaration without return type. (Note that constructors are always named this)  Error: no identifier for declarator empty() ``` looks like ` disable` expect the full function implementation with body:
Ugh, no, I just forgot to put in the types: disable: T front(); void popFront(); bool empty(); And I figured I'd try it out rather than use you as a REPL ;) They also need final to avoid putting the non-existent functions in the vtable. This works (and prints what you want): class SharedArray(T) { T[] array; alias array this; disable final: T front(); void popFront(); bool empty(); } -Steve
Aug 10 2020
next sibling parent reply mw <mingwu gmail.com> writes:
On Monday, 10 August 2020 at 20:42:34 UTC, Steven Schveighoffer 
wrote:
 On 8/10/20 4:13 PM, mw wrote:
 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.
This subtyping loophole should be fixed by the compiler.
No, it is doing exactly what you asked it to do -- turn a non-reference type into a full reference type.
Sorry, I typed too quickly, I'm trying to say: by the __compiler__ -> by the std library.
 The fault here is Phobos for accepting classes as ranges (range 
 classes are IMO an abomination that should never be used).
Yes, it's the library did it wrong, I still hold the opinion that: `writeln` should be a *view* (i.e read-only) function, it shouldn't *internally* call anything thing that change the data passed in.
Aug 10 2020
next sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Monday, 10 August 2020 at 21:20:11 UTC, mw wrote:
 `writeln` should be a *view* (i.e read-only) function, it 
 shouldn't *internally* call anything thing that change the data 
 passed in.
So how should writeln deal with input ranges? Should it simply not be possible to print them, as that would (by their very nature) change them? -- Simen
Aug 11 2020
parent Dominikus Dittes Scherkl <dominikus scherkl.de> writes:
On Tuesday, 11 August 2020 at 13:36:19 UTC, Simen Kjærås wrote:
 So how should writeln deal with input ranges?
I would expect writeln to first try to use some const-interface (e.g. indexing if the given type provides that). Only if such interface is not available, it should try modifying interfaces like range pop-front.
Aug 11 2020
prev sibling parent Kagamin <spam here.lot> writes:
On Monday, 10 August 2020 at 21:20:11 UTC, mw wrote:
 Yes, it's the library did it wrong, I still hold the opinion 
 that:

 `writeln` should be a *view* (i.e read-only) function, it 
 shouldn't *internally* call anything thing that change the data 
 passed in.
This would disable writeln features people already use, you can create a simple const-flavored writeln wrapper: void writeln(Args...)(in Args a) { static import std.stdio; std.stdio.writeln(a); } int main(string[] args) { Filenames a=new Filenames; writeln(a); return 0; }
Aug 15 2020
prev sibling next sibling parent RazvanN <razvan.nitu1305 gmail.com> writes:
On Monday, 10 August 2020 at 20:42:34 UTC, Steven Schveighoffer 
wrote:

 The fault here is Phobos for accepting classes as ranges (range 
 classes are IMO an abomination that should never be used).
Honestly, I think that the real problem here is that alias this can be used with classes. I would argue that alias this should be available only for structs because: 1. There are no rules that define precedence: which should have priority alias this or one of the parents? 2. For classes you already have the inheritance mechanism and therefore alias this basically invites multiple inheritance into the problem (with its known issues: the diamond problem). It would be easier for everyone if alias this would be banned and classes and just stick to structs. Cheers, RazvanN
Aug 11 2020
prev sibling parent Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Monday, 10 August 2020 at 20:42:34 UTC, Steven Schveighoffer 
wrote:
 On 8/10/20 4:13 PM, mw wrote:
 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.
This subtyping loophole should be fixed by the compiler.
No, it is doing exactly what you asked it to do -- turn a non-reference type into a full reference type. The fault here is Phobos for accepting classes as ranges (range classes are IMO an abomination that should never be used).
Yet, they allow to mask implementation of a range. Phobos shouldn't just prohibit all class based ranges, just those that don't implement right interfaces, such as InputRange(T) or ForwardRange(T).
Aug 11 2020
prev sibling parent mw <mingwu gmail.com> writes:
On Monday, 10 August 2020 at 19:52:19 UTC, Steven Schveighoffer 
wrote:

 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.
compiler => library :-) BTW, where is the code of this interface testing sequence? I think we should change the testing order to try slicing first, because slice is a “view” (readonly to the underlying data) struct.
Aug 10 2020
prev sibling parent reply mw <mingwu gmail.com> writes:
On Monday, 10 August 2020 at 19:30:18 UTC, Steven Schveighoffer 
wrote:
 This is precisely why classes should never be ranges.
And the user didn't code this range, the range is provided by the language standard library: https://dlang.org/phobos/std_range_primitives.html it's the std library that didn't take into account of the array-be-subtyped-as class loop-hole. Ideally, to the user, subtyping should just perform simple forward to the underlying data member, i.e. writeln(s0.fns); should be translated by the *compiler* to: writeln(s0.fns.array); Obviously it failed to do so here.
Aug 10 2020
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 8/10/20 3:49 PM, mw wrote:
 Ideally, to the user, subtyping should just perform simple forward to 
 the underlying data member, i.e.
 
 writeln(s0.fns);
 
 should be translated by the *compiler* to:
 
 writeln(s0.fns.array);
 
 Obviously it failed to do so here.
It would never do that. Because classes are based on Object, and writeln is defined to accept an Object and print it that way (using Object.toString). So even if the range methods weren't available, it wouldn't instead pass the base type. What's happening is that inadvertently, you have created a range type. writeln is going to PREFER using range mechanisms over Object.toString, so when it sees it can use range primitives, it uses those. This is the danger of duck-typing. What you want is array functionality, not range functionality on the class. So you need to avoid those mechanisms. Try the disable idea, see if it works. -Steve
Aug 10 2020