Thread overview
Plan for InputRange?
Sep 28, 2021
Dukc
Sep 29, 2021
Paul Backus
Sep 29, 2021
bauss
Sep 29, 2021
Dukc
Sep 29, 2021
Paul Backus
September 28, 2021

Yesterday, I came across this bug that is caused by design of std.range.interfaces. The problem is that InputRange has an abstract member function moveFront().

Why is this a problem, you could ask - if I'm able to get a front of a range by copy, surely I can get it when I don't even require a copy? This is all good and true with most types. But moveFront() from std.range.primitives, just like core.lifetime.move, will require the element to be a LValue. moveFront() is smart enough to use copy with RValues if there are no copy constructors or postblits, but with them a reference access to front is required. It follows that the dynamic range interfaces will not work at for ranges with RValue elements with copy constructors:

import std;

struct HasCC
{   inout this(ref inout typeof(this)){}
}

void main()
{   // will not compile
    auto dynamicRange = repeat(HasCC.init).inputRangeObject;
}

What should we do about this? One could argue that moveFront belongs to InputAssignable as opposed to InputRange, but I think it would be too much breakage to change it like that anymore.

Another option might be to say that moveFront should behave just like front when front is a RValue. Either just in std.range.interfaces or also in std.range.primitives. Perhaps even move could behave like that? But maybe there is some reason I'm missing why they do not act like that already?

We could also leave it just as is, documenting the limitation in std.range.interfaces. It would not necessarily mean a permanent defeat, because of possibility to fix it in Phobos V2 (Timeline estimates on that, anyone?).

September 29, 2021

On Tuesday, 28 September 2021 at 15:07:59 UTC, Dukc wrote:

>

What should we do about this? One could argue that moveFront belongs to InputAssignable as opposed to InputRange, but I think it would be too much breakage to change it like that anymore.

Maybe moveFront could throw an exception at runtime if the wrapped range does not support it. Something like the following:

override E moveFront()
{
    static if (__traits(compiles, r.moveFront))
        return r.moveFront;
    else
        throw new Exception("Cannot move the front of a `" ~ typeof(r).stringof ~ "`");
}

It's a stupid hack, but that may be the best way to work around a stupid design mistake like this.

September 29, 2021

On Wednesday, 29 September 2021 at 02:06:16 UTC, Paul Backus wrote:

>

On Tuesday, 28 September 2021 at 15:07:59 UTC, Dukc wrote:

>

What should we do about this? One could argue that moveFront belongs to InputAssignable as opposed to InputRange, but I think it would be too much breakage to change it like that anymore.

Maybe moveFront could throw an exception at runtime if the wrapped range does not support it. Something like the following:

override E moveFront()
{
    static if (__traits(compiles, r.moveFront))
        return r.moveFront;
    else
        throw new Exception("Cannot move the front of a `" ~ typeof(r).stringof ~ "`");
}

It's a stupid hack, but that may be the best way to work around a stupid design mistake like this.

Why should it throw an exception when you can detect it at compiletime?

override E moveFront()
{
    static if (__traits(compiles, r.moveFront))
        return r.moveFront;
    else
        static assert(0, "Cannot move the front of a `" ~ typeof(r).stringof ~ "`");
}
September 29, 2021

On Wednesday, 29 September 2021 at 06:10:30 UTC, bauss wrote:

>

Why should it throw an exception when you can detect it at compiletime?

override E moveFront()
{
    static if (__traits(compiles, r.moveFront))
        return r.moveFront;
    else
        static assert(0, "Cannot move the front of a `" ~ typeof(r).stringof ~ "`");
}

inputRangeObject already does something like that, but there lies the problem. It means that you cannot wrap a range with immobile elements in any of the std.range.primitives at all, even if you never move them.

Perhaps it could do a regular assert(false) though. I'm not sure it should throw an exception, since calling moveFront where one is not implemented is a bug, not an environmental error. On the other hand catchable exceptions are the simplest way to let the user to introspect whether the elements are mobile.

September 29, 2021

On Wednesday, 29 September 2021 at 08:20:31 UTC, Dukc wrote:

>

Perhaps it could do a regular assert(false) though. I'm not sure it should throw an exception, since calling moveFront where one is not implemented is a bug, not an environmental error. On the other hand catchable exceptions are the simplest way to let the user to introspect whether the elements are mobile.

When you use assert, you put the responsibility on the calling function to avoid the failure case (e.g., by calling empty before calling front). In this case, though, because InputRange hides the wrapped range's original type, there is nothing the caller can do in advance to check whether moveFront will fail. So it has to be moveFront's responsibility to detect failure, which means it must use a recoverable exception.