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)); } ```