Philippe Sigaud wrote:
On Thu, Dec 31, 2009 at 16:47, Michel Fortin <[email protected]
<mailto:[email protected]>> wrote:
On 2009-12-31 09:58:06 -0500, Andrei Alexandrescu
<[email protected]
<mailto:[email protected]>> said:
The question of this post is the following: should output ranges
be passed by value or by reference? ArrayAppender uses an extra
indirection to work properly when passed by value. But if we
want to model built-in arrays' operator ~=, we'd need to request
that all output ranges be passed by reference.
I think modeling built-in arrays is the way to go as it makes less
things to learn. In fact, it makes it easier to learn ranges because
you can begin by learning arrays, then transpose this knowledge to
ranges which are more abstract and harder to grasp.
I agree. And arrays may well be the most used range anyway.
Upon more thinking, I'm leaning the other way. ~= is a quirk of arrays
motivated by practical necessity. I don't want to propagate that quirk
into ranges. The best output range is one that works properly when
passed by value.
Beside, an extra indirection is wasteful when you don't need it.
It's easier to add a new layer of indirection when you need one than
the reverse, so the primitive shouldn't require any indirection.
So (squint through sleep-deprived eyes:) that makes it by ref, right?
// pseudo-method
void put(R, E)(ref R tgt, E e) {
tgt.front = e;
tgt.popFront();
}
It doesn't. The ref in there is to pass tgt to the pseudo-method put,
not to the function that invokes it.
A few random comments, sorry if they are not entirely coherent:
- this new put needs hasAssignableElements!R, right? What's in this case
the difference between isOutputRange!R and hasAssignableElements!R?
It's a good question. There are two possible designs:
1. In the current design, the difference is that hasAssignableElements!R
does not imply the range may grow. Consider this:
auto a = new int[10], b = new int[10];
copy(a, b);
This should work. But this shouldn't:
auto a = new int[10], b = new int[5];
copy(a, b);
because copy does not grow the target. If you want to append to b, you
write:
copy(a, appender(&b));
2. In the design sketched in
http://www.informit.com/articles/printerfriendly.aspx?p=1407357,
iteration is separated from access. In that approach, you'd have a
one-pass range for both input and output.
I'm not sure which design is better. (Suggestions are welcome.) For a
pure output range, it's awkward to define empty() (what should it
return?) and it's also awkward to put elements by calling two functions
front/popFront instead of one.
- should all higher-order ranges expose a put method if possible?
(stride comes to mind, but also take or filter).
I don't think so. The generic put will take care of that.
- does that play nice with the new auto ref / ref template parameter
from 2.038? It seems to me that this new feature will go hand in hand
with this, but I may be mistaken.
There's no obvious connection. The nice thing about auto ref is this:
struct SomeAdapterRange(R) if (isWhateverRange!R) {
private R src;
@property auto ref front() {
return src.front;
}
}
You don't want to see how that looks today. Actually:
http://www.dsource.org/projects/phobos/browser/trunk/phobos/std/range.d
Search the page for "mixin" :o}.
- your shim method will be used like this:
put(r,e);
whereas for an output range you use:
r.put(e);
and you cannot freely go from one form to the other, except for arrays,
which are output ranges anyway [*]. Does that mean that you must
disseminate static if ByRef/Assignable/Output/Whatever checks
everywhere, to use either put(r,e) or r.put(e)?
The D language automatically rewrites the latter into the former.
- what if R is a range of ranges (ie: if E is itself a range). Should
put by invoked recursively? What if its a chunked range?
I don't know.
- something I wanted to ask for a long time: does put really write to
the range as written in the docs or to the underlying container for
which the output range is but a 'writable' view? The container/range
separation does not exist for arrays, but is important for other cases.
Depends on how the range is defined. Appender holds a pointer to an
array and appends to it. But appender is a special-purpose range. A
usual range cannot change the topology of the container it's under.
Andrei