Overhauling the notion of output range
Steven Schveighoffer
schveiguy at yahoo.com
Mon Jul 12 11:47:42 PDT 2010
On Mon, 12 Jul 2010 13:49:50 -0400, Andrei Alexandrescu
<SeeWebsiteForEmail at erdani.org> wrote:
> On 07/12/2010 11:48 AM, Steven Schveighoffer wrote:
>> On Mon, 12 Jul 2010 11:05:33 -0400, Andrei Alexandrescu
>> <SeeWebsiteForEmail at erdani.org> wrote:
>>
>>> On 07/12/2010 09:59 AM, Steven Schveighoffer wrote:
>>>> If I always have to do something like this in order to append a single
>>>> element:
>>>>
>>>> put(r, (&elem)[0..1]);
>>>
>>> No, the library does that. Look here:
>>>
>>> http://www.dsource.org/projects/phobos/browser/trunk/phobos/std/range.d#L306
>>>
>>
>> So you're saying it's not ok for an output range to support appending a
>> single element, but it's ok for put to support appending a single
>> element?
>
> Yes. The point is that with a delegate you must choose between accepting
> E and E[]. Given the constraint, it's better for everyone to accept E[]
> and let put() take care of the occasional E by doing the wraparoo
> (&elem)[0..1].
But given a delegate that takes a single element, there's no way to wrap
it so it can be an output range. Yet such a delegate can easily be
something that outputs something.
Indeed, a delegate that takes a string takes a single element, but because
a string happens to be defined as a range of chars, it passes the test for
output ranges.
I could loop on an array of strings of one character, and output that to a
valid output range no problem. The only thing that solves this problem
correctly is buffering.
What if I have my own container types that are large chunks of data, but
don't happen to define the input range primitives? Why should I be
artificially prevented from using those as input to output ranges?
Really to me, you are saying, "I want your delegate to be efficient", but
you defined something that is related to that in a small set of
circumstances (when the arrays being passed in are large).
>> Well, all that will end up happening is cases where appending a single
>> element is the only possibility will produce overloaded add functions,
>> one that takes a single element, and one that takes an array. The one
>> that takes an array will look like this:
>>
>> foreach(e; arr)
>> add(e);
>
> That's fine. the point is that if you put this loop on the wrong side of
> the delegate, it's much less efficient.
Here's a proposal for put/isOutputRange which would solve my problem and
not have any for loops in it:
template isOutputRange(R, E)
{
enum bool isOutputRange = is(typeof(
{
R r;
E e;
r.put(e); // can write element to range
}()))
||
isInputRange!R && is(typeof(
{
R r;
E e;
r.front = e; // can assign to the front of range
}()))
||
is(typeof(
{
R r;
E[] es;
r(es);
}()))
||
is(typeof(
{
R r;
E es;
r(es);
}()));
}
void put(R, E)(ref R r, E e) if (isOutputRange!(R, E))
{
static if (!isArray!R && is(typeof(r.put(e))))
{
r.put(e);
}
else static if (isInputRange!R && is(typeof(r.front = e)))
{
r.front = e;
r.popFront();
}
else static if (is(typeof(r(e))))
{
r(e);
}
else
{
static assert(false);
}
}
>
>> I can tell you this for sure, because it's exactly what's in many
>> dcollections classes.
>>
>> So what happens when you call put(r, e) for one of these output classes?
>> Instead of just calling add(e), it calls (add((&e)[0..1])) which in turn
>> goes through some needless loop, which then ends up calling add(e). I
>> don't see why this is preferable.
>
> Ah, I see. There is a confusion. The array restriction is only for
> delegates. For straight ranges, you should accept individual Es.
Why the discrepency? A naive coder can define inefficient ranges just as
well as he can define inefficient delegates.
-Steve
More information about the Digitalmars-d
mailing list