isInputRange not satisfied even if all individual conditions are satisfied

Johannes Loher johannes.loher at fg4f.de
Fri Jun 26 13:09:51 UTC 2020


Recently, I read about problems regarding the API design of ranges (not
being able to make them const, differences between class and struct
based ranges etc.).

One of the issues that came up what the fact that sometimes it is useful
to use the class wrappers `InputRangeObject` etc. if you need to decide
at runtime which range you want to use. I ran into this into a personal
project in the past and this also was how I solved the issue.

Another suggestion in that thread was to simply use choose, which I
guess should work, but I had trouble getting it to work in my particular
case.

However, a third option came to my mind: Why not simply use a sumtype to
return either one range or the other? In order to make it more
convenient to use afterwards, it would be useful if we could regard a
sumtype of ranges as a range itself. That should be no problem because
we just need to delegate all function calls to the range api to the
actual range that sits inside the sumtype.

So I tried to implement this and it kind of seems to work (I can call
the individual functions on the sumtype using UFCS) but for some reason,
`isInputRange` evaluates to `false`. What makes it even weirder is the
fact that all individual conditions that are part of `isInputRange` seem
to be fulfilled. Could somebody explain to me what is going on? Here is
the code:

https://run.dlang.io/gist/93bcc196f73b0a7c7aa1851beef59c22 (currently
give me a 502 though)

```
#!/usr/bin/env dub
/+ dub.sdl:
	name "sumtype-range"
    dependency "sumtype" version="~>0.9.4"
+/

module sumtype_range;

import std.meta : allSatisfy, staticMap;
import std.traits : allSameType;
import std.range : isInputRange, ElementType, empty;

import sumtype;

private template EmptyLambda(T) if (isInputRange!T)
{
    alias EmptyLambda = (ref T t) => t.empty;
}

@property bool empty(TypeArgs...)(auto ref scope SumType!(TypeArgs) r)
        if (allSatisfy!(isInputRange, TypeArgs) && TypeArgs.length > 0)
{
    return r.match!(staticMap!(EmptyLambda, TypeArgs));
}

private template FrontLambda(T) if (isInputRange!T)
{
    alias FrontLambda = (ref T t) => t.front;
}

@property ElementType!(TypeArgs[0]) front(TypeArgs...)(auto ref scope
SumType!(TypeArgs) r)
        if (allSatisfy!(isInputRange, TypeArgs)
            && allSameType!(staticMap!(ElementType, TypeArgs)) &&
TypeArgs.length > 0)
{
    return r.match!(staticMap!(FrontLambda, TypeArgs));
}

private template PopFrontLambda(T) if (isInputRange!T)
{
    alias PopFrontLambda = (ref T t) => t.popFront();
}

void popFront(TypeArgs...)(auto ref scope SumType!(TypeArgs) r)
        if (allSatisfy!(isInputRange, TypeArgs) && TypeArgs.length > 0)
{
    return r.match!(staticMap!(PopFrontLambda, TypeArgs));
}

enum bool myIsInputRange(R) =
    is(typeof(R.init) == R)
    && is(ReturnType!((R r) => r.empty) == bool)
    && is(typeof((return ref R r) => r.front))
    && !is(ReturnType!((R r) => r.front) == void)
    && is(typeof((R r) => r.popFront));

void main() {
	import std.range : iota, only;
    import std.stdio : writeln;
    import std.traits : ReturnType;

    auto i = iota(4);
    auto o = only(1);
    alias R = SumType!(typeof(i), typeof(o));

    // all individual conditions of `isInputRange` are satisfied
    static assert(is(typeof(R.init) == R));
    static assert(is(ReturnType!((R r) => r.empty) == bool));
    static assert(is(typeof((return ref R r) => r.front)));
    static assert(!is(ReturnType!((R r) => r.front) == void));
    static assert(is(typeof((R r) => r.popFront)));

    // but `isInputRange` is not satisfied
    static assert(!isInputRange!(R));

    // and neither is a local copy
    static assert(!myIsInputRange!(R));
}
```


More information about the Digitalmars-d-learn mailing list