opNext: Simplified ranges

Steven Schveighoffer schveiguy at gmail.com
Sat Nov 5 23:32:51 UTC 2022


On 11/5/22 2:08 PM, Paul Backus wrote:
> On Saturday, 5 November 2022 at 15:17:42 UTC, Steven Schveighoffer wrote:
>> This can be solved by a wrapper:
>>
>> ```d
>> struct RefOf(T)
>> {
>>   private T *_val;
>>   ref T _get() { return *_val; }
>>   alias _get this;
>> }
>>
>> auto refOf(T)(return ref T val) { return RefOf!T(&val); }
>>
>> foreach(elem; range.refOf) // now doesn't make a copy.
>> ```
> 
> You'd think, but if you actually try this, it turns out it doesn't work: 
> the compiler is "smart" enough to see through the `alias this`, and 
> makes a copy of `range.refOf._get` (the actual range object) instead of 
> `range.refOf` (the wrapper).

It's not "smart", just picky.

> 
> So, for example, this usage code prints `[1, 2, 3]`, demonstrating that 
> the range is *not* consumed by the `foreach` loop:
> 
> ```d
> void main()
> {
>      import std.range, std.stdio;
> 
>      auto r = only(1, 2, 3);
>      foreach (e; refOf(r)) {}
>      writeln(r);
> }
> ```
> 
> In the `-vcg-ast` output, you can see the call to `_get` inserted by the 
> compiler:
> 
> ```d
> void main()
> {
>      import std.range;
>      import std.stdio;
>      OnlyResult!(int, int, int) r = only(1, 2, 3);
>      {
>          OnlyResult!(int, int, int) __r47 = refOf(r)._get().opSlice();
>          for (; !__r47.empty(); __r47.popFront())
>          {
>              int e = __r47.front();
>          }
>      }
>      writeln(r);
>      return 0;
> }
> ```

What is happening is the compiler is not using duck typing exactly, and 
it also has some pretty puzzling logic.

1. If it has the ability to slice, it will *always do this at least 
once*. See the bug reported long ago: 
https://issues.dlang.org/show_bug.cgi?id=14619
2. The definition of a range is that it contains a `front` *member*. 
This cannot be via `alias this`.

So this works:

```d
struct RefOf(T)
{
   private T *_val;
   auto front() { return _get.front; }
   RefOf opSlice() { return this; }
   ref T _get() { return *_val; }
   alias _get this;
}
```

Take away from that what you will.

(also I forgot there actually is something called `std.range.refRange` 
which *is supposed to do this*, but apparently still broken).

-Steve


More information about the Digitalmars-d mailing list