www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Missing introspection? "How exactly this type is actually used"

reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
parent reply Adam D Ruppe <destructionator gmail.com> writes:
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
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
parent reply Adam D Ruppe <destructionator gmail.com> writes:
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
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
May 20 2022