As of now, foreach admits ref variables as in foreach (ref x; xs). There, ref can be used for two conceptually different things:
- Avoiding copies
- Mutating the values in place
If mutating in place is desired, ref is an excellent choice.
However, if mere copy avoiding is desired, another great option would be in.
On parameters, it avoids expensive copies, but does trivial ones.
A type supplying opApply can, in principle, easily provide an implementation where the callback takes an argument by in or out:
struct Range
{
int opApply(scope int delegate(size_t, in X) callback)
{
X x;
if (auto result = callback(0, x)) return result;
return 0;
}
}
For out, it’s not really different.
However, how do classical ranges (empty, front, popFront) fare with these?
First in.
foreach (in x; xs) { … }
// lowers to
{
auto __xs = xs;
for (; !__xs.empty; __xs.popFront)
{
static if (/* should be ref */)
const scope ref x = __xs.front;
else
const scope x = __xs.front;
…
}
}
The first notable observation is that out makes no sense for input ranges. Rather, it would make sense for, well, output ranges: Every time the loop reaches the end, a put is issued, whereas continue means “this loop iteration did not produce a value, but continue” and break means “end the loop”:
foreach (out T x; xs) { … }
// lowers to
{
auto __xs = xs; // or xs[]
for (; !__xs.empty /* or __xs.length > 0 or nothing */;)
{
auto x = T.init;
…
__xs.put(x); /* or similar */
}
}
The program should assign x in its body. If control reaches the end of the loop, the value is put in the output range.
As an output range, in general, need not be finite, the loop is endless by design, but if the range has an empty member, it’s being used, and for types with length, but no empty, the condition is __xs.length > 0. For arrays and slices, the put operation is __xs[0] = x; __xs = __xs[1 .. $];.
If T is not explicitly given, and xs is not an array or slice, an attempt should be made to extract it from the single parameter of a non-overloaded xs.put. Otherwise, it’s an error.
Dynamic arrays and slices should support size_t keys as well:
foreach (i, out x; xs) { … }
// lowers to
{
auto __xs = xs[];
for (size_t __i = 0; __xs.length > 0; ++__i)
{
size_t i = __i;
auto x = typeof(xs[0]).init;
…
__xs[0] = x;
__xs = __xs[1 .. $];
}
}
Associative arrays specifically can be filled using out key and values:
int[string] aa;
foreach (out key, out value; aa) { … }
// lowers to
{
auto __aa = aa;
for (;;)
{
KeyType key = KeyType.init;
ValueType value = ValueType.init;
…
__aa[key] = value;
}
}
At some point, a break is needed, otherwise the loop is infinite.
Permalink
Reply