Thread overview
Missing introspection? "How exactly this type is actually used"
May 20, 2022
Ali Çehreli
May 20, 2022
Adam D Ruppe
May 20, 2022
Ali Çehreli
May 20, 2022
Adam D Ruppe
May 20, 2022
Ali Çehreli
May 20, 2022
H. S. Teoh
May 20, 2022
Ali Çehreli
May 20, 2022
This is not really important and not possible at least due to separate compilation but I've just realized something.

Imagine there is a range type that uses DbI:

struct S(R) {
// ...
  static if (isRandomAccessRange!R) {
    // Potentially costly additional members
  }
}

What if the user never uses those costly members of that particular instantiation? What is missing is the ability to know whether the type is actually used that way. Since the compiler cannot know, I guess one option is to provide an additional template parameter to help the user opt out of certain DbI costs:

struct S(JustInputRangePlease justInputRangePlease /* ... */, R) {
// ...
  static if (justInputRangePlease) {
    // Nothing to add extra

  } else {
    static if (isRandomAccessRange!R) {
      // Potentially costly additional members
    }
  }
}

I don't think there is any real problem and this is not worth the complexity. I just wanted to tell you. :)

Ali
May 20, 2022
On Friday, 20 May 2022 at 17:45:01 UTC, Ali Çehreli wrote:
> What if the user never uses those costly members of that particular instantiation?

If the functions themselves are templates, if they are never used, it will never be instantiated. That's a way you can do this kind of thing.
May 20, 2022
On 5/20/22 11:04, Adam D Ruppe wrote:
> On Friday, 20 May 2022 at 17:45:01 UTC, Ali Çehreli wrote:
>> What if the user never uses those costly members of that particular
>> instantiation?
>
> If the functions themselves are templates, if they are never used, it
> will never be instantiated. That's a way you can do this kind of thing.

Interesting. That sounds like a general guideline to all user-defined types.

I think the cost would be multiple compilations of those member function instances, N-1 of which could be eliminated by the linker.

The issue with the member variables remains: Currently, there is no way of determining that a member variable is used only by a set of member function instances that are never generated, meaning that the member variable could be eliminated. (For this, we are clearly talking about struct and class templates.) But as I understand, the compiler cannot see through function interfaces to determine what exactly is used on the other side. So we would end up with mismatched types.

Ali

May 20, 2022
On Friday, 20 May 2022 at 18:23:47 UTC, Ali Çehreli wrote:
> The issue with the member variables remains: Currently, there is no way of determining that a member variable is used only by a set of member function instances that are never generated, meaning that the member variable could be eliminated.

What's your goal here? To minimize the memory used at runtime?
May 20, 2022
On 5/20/22 11:31, Adam D Ruppe wrote:
> On Friday, 20 May 2022 at 18:23:47 UTC, Ali Çehreli wrote:
>> The issue with the member variables remains: Currently, there is no
>> way of determining that a member variable is used only by a set of
>> member function instances that are never generated, meaning that the
>> member variable could be eliminated.
>
> What's your goal here? To minimize the memory used at runtime?

Nothing is important really. My thought process was the following:

DbI allows range algorithms to e.g. provide random access:

  iota(10)
    .map!(i => i)
    .take(3)[1];

iota is uneful enough to provide opIndex, which is propagated by map and take and that random access works.

What if say, map used some expensive members (imagine some range type keeps an int[1000] for some reason) just because iota was isRandomAccessRange etc. (Same for take.) If the user never had any interest for that machinery (for this particular use) then there would be both compile-time and runtime cost.

That's when I realized that there is no way of cutting that cost other than the user explicitly saying something like justInputRangePlease and it would be more efficient:

  iota!justInputRangePlease(10)  // <-- Here
    .map!(i => i)
    .take(3);

Just an observation. Nothing to fix here. :)

Ali

May 20, 2022
On Fri, May 20, 2022 at 12:19:25PM -0700, Ali Çehreli via Digitalmars-d wrote:
> On 5/20/22 11:31, Adam D Ruppe wrote:
[...]
> > What's your goal here? To minimize the memory used at runtime?
> 
> Nothing is important really. My thought process was the following:
> 
> DbI allows range algorithms to e.g. provide random access:
> 
>   iota(10)
>     .map!(i => i)
>     .take(3)[1];
> 
> iota is uneful enough to provide opIndex, which is propagated by map and take and that random access works.
> 
> What if say, map used some expensive members (imagine some range type keeps an int[1000] for some reason) just because iota was isRandomAccessRange etc.  (Same for take.) If the user never had any interest for that machinery (for this particular use) then there would be both compile-time and runtime cost.

Actually, there would not be any compile-time or runtime cost if the user doesn't actually use a method that requires calling the expensive member.  For example, if opIndex for whatever reason is expensive (though technically it should not be; it's supposed to be O(1) according to the range API, IIRC), then .map would declare an opIndex that calls this expensive method.  But if the user never calls map.opIndex, then the template is never instantiated; the compiler wouldn't even process the function body and no code is emitted for it in the executable. That's the beauty of templated methods.

Of course, it's a different story if .map's implementation for whatever reason uses DbI to determine that since .opIndex is present, we'll always use it.  *Then* you'd be using the expensive method even if you didn't intentionally call something that might require it.


> That's when I realized that there is no way of cutting that cost other than the user explicitly saying something like justInputRangePlease and it would be more efficient:
[...]

I'm pretty sure Phobos has a wrapper function that "hides" away higher level methods in a range, like force something into an input range only even if it's a forward range or higher. Maybe one of the unittesting utility functions?  Or maybe I saw it once in a unittest.  I'm almost certain Phobos has such a thing.  If it's not a public function, it'd be a good idea to expose it as such.


T

-- 
Dogs have owners ... cats have staff. -- Krista Casada
May 20, 2022
On 5/20/22 12:41, H. S. Teoh wrote:

> Actually, there would not be any compile-time or runtime cost if the
> user doesn't actually use a method that requires calling the expensive
> member.

Agreed about functions.

DbI allows one to inject member variables:

  static if (something) {
    int[1000] big;
  }

That big member cannot be optimized away if 'something' is true. Even if the user never uses this type in a way that necessitates that big member...

This appeared as I was designing a range type that could support BidirectionalRange primitives, which could make me add a buffer for those back elements:

  static if (isBidirectionalRange!R) {
    ElementType!R[] backElements;
  }

As you can see, the type is going out of its way to be helpful. But it costs the user even if they don't use .back or .popBack().

Ali