On Mon, 12 Jul 2010 13:49:50 -0400, Andrei Alexandrescu <seewebsiteforem...@erdani.org> wrote:

On 07/12/2010 11:48 AM, Steven Schveighoffer wrote:
On Mon, 12 Jul 2010 11:05:33 -0400, Andrei Alexandrescu
<seewebsiteforem...@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

Reply via email to