Thread overview
Can’t use UFCS to create InputRange?
Apr 29, 2020
Ogi
Apr 29, 2020
Simen Kjærås
Apr 29, 2020
user1234
Apr 29, 2020
Simen Kjærås
Apr 29, 2020
Paul Backus
April 29, 2020
struct R {}
int front(R r) { return 42; }
void popFront(R r) {}
bool empty(R r) { return false; }

void main() {
    import std.range.primitives : isInputRange;
    static assert(isInputRange!R);
}

> Error: static assert:  `isInputRange!(R)` is false

Whats really weird is that if I replace isInputRange with its definition from std.range.primitives, it returns true:

import std;

struct R {}
int front(R r) { return 42; }
void popFront(R r) {}
bool empty(R r) { return false; }

void main() {
    static assert(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)));
}
This compiles.

What’s going on here?
April 29, 2020
On Wednesday, 29 April 2020 at 08:34:53 UTC, Ogi wrote:
> struct R {}
> int front(R r) { return 42; }
> void popFront(R r) {}
> bool empty(R r) { return false; }
>
> void main() {
>     import std.range.primitives : isInputRange;
>     static assert(isInputRange!R);
> }
>
>> Error: static assert:  `isInputRange!(R)` is false
>
> What’s going on here?


The template IsInputRange is in the std.range.primitives module, and thus can't see the front, popFront and empty definitions in your module.

--
  Simen
April 29, 2020
On Wednesday, 29 April 2020 at 08:34:53 UTC, Ogi wrote:
> struct R {}
> int front(R r) { return 42; }
> void popFront(R r) {}
> bool empty(R r) { return false; }
>
> void main() {
>     import std.range.primitives : isInputRange;
>     static assert(isInputRange!R);
> }
>
>> Error: static assert:  `isInputRange!(R)` is false
>
> Whats really weird is that if I replace isInputRange with its definition from std.range.primitives, it returns true:
>
> import std;
>
> struct R {}
> int front(R r) { return 42; }
> void popFront(R r) {}
> bool empty(R r) { return false; }
>
> void main() {
>     static assert(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)));
> }
> This compiles.
>
> What’s going on here?

The static checker doesn't see your free funcs because to do so it would have to import the whole module. (is it possible to do that ? no idea.)
Also your signature for the primitives are quite unusual (i.e not idiomatic). Usually they dont take param. Usually we pass a type that contains the member funcs matching to IsIntputRange.
April 29, 2020
On Wednesday, 29 April 2020 at 09:16:58 UTC, user1234 wrote:
> The static checker doesn't see your free funcs because to do so it would have to import the whole module. (is it possible to do that ? no idea.)

Of course it's possible! :) We can find the context of R (in this case, the module) with __traits(parent), and import that:

    mixin("import "~__traits(parent, R).stringof["module ".length..$]~";");

However, doing that in isInputRange doesn't help much. First, all other range functions would have to do it, and second, just importing into function scope doesn't enable UFCS lookup.


> Also your signature for the primitives are quite unusual (i.e not idiomatic). Usually they dont take param. Usually we pass a type that contains the member funcs matching to IsIntputRange.

You can see a good counterexample to this in https://dlang.org/library/std/range/primitives/pop_front.html, which defines popFront for regular arrays. However, that is the one and only counterexample I know of.

Of course, nothing stops us from defining our own front, popFront and friends that combine the two approaches above:


template front(R) {
    auto front(R r) {
        return __traits(getMember, __traits(parent, R), "front")(r);
    }
}
template popFront(R) {
    auto popFront(R r) {
        return __traits(getMember, __traits(parent, R), "popFront")(r);
    }
}
template empty(R) {
    auto empty(R r) {
        return __traits(getMember, __traits(parent, R), "empty")(r);
    }
}

We could conceivably add these to std.range.primitives (probably adding some constraints first), and suddenly UFCS ranges are possible! (I am as of yet not convinced that we should, though)

--
  Simen
April 29, 2020
On Wednesday, 29 April 2020 at 12:23:11 UTC, Simen Kjærås wrote:
> Of course, nothing stops us from defining our own front, popFront and friends that combine the two approaches above:
>
[...]
>
> We could conceivably add these to std.range.primitives (probably adding some constraints first), and suddenly UFCS ranges are possible! (I am as of yet not convinced that we should, though)
>
> --
>   Simen

This is basically what C++ calls "argument-dependent lookup." Discussion about adding this sort of thing to D has come up before on the forums [1], and iirc Walter has generally been opposed to it. If it were to be added as a library feature, it would probably have to be opt-in.

[1] https://forum.dlang.org/post/mailman.123.1472818535.2965.digitalmars-d@puremagic.com