New blog post on the cost of compile time

Steven Schveighoffer schveiguy at gmail.com
Sun Feb 19 18:08:28 UTC 2023


On 2/18/23 2:25 PM, Nick Treleaven wrote:
> On Saturday, 18 February 2023 at 17:16:18 UTC, Steven Schveighoffer wrote:
>> This is how isInputRange used to look. The reason it was changed is 
>> because the compiler now "sees" the different clauses separated by &&, 
>> and will tell you which one failed. When you wrap it like this, it 
>> just sees one big constraint.
> 
> Makes sense, although I don't get that on my machine with dmd v2.101.0:
> ```d
> import std.range;
> int f(R)() if (isInputRange!R) => 2;
> int i = f!int;
> ```
> ```
> isinputrange.d(68): Error: template instance `isinputrange.f!int` does 
> not match template declaration `f(R)()`
>    with `R = int`
>    must satisfy the following constraint:
> `       isInputRange!R`
> ```
> That's it, nothing about why isInputRange failed. Maybe I'm doing 
> something wrong.

uhh... I think I am wrong on the reasoning then. I thought it would keep 
going down into the constraint, but it doesn't? Maybe it was planned? I 
do know that && in the constraint itself does do that. e.g.:

```d
void foo(R)(R r) if (isInputRange!R && !is(R == int[])) {}

void main()
{
    foo(1);
    foo([1]);
}
```

```
onlineapp.d(8): Error: none of the overloads of template `onlineapp.foo` 
are callable using argument types `!()(int)`
onlineapp.d(2):        Candidate is: `foo(R)(R r)`
   with `R = int`
   must satisfy the following constraint:
`       isInputRange!R`
onlineapp.d(9): Error: none of the overloads of template `onlineapp.foo` 
are callable using argument types `!()(int[])`
onlineapp.d(2):        Candidate is: `foo(R)(R r)`
   with `R = int[]`
   must satisfy the following constraint:
`       !is(R == int[])`
```

and I also do know that the original `isInputRange` code was like you 
proposed -- one lambda with all the things.

> Anyway, assuming dmd can use that I wonder if this would work (aside 
> from the inout issue):
> ```d
> template isInputRange(R) {
>      extern R r; // dummy
>      enum isInputRange =
>          is(typeof(R.init) == R) &&
>          is(typeof({ return r.empty; }()) == bool) &&
>          (is(typeof(() return => r.front)) ||
>              is(typeof(ref () return => r.front))) &&
>          !is(typeof({ return r.front; }()) == void) &&
>          is(typeof({ r.popFront; }));
> }
> ```
> dmd could still see through the eponymous template to the expression, in 
> theory.

I don't know if that works, would it fail to link? It's an interesting idea.

> 
>> I actually did get a PR merged. It wasn't as simple as I had written 
>> in that blog post, due to the stupid `inout` requirement that `inout` 
>> data can only be used inside a function with an `inout` parameter.
> 
> OK, so `typeof((R r) { return r.empty; } (R.init))` works with inout.

Yes, that's what I found that worked. You need the lambda parameter to 
mimic the mutability of the type. Which kind of sucks, but it's what we 
have right now.

`lvalueOf!R` would work too, I just figured it isn't needed.

I wonder what the cost of using `T.init` is vs. a cached lookup of a 
template. It's probably pretty small, but these are the kinds of things 
that are unintuitive.

> 
> Thanks for the blog post BTW.

You're welcome! I write blog posts too infrequently, but I do have a 
bunch of half-started ones. I'm working on another right now...

>> I did start with using `lvalueOf!R`, but then realized that since 
>> `isInputRange` validates that `typeof(R.init) == R`, I just used 
>> `R.init` as the parameter.
> 
> Is that validation to detect types with redefined `init`? I didn't 
> realize Phobos needs to care about that.

I believe so. I'm not 100% sure why we are checking for redefined init 
here, but apparently it's needed somewhere inside phobos.

-Steve


More information about the Digitalmars-d mailing list