www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Is `alias this` a mistake?

reply NonNull <non-null use.startmail.com> writes:
Here, further down, I just saw Walter indicate his opinion that 
`alias this` is a mistake. Any thoughts?

https://news.ycombinator.com/item?id=28029184
Aug 03
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Aug 03, 2021 at 03:52:07PM +0000, NonNull via Digitalmars-d wrote:
 Here, further down, I just saw Walter indicate his opinion that `alias
 this` is a mistake. Any thoughts?
 
 https://news.ycombinator.com/item?id=28029184
This has been discussed many times before. Walter's stance is that it's a mistake because it's essentially another way of implementing subtyping, that has unclear interactions with well-established subtyping mechanisms (OO-style inheritance, in particular). This leads to a cascade of special cases that's hard to understand and adds a lot of complexity to the language for only meager benefits. A secondary reason is that it allows implicit conversions, and Walter has always had a low opinion of implicit conversions, especially the user-defined kind. I used to be a big fan of `alias this`, but after some actual experience with using it in my own code, I've come to agree with Walter more and more that it was a mistake. It's cool for quick hacks to make things work with code designed to receive a different type (and sometimes I still can't resist the temptation to use it), but in the long run it hurts code readability and maintainability. I've come to realize that whenever something in my code demands `alias this` as the answer, it's almost always a symptom of poor code design on my part. My code has often benefitted a lot from being rewritten to *not* need `alias this`. T -- He who sacrifices functionality for ease of use, loses both and deserves neither. -- Slashdotter
Aug 03
prev sibling next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Tuesday, 3 August 2021 at 15:52:07 UTC, NonNull wrote:
 Here, further down, I just saw Walter indicate his opinion that 
 `alias this` is a mistake. Any thoughts?

 https://news.ycombinator.com/item?id=28029184
`alias this` is typically used to - make a user-defined type behave like a subtype of some other type - make a user-defined type implicitly convert to some other type ...but it does kind of a crappy job at both. It's very easy to use `alias this` in ways that violate the [Liskov substitution principle][1] (see for example [this thread][2]), so it fails at being a good subtyping mechanism. And you can only have one `alias this` per type, so it also fails at being a good mechanism for user-defined implicit conversions (compare to `opCast`, the mechanism for user-defined *explicit* conversion). [1]: https://en.wikipedia.org/wiki/Liskov_substitution_principle [2]: https://forum.dlang.org/thread/q6plhj$1l9$1 digitalmars.com
Aug 03
parent Kagamin <spam here.lot> writes:
On Tuesday, 3 August 2021 at 16:24:26 UTC, Paul Backus wrote:
 On Tuesday, 3 August 2021 at 15:52:07 UTC, NonNull wrote:
 Here, further down, I just saw Walter indicate his opinion 
 that `alias this` is a mistake. Any thoughts?

 https://news.ycombinator.com/item?id=28029184
`alias this` is typically used to - make a user-defined type behave like a subtype of some other type - make a user-defined type implicitly convert to some other type ...but it does kind of a crappy job at both. It's very easy to use `alias this` in ways that violate the [Liskov substitution principle][1] (see for example [this thread][2]), so it fails at being a good subtyping mechanism. And you can only have one `alias this` per type, so it also fails at being a good mechanism for user-defined implicit conversions (compare to `opCast`, the mechanism for user-defined *explicit* conversion). [1]: https://en.wikipedia.org/wiki/Liskov_substitution_principle [2]: https://forum.dlang.org/thread/q6plhj$1l9$1 digitalmars.com
I use alias this for implicit conversion between reference and value type. It's not really subtyping and not really type conversion and works fine without humongous amounts of metaprogramming.
Aug 04
prev sibling next sibling parent reply IGotD- <nise nise.com> writes:
On Tuesday, 3 August 2021 at 15:52:07 UTC, NonNull wrote:
 Here, further down, I just saw Walter indicate his opinion that 
 `alias this` is a mistake. Any thoughts?

 https://news.ycombinator.com/item?id=28029184
Usually inheritance can be replaced with composition and alias this has been the way to do that. Instad of alias this, Walter wants us to use mixin templates for composition. I have no problems with this but the documentation and marketing has totally failed regarding this few seem to know about mixin templates its usage and limitations. The D community needs to be market mixin templates better.
Aug 03
parent Tejas <notrealemail gmail.com> writes:
On Tuesday, 3 August 2021 at 16:34:35 UTC, IGotD- wrote:
 On Tuesday, 3 August 2021 at 15:52:07 UTC, NonNull wrote:
 Here, further down, I just saw Walter indicate his opinion 
 that `alias this` is a mistake. Any thoughts?

 https://news.ycombinator.com/item?id=28029184
Usually inheritance can be replaced with composition and alias this has been the way to do that. Instad of alias this, Walter wants us to use mixin templates for composition. I have no problems with this but the documentation and marketing has totally failed regarding this few seem to know about mixin templates its usage and limitations. The D community needs to be market mixin templates better.
This is the first time I'm hearing about this application of mixin templates. I've seen Adam also say that MTs are underrated but I just don't understand how you can say that they replace inheritance. Think its possible for someone to write an article on the D blog? I'm interested in seeing this.
Aug 03
prev sibling next sibling parent reply bachmeier <no spam.net> writes:
On Tuesday, 3 August 2021 at 15:52:07 UTC, NonNull wrote:
 Here, further down, I just saw Walter indicate his opinion that 
 `alias this` is a mistake. Any thoughts?

 https://news.ycombinator.com/item?id=28029184
The only argument I've heard against alias this is that it can be misused. That's kind of a joke argument in a language that has pointers.
Aug 03
parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Tuesday, 3 August 2021 at 17:18:48 UTC, bachmeier wrote:
 On Tuesday, 3 August 2021 at 15:52:07 UTC, NonNull wrote:
 Here, further down, I just saw Walter indicate his opinion 
 that `alias this` is a mistake. Any thoughts?

 https://news.ycombinator.com/item?id=28029184
The only argument I've heard against alias this is that it can be misused. That's kind of a joke argument in a language that has pointers.
Fyi, 'alias this' nicely fits for doing auto unboxing of a wrapper type into wrapped type, similar to how java does unboxing of primitives, not sure that it will be possible with mixin templates.
Aug 03
parent reply bachmeier <no spam.net> writes:
On Tuesday, 3 August 2021 at 20:19:56 UTC, Alexandru Ermicioi 
wrote:
 On Tuesday, 3 August 2021 at 17:18:48 UTC, bachmeier wrote:
 On Tuesday, 3 August 2021 at 15:52:07 UTC, NonNull wrote:
 Here, further down, I just saw Walter indicate his opinion 
 that `alias this` is a mistake. Any thoughts?

 https://news.ycombinator.com/item?id=28029184
The only argument I've heard against alias this is that it can be misused. That's kind of a joke argument in a language that has pointers.
Fyi, 'alias this' nicely fits for doing auto unboxing of a wrapper type into wrapped type, similar to how java does unboxing of primitives, not sure that it will be possible with mixin templates.
It's difficult to beat the simplicity of alias this. Even if something cam be done with mixin templates, that's a lot of overhead vs `alias x this;`. I can't say I use mixin templates very often, but am skeptical that there's no way to misuse them.
Aug 04
next sibling parent reply jmh530 <john.michael.hall gmail.com> writes:
On Wednesday, 4 August 2021 at 14:32:17 UTC, bachmeier wrote:
 [snip]

 It's difficult to beat the simplicity of alias this. Even if 
 something cam be done with mixin templates, that's a lot of 
 overhead vs `alias x this;`. I can't say I use mixin templates 
 very often, but am skeptical that there's no way to misuse them.
I'm sympathetic to this. I think if you got rid of alias this, then you would end up needing something else to accomplish some form of subtyping relationship for structs beyond just composition. For instance, the struct below can be used anywhere an int can be used. Without alias this, you either need to pull out that x value each time it is used in a function or you need to write overloads for all the functions you want to use Foo with like an int. Alternately, you can modify those functions to become generic and handle things that are like an int, but then what if you don't control those functions? It's just so simple. ```d struct Foo { int x; alias x this; } ```
Aug 04
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Aug 04, 2021 at 03:13:07PM +0000, jmh530 via Digitalmars-d wrote:
 On Wednesday, 4 August 2021 at 14:32:17 UTC, bachmeier wrote:
[...]
 It's difficult to beat the simplicity of alias this. Even if
 something cam be done with mixin templates, that's a lot of overhead
 vs `alias x this;`. I can't say I use mixin templates very often,
 but am skeptical that there's no way to misuse them.
I'm sympathetic to this. I think if you got rid of alias this, then you would end up needing something else to accomplish some form of subtyping relationship for structs beyond just composition. For instance, the struct below can be used anywhere an int can be used. Without alias this, you either need to pull out that x value each time it is used in a function or you need to write overloads for all the functions you want to use Foo with like an int. Alternately, you can modify those functions to become generic and handle things that are like an int, but then what if you don't control those functions? It's just so simple. ```d struct Foo { int x; alias x this; } ```
This is exactly why I used to love alias this. It lets me cowboy a wrapper type into code that wasn't originally intended to handle it, and it works wonderfully with minimal code changes. I used to do this all the time in my code, and it let me get away with quick fixes that for the most part worked well. Unfortunately, the long term effect of this sort of hackish solution is a gradual degradation of maintainability and code readability. After a while, when new code needs to be added, it's not clear whether I should use type X, or wrapper type Y with alias this X, or wrapper type Z with alias this Y (chained `alias this` used to be my favorite trick). It became context-dependent -- if I needed special functionality provided by wrapper type Y, then I'd use Y; if Z provided something I needed at the time then I'd use Z. But soon I find out that I need *both* Z and Y in the same function, so my code started to get cluttered with interconversions between these wrappers. Soon I had to invent yet another wrapper type that encompasses *both* Z and Y just to get all the data I need in one place. Which in turn leads to further such issues later down the road. Worse yet, (no) thanks to `alias this`'s super-convenient implicit conversions, half of the code looks like it takes Y but actually receives only X -- it's not immediately obvious because Y implicitly converts to X. So when I need to propagate Y's functionality down into code expecting only X, I find myself in a bind. And unlike actual OO polymorphism there is no equivalent in `alias this` to an upcast: once Y decays to X you cannot get Y back. Which means that now, the code that originally received only X had to be revised to receive Y. Also, code that receives X have no obvious connection to Y: there is no class hierarchy that you can look up to learn possible relationships between class X and class Y -- X can be the target of an implicit `alias this` conversion of literally *any* type anywhere in the program. It's highly unstructured subtyping that hurts long-term code readability and maintainability. Taking a step back from this cascading complexity, I realized that had I *not* used alias this, I would have been forced to consider how to encapsulate the functionality of X, Y *and* Z in a single common type (or a proper class hierarchy) instead. It would have been much less convenient, but in the long run it would have improved code quality: instead of the above spaghetti involving shuffling the same danged data between X, Y, and Z, trying to figure out which type to accept and where implicit conversions are happening, there would have been a single unified type that everyone accepts. There would be no question which (wrapper) type to use because there would be no wrapper types. And there would be no spurious implicit conversions to trip you up when reading the code. The code would be cleaner and more consistent -- instead of various modules accepting different variations on X, Y, and Z, there would be a single type that serves as common currency between all code, eliminating a lot of needless complexity and implicit conversion messiness. This is why, even though I loved alias this (and still do to some extent), I have come to realize that in the long term, it lies more on the negative side of the scale than the positive. T -- It is not the employer who pays the wages. Employers only handle the money. It is the customer who pays the wages. -- Henry Ford
Aug 04
next sibling parent reply jmh530 <john.michael.hall gmail.com> writes:
On Wednesday, 4 August 2021 at 16:45:27 UTC, H. S. Teoh wrote:
 [snip]

 This is why, even though I loved alias this (and still do to 
 some extent), I have come to realize that in the long term, it 
 lies more on the negative side of the scale than the positive.


 T
Goods points throughout. Thanks for your perspective. You describe an issue with upcasting. Would something like this only work in the context of classes and reference types? In other words, suppose alias this was removed from the language, but some more formal inheritance scheme was introduced for structs. Would it be able to have that behavior?
Aug 05
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Aug 05, 2021 at 03:49:25PM +0000, jmh530 via Digitalmars-d wrote:
 On Wednesday, 4 August 2021 at 16:45:27 UTC, H. S. Teoh wrote:
 [snip]
 
 This is why, even though I loved alias this (and still do to some
 extent), I have come to realize that in the long term, it lies more
 on the negative side of the scale than the positive.
[...]
 Goods points throughout. Thanks for your perspective.
 
 You describe an issue with upcasting. Would something like this only
 work in the context of classes and reference types? In other words,
 suppose alias this was removed from the language, but some more formal
 inheritance scheme was introduced for structs. Would it be able to
 have that behavior?
In C++, structs also support OO-style inheritance, and upcasting / downcasting works the same way as in classes. So I suppose it could be made to work. Note, however, that the reason D structs don't support inheritance, AIUI, is because of issues with this feature in C++. One of which is, the size of a variable of that type. Since a derived class implicitly converts to a base class, the same should apply to structs; but what happens when you try to pass a derived struct to a function that takes a base struct? There would not be room to store the derived struct's data, which means you can only pass the base portion of the struct to the function. Which in turn means overridden methods may try to access data that isn't there. IOW, this violates the Liskov Substitution Principle. In C++, this is worked around by requiring passing by reference instead by value. But in D, this kinda defeats the purpose of structs, the whole point of which is a by-value type that gets passed around as "glorified ints", as Andrei calls it. Once you need to pass things around by reference, structs lose their raison d'etre, and you might as well just use a class instead. T -- Just because you can, doesn't mean you should.
Aug 05
next sibling parent reply IGotD- <nise nise.com> writes:
On Thursday, 5 August 2021 at 16:46:53 UTC, H. S. Teoh wrote:
 In C++, this is worked around by requiring passing by reference 
 instead by value. But in D, this kinda defeats the purpose of 
 structs, the whole point of which is a by-value type that gets 
 passed around as "glorified ints", as Andrei calls it.  Once 
 you need to pass things around by reference, structs lose their 
 raison d'etre, and you might as well just use a class instead.
I never understood the benefit of structs are value types. In what use case would you like to pass the value of a struct rather than a reference. The struct will be copied on the stack which is inefficient so in 99% of the cases you would like to use the reference, especially when the parameter is const. If you need a copy the called function could be responsible for that. Could someone explain the benefit of this "glorified int".
Aug 05
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Aug 05, 2021 at 05:24:56PM +0000, IGotD- via Digitalmars-d wrote:
[...]
 I never understood the benefit of structs are value types. In what use
 case would you like to pass the value of a struct rather than a
 reference. The struct will be copied on the stack which is inefficient
 so in 99% of the cases you would like to use the reference, especially
 when the parameter is const. If you need a copy the called function
 could be responsible for that.
 
 Could someone explain the benefit of this "glorified int".
I think your assumption that passing on the stack is inefficient is not necessarily correct. Since the stack is almost constantly being accessed in the course of a program's execution, it remains in cache and so is much more cache-friendly than dereferencing a pointer to the heap. Furthermore, if your struct is small enough, it can be passed entirely in CPU registers, which is much faster than any heap-allocated object can ever hope to be. (And if your struct is too large, esp. if it's so large the compiler has to resort to passing a reference under the hood, you probably should be using a class in the first place.) Also, a stack-allocated object has a well-defined lifetime that's trivially known to the optimizer, so in many cases good optimizing compilers like LDC can entirely elide the stack allocation and keep the struct in registers for its entire lifetime. It's much harder for the optimizer to figure out the lifetime of a heap-allocated object, so it would conservatively skip such optimizations. If performance is important to you, one of the first things you should be looking at is reducing heap usage where possible -- memory management is expensive, whether you use GC or some other method of memory management. Stack allocation is the best because deallocation comes "for free" when the function returns. Using structs instead of classes is a powerful tool for this purpose. T -- Маленькие детки - маленькие бедки.
Aug 05
parent reply wjoe <invalid example.com> writes:
On Thursday, 5 August 2021 at 17:44:48 UTC, H. S. Teoh wrote:
 On Thu, Aug 05, 2021 at 05:24:56PM +0000, IGotD- via 
 Digitalmars-d wrote: [...]
 Since the stack is almost constantly being accessed in the 
 course of a program's execution, it remains in cache and so is 
 much more cache-friendly than dereferencing a pointer to the
This raises the question how does this apply to a CPU that has a L1 cache of 512KiB, a L3 of 4MiB for a program with a stack size of 8MiB ? As far as I'm aware stack memory is residing on the heap so it will need to be allocated on program start up. So in theory... if we were pre-allocating a chunk of memory on the heap at program startup and used that to store our data (like on a stack), the cost of allocation would have been paid, once we need to use it, and would only have to be done once. "Deallocation" cost for data using this "stack" should be getting pretty close to that of *the* stack (which is basically a subtraction). Deallocation cost for the block of heap memory on program termination doesn't matter. In practice the "stack" would probably be closer to a pool and memory management a bit more involved than an addition/subtraction. A cache line is usually much smaller than L1 at just a few data words. So once the pre-fetcher is set up, and the memory in question is residing in L1, there shouldn't be a difference anymore. Therefore I would reason that utilizing cache line bandwidth efficiently is important and whether the data resides on the heap or stack is secondary (i.e. what a struct (doesn't) contain is more important than where it's stored).
Aug 06
next sibling parent Paul Backus <snarwin gmail.com> writes:
On Friday, 6 August 2021 at 12:27:55 UTC, wjoe wrote:
 So in theory...
 if we were pre-allocating a chunk of memory on the heap at 
 program startup and used that to store our data (like on a 
 stack), the cost of allocation would have been paid, once we 
 need to use it, and would only have to be done once. 
 "Deallocation" cost for data using this "stack" should be 
 getting pretty close to that of *the* stack (which is basically 
 a subtraction). Deallocation cost for the block of heap memory 
 on program termination doesn't matter.
 In practice the "stack" would probably be closer to a pool and 
 memory management a bit more involved than an 
 addition/subtraction.

 A cache line is usually much smaller than L1 at just a few data 
 words. So once the pre-fetcher is set up, and the memory in 
 question is residing in L1, there shouldn't be a difference 
 anymore.

 Therefore I would reason that utilizing cache line bandwidth 
 efficiently is important and whether the data resides on the 
 heap or stack is secondary (i.e. what a struct (doesn't) 
 contain is more important than where it's stored).
The thing is, you are already forced to use the "real" stack if you want to take advantage of the CPU's `call` and `ret` instructions. Since you're going to be accessing that region of memory anyway, you might as well store your other "hot" data nearby. That said, there *are* languages that use separate stacks for return addresses and parameter passing, like [Forth][1]. Doing so can be useful on embedded systems with highly restrictive stack-size limits, since it allows you to keep the size of the "real" stack small. [1]: https://en.wikipedia.org/wiki/Forth_(programming_language)
Aug 06
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Fri, Aug 06, 2021 at 12:27:55PM +0000, wjoe via Digitalmars-d wrote:
 On Thursday, 5 August 2021 at 17:44:48 UTC, H. S. Teoh wrote:
[...]
 Since the stack is almost constantly being accessed in the course of
 a program's execution, it remains in cache and so is much more
 cache-friendly than dereferencing a pointer to the
This raises the question how does this apply to a CPU that has a L1 cache of 512KiB, a L3 of 4MiB for a program with a stack size of 8MiB ?
The stack is called the stack, precisely because you generally only access it at the top (or near the top). You generally do not access random parts of it, esp. not the parts near the bottom; most accesses are expected to be near the top. That's the area of the stack that would tend to be in the cache.
 As far as I'm aware stack memory is residing on the heap so it will
 need to be allocated on program start up.
I think you're confusing the terminology here. "Stack" refers to an area of a program's memory that the OS allocates specifically to be used as the runtime stack, the usage pattern of which is expected to be FIFO (first-in, first-out). The "heap" is an additional area (or areas, depending on the implementation of a language's runtime) designated for non-FIFO, ad hoc memory allocation (malloc/free, GC, etc.). In modern OSes, the stack is mapped to its own range of addresses in the program's address space, which, depending on OS settings, may grow as program recursion level increases.
 So in theory...
 if we were pre-allocating a chunk of memory on the heap at program
 startup and used that to store our data (like on a stack), the cost of
 allocation would have been paid, once we need to use it, and would
 only have to be done once. "Deallocation" cost for data using this
 "stack" should be getting pretty close to that of *the* stack (which
 is basically a subtraction).  Deallocation cost for the block of heap
 memory on program termination doesn't matter.
[...] That's missing my point. My point was that the runtime stack is cache-friendly because it's always "hot": the program is practically constantly accessing it: every function call, every `return`, every access of local variables touch the stack, so it practically never leaves the cache. Whereas a user-allocated stack on the heap may be evicted from the cache if it hasn't been accessed in a while. T -- What's a "hot crossed bun"? An angry rabbit.
Aug 06
parent wjoe <invalid example.com> writes:
On Friday, 6 August 2021 at 14:18:30 UTC, H. S. Teoh wrote:
 On Fri, Aug 06, 2021 at 12:27:55PM +0000, wjoe via 
 Digitalmars-d wrote:
 [...]
This explains what I was wondering about for quite a while now. Thanks!
Aug 07
prev sibling next sibling parent tsbockman <thomas.bockman gmail.com> writes:
On Thursday, 5 August 2021 at 17:24:56 UTC, IGotD- wrote:
 I never understood the benefit of structs are value types. In 
 what use case would you like to pass the value of a struct 
 rather than a reference. The struct will be copied on the stack 
 which is inefficient so in 99% of the cases you would like to 
 use the reference, especially when the parameter is const. If 
 you need a copy the called function could be responsible for 
 that.

 Could someone explain the benefit of this "glorified int".
(0) Small structs with one or two small, simple data members can usually be passed in via registers, just like basic types (`int`, `float`, pointers, etc.). This enables efficient custom number, pointer, slice, enum, bit flag, etc. types. For example, on x86-64 `Complex`, below, is passed to `f` in registers: ```D struct Complex { float r, i; } float f(const(Complex) x) { return x.r * x.i; } ``` (1) Forcing all custom aggregate types to be reference types causes aggregate composition to introduce pointless additional indirections, and seriously complicates initialization. For example, suppose we want to make a third type by composing these two: ```D struct A { int x; } struct B { int y; } ``` If `A` and `B` are value types, it looks like this: ```D struct C { A a = A(3); B b = B(7); } ``` The default value can be pre-computed at compile time, and initialization is just a blit of `C.init` into the target address. Each instance of `C` requires 8 bytes. But, if `A` and `B` were reference types, it would look like this: ```D struct C { A a = null; B b = null; this() { a = new A(3); b = new B(7); } } ``` Initialization now requires two allocations, and each instance of `C` requires 16 bytes + at least 4 bytes for the `new A` and at least 4 bytes for the `new B`. I say "at least" 4 bytes because the true minimum heap allocation size for a general-purpose allocator is usually around 16 bytes, and there is some non-trivial overhead for the allocator's internal book-keeping, too. (2) The fact that `class` values exist, but have no formal type in D, requires tons of special code to handle them correctly in certain low-level meta-programming tasks. I'm seriously considering banning `class` from my current project entirely so that I can simplify my custom memory management scheme.
Aug 06
prev sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Thursday, 5 August 2021 at 17:24:56 UTC, IGotD- wrote:
 I never understood the benefit of structs are value types. In 
 what use case would you like to pass the value of a struct 
 rather than a reference.
Functional programming requires you to conceptually pass by value, then passing it by reference becomes an optimization. But in D this is lost since you also can pass by reference. So there is no advantage to having the struct/class split.
Aug 06
parent IGotD- <nise nise.com> writes:
On Saturday, 7 August 2021 at 06:05:02 UTC, Ola Fosheim Grøstad 
wrote:
 Functional programming requires you to conceptually pass by 
 value, then passing it by reference becomes an optimization. 
 But in D this is lost since you also can pass by reference. So 
 there is no advantage to having the struct/class split.
It's an interesting aspect of language design which seems to proliferate almost all imperative languages despite the main use is not functional programming. Rather having the compiler optimizing the parameter passing (which some do underneath anyway), default is copy and it has been like that for decades. Not sure what the historical impact is, maybe that early CPUs had few registers and the stack was the only choice. In the case of functional programming, I would just let the programmer manually copy the struct inside the function body as no assumption can be made if the struct is passed as reference (pointer) or in registers, or just have a keyword that forces the struct to be copied on stack.
Aug 07
prev sibling parent jmh530 <john.michael.hall gmail.com> writes:
On Thursday, 5 August 2021 at 16:46:53 UTC, H. S. Teoh wrote:
 [snip]

 In C++, structs also support OO-style inheritance, and 
 upcasting / downcasting works the same way as in classes.  So I 
 suppose it could be made to work.

 [...]
Makes sense. Thanks.
Aug 05
prev sibling parent 12345swordy <alexanderheistermann gmail.com> writes:
On Wednesday, 4 August 2021 at 16:45:27 UTC, H. S. Teoh wrote:
 On Wed, Aug 04, 2021 at 03:13:07PM +0000, jmh530 via 
 Digitalmars-d wrote:
 On Wednesday, 4 August 2021 at 14:32:17 UTC, bachmeier wrote:
[...]
 It's difficult to beat the simplicity of alias this. Even if 
 something cam be done with mixin templates, that's a lot of 
 overhead vs `alias x this;`. I can't say I use mixin 
 templates very often, but am skeptical that there's no way 
 to misuse them.
I'm sympathetic to this. I think if you got rid of alias this, then you would end up needing something else to accomplish some form of subtyping relationship for structs beyond just composition. For instance, the struct below can be used anywhere an int can be used. Without alias this, you either need to pull out that x value each time it is used in a function or you need to write overloads for all the functions you want to use Foo with like an int. Alternately, you can modify those functions to become generic and handle things that are like an int, but then what if you don't control those functions? It's just so simple. ```d struct Foo { int x; alias x this; } ```
This is exactly why I used to love alias this. It lets me cowboy a wrapper type into code that wasn't originally intended to handle it, and it works wonderfully with minimal code changes. I used to do this all the time in my code, and it let me get away with quick fixes that for the most part worked well. Unfortunately, the long term effect of this sort of hackish solution is a gradual degradation of maintainability and code readability. After a while, when new code needs to be added, it's not clear whether I should use type X, or wrapper type Y with alias this X, or wrapper type Z with alias this Y (chained `alias this` used to be my favorite trick). It became context-dependent -- if I needed special functionality provided by wrapper type Y, then I'd use Y; if Z provided something I needed at the time then I'd use Z. But soon I find out that I need *both* Z and Y in the same function, so my code started to get cluttered with interconversions between these wrappers. Soon I had to invent yet another wrapper type that encompasses *both* Z and Y just to get all the data I need in one place. Which in turn leads to further such issues later down the road. Worse yet, (no) thanks to `alias this`'s super-convenient implicit conversions, half of the code looks like it takes Y but actually receives only X -- it's not immediately obvious because Y implicitly converts to X. So when I need to propagate Y's functionality down into code expecting only X, I find myself in a bind. And unlike actual OO polymorphism there is no equivalent in `alias this` to an upcast: once Y decays to X you cannot get Y back. Which means that now, the code that originally received only X had to be revised to receive Y. Also, code that receives X have no obvious connection to Y: there is no class hierarchy that you can look up to learn possible relationships between class X and class Y -- X can be the target of an implicit `alias this` conversion of literally *any* type anywhere in the program. It's highly unstructured subtyping that hurts long-term code readability and maintainability. Taking a step back from this cascading complexity, I realized that had I *not* used alias this, I would have been forced to consider how to encapsulate the functionality of X, Y *and* Z in a single common type (or a proper class hierarchy) instead. It would have been much less convenient, but in the long run it would have improved code quality: instead of the above spaghetti involving shuffling the same danged data between X, Y, and Z, trying to figure out which type to accept and where implicit conversions are happening, there would have been a single unified type that everyone accepts. There would be no question which (wrapper) type to use because there would be no wrapper types. And there would be no spurious implicit conversions to trip you up when reading the code. The code would be cleaner and more consistent -- instead of various modules accepting different variations on X, Y, and Z, there would be a single type that serves as common currency between all code, eliminating a lot of needless complexity and implicit conversion messiness. This is why, even though I loved alias this (and still do to some extent), I have come to realize that in the long term, it lies more on the negative side of the scale than the positive. T
Speaking of weird edge case. This code compiles. struct TIntStatic { static int mX; static property int x() { return mX; } static property void x(int v) { mX = v; } alias x this; } alias t = TIntStatic; t = 5; // According to Walter bright this shouldn't compile, yet it does.
Aug 07
prev sibling parent IGotD- <nise nise.com> writes:
On Wednesday, 4 August 2021 at 14:32:17 UTC, bachmeier wrote:
 It's difficult to beat the simplicity of alias this. Even if 
 something cam be done with mixin templates, that's a lot of 
 overhead vs `alias x this;`. I can't say I use mixin templates 
 very often, but am skeptical that there's no way to misuse them.
I think that mixin templates beats alias this. You can only have one alias this which I think is a big disadvantage but you can have several mixin templates. You can also decide the visibility (or scope) for mixin templates. You want your mixin template to be visible outwards you just write. mixin MyTemplate; If you want to just use your mixin template inside your class/struct internally you can do that. mixin MyTemplate instance; which also enables several instances of the same mixin template. mixin MyTemplate instance1; mixin MyTemplate instance2; Few D programmers know about this and it is badly documented. Also what happens with constructors/deconstructors doesn't seem to be documented at all. I'm disappointed with the documentation as mixin template is one of the central features of D (or should be). If we can point out to people in the documentation that alias this will be deprecated and link to mixin templates. Having a good tutorial/documentation about mixin templates and I think the discussions over alias this will be gone and people will adopt mixin templates instead.
Aug 05
prev sibling next sibling parent reply Mike <slavo5150 yahoo.com> writes:
On Tuesday, 3 August 2021 at 15:52:07 UTC, NonNull wrote:
 Here, further down, I just saw Walter indicate his opinion that 
 `alias this` is a mistake. Any thoughts?

 https://news.ycombinator.com/item?id=28029184
`alias this` don't have much value for classes, as the same can be achieved with interfaces, inheritance, and D's killer metaprogramming facilities. Where `alias this` does have value is with structs. My suggestion has been to deprecate (or at least discourage) `alias this` for classes, but invest more in `alias this` for structs (e.g. add multiple `alias this` features for structs). More on that can be read at https://forum.dlang.org/post/vggskphkqxtriqnavmnf forum.dlang.org
Aug 03
parent Nick Treleaven <nick geany.org> writes:
On Wednesday, 4 August 2021 at 00:29:21 UTC, Mike wrote:
 My suggestion has been to deprecate (or at least discourage) 
 `alias this` for classes,
It might be useful to allow `alias this` in a `final class` for when you want a wrapper that is a reference type.
 More on that can be read at 
 https://forum.dlang.org/post/vggskphkqxtriqnavmnf forum.dlang.org
Thanks.
Aug 04
prev sibling next sibling parent Guillaume Piolat <first.last gmail.com> writes:
On Tuesday, 3 August 2021 at 15:52:07 UTC, NonNull wrote:
 Here, further down, I just saw Walter indicate his opinion that 
 Any thoughts?
It gives limited value while having insane edge cases, hence a mistake.
Aug 04
prev sibling parent TheGag96 <thegag96 gmail.com> writes:
On Tuesday, 3 August 2021 at 15:52:07 UTC, NonNull wrote:
 Here, further down, I just saw Walter indicate his opinion that 
 `alias this` is a mistake. Any thoughts?

 https://news.ycombinator.com/item?id=28029184
This is just surprising to me. I think `alias this` is a pretty cool thing... I use it within a mixin template (among stuff other stuff) in order to roll my own implementation of inheritance for entities in my game engine. Might be a bit goofy, but it does work pretty well for my hobbyist purposes, at least. How else are we going to get any kind of smooth "subtyping" with structs? For what it's worth, the Jai language [is lifting the idea wholesale](https://youtu.be/MXNkQ5L4pLo) with its `using` syntax. I think it has support for more than one of them as well. If it's not a good idea today, how can we make it one?
Aug 05