DIP 1022--foreach auto ref--Community Review Round 1

Manu turkeyman at gmail.com
Tue Aug 13 23:34:32 UTC 2019


On Tue, Aug 13, 2019 at 6:50 AM Dukc via Digitalmars-d
<digitalmars-d at puremagic.com> wrote:
>
> On Saturday, 10 August 2019 at 21:42:00 UTC, Manu wrote:
>
> > I tend to hate special-case rules (in this case, loop counters
> > behaving differently than function arguments), but I think I'm
> > persuaded here.
>
> Not sure what you mean here... example?

struct S
{
  int front();
  ...
}

void takesRef(ref int);

{
  S s;

  takesRef(s.front()); // <- fine, temporary is created and passed by ref

  foreach(ref i; s) // <- error: no temporary is created; we have
decided to emit an error where an rval is given to a loop counter
  { ... }
}

This is the special case I'm talking about.

> > Okay, here's some experiment cases:
> >
> >   int x = 1;
> >   static foreach (i; AliasSeq!(10, x, 20))
> >       pragma(msg, __traits(isRef, i));
> >
> >> false false false
> >
> > In this case, `i` is an alias. Ref-ness doesn't mean anything
> > here.
>
> I thought that would compile only without the `static` keyword.
> Gotta investigate. If it compiles, I'm going to say it should
> behave exactly as written without the `static` keyword.

Iterating a tuple is a compile-time expansion, which is distinct from
a runtime expansion.
The result is `false false false` either way, but static and
non-static are very different operations.

`foreach` will iterate an array of int's, `static foreach` will expand
the tuple and `i` will be an alias of each element.

> Same answer for the rest of examples with tuples.

No, static and non-static foreach are completely different semantically.
My examples may show the same outputs either way, but it's easy to
make cases which show the distinction between static and non-static.

> > Now, there's a different set of cases, where you call static
> > foreach over a not-a-tuple:
> >
> >   static foreach (i; [10, 20, 30])
> >       pragma(msg, __traits(isRef, i));
> >
> >> false false false
> >
> > In this case, `i` is not an alias like the previous experiment,
> > it is
> > inferred to be `int`.
> > Should be the same as `static foreach (int i; ...)`, right?
> >
> >   static foreach (ref i; [10, 20, 30])
> >       pragma(msg, __traits(isRef, i));
> >
> >> true true true
>
> Yes, these are the cases I meant with `static foreach`. I think
> `[10, 20, 30]` is a rvalue, and if the latter of these two
> examples compiles, it should not.

Actually, because of D's 'weird shit' law, the array is not an rvalue.
(I think...?)

> > As above, we could allow this by creating temporaries the same
> > as function arguments... but we've decided not to allow ref
> > iterators from rvalues.
> >
> >   static foreach (auto ref i; [10, 20, 30])
> >       pragma(msg, __traits(isRef, i));
> >
> >> false false false ??
>
> Yes, exactly what's supposed to happen.

Right... what's your point?
Incidentally, I'm not sure this will hold; because array literals are
not rvalues, so I think it might be `true true true` here...

> > I guess the whole array is an rvalue, so then the loop counter
> > would take copies of the elements?
> >
> > It gets interesting when you do this:
> >
> >   int[3] x = [10, 20, 30];
> >   static foreach (ref i; x)
> >       pragma(msg, __traits(isRef, i));
> >
> >> true true true
>
> I don't think that compiles. x is not an enum value. But I might
> be wrong, I'll have to recheck this also.

Why not? It's an alias... it should compile just fine.

> >
> > And:
> >
> >   int[3] x = [10, 20, 30];
> >   static foreach (auto ref i; x)
> >       pragma(msg, __traits(isRef, i));
> >
> >> true true true
>
> Same as above. If x was enum, then it should not compile with
> `ref` and should return false false false with `auto ref`.

`x` is not an enum, it's a local.
The cases I propose to consider are exactly what I wrote, not some other thing.

For instance:

int fun(ref int x)
{
  static foreach (i; AliasSeq!(10, x, 30))  // <- outputs `false true false`
    pragma(msg, __traits(isRef, i));

  foreach (i; AliasSeq!(10, x, 30)) // <- outputs `false false false` (!!!)
    pragma(msg, __traits(isRef, i));
}

And there's another interesting case of 'weird shit' here too. That
second one should output `false`, not `false false false`; the
pragma(msg) should only evaluate once, because the non-static foreach
shouldn't be expanding... but strangely, runtime foreach when given a
tuple behaves almost like a static foreach, but the loop counter is
not an alias for the tuple element, it is rather an `auto i =
element[n]` copy.
I actually think this form of runtime foreach should be deprecated, if
you want to do this with runtime foreach, wrap the tuple in `[ ]`.

Here there be dargons!

> > TL;DR, your sentence "It should be allowed in static foreach,
> > but with no effect" should be removed, and if you want to
> > detail the expected semantics with `static foreach`, that might
> > be a good idea.
>
> I will check those examples that I thought won't compile do. If
> they do, I'll be more explicit.

I'm not strictly talking about what does actually compile, I'm talking
about what makes semantic sense, and would be uniform semantically
with no bizarre cases. Some cases might not work, but should.


More information about the Digitalmars-d mailing list