www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Proper way to handle "alias this" deprecation for classes

reply Chris Piker <chris hoopjump.com> writes:
Hi D

One of the dependencies for my project has a class that makes use 
of the `alias x this` construct.  According to dmd 2.103, alias 
this is deprecated for classes, so I'd like to correct the 
problem.

Is there a specific paragraph or two that I can read to find out 
what is the appropriate replacement construct?  On a related 
note, has anyone created a code maintenance guide to help 
work-a-day programmers navigate recent changes to the D language?

For reference, here's a the code in question (from dpq2, 
result.d):
```d
package immutable final class ResultContainer
{
     version(Dpq2_Dynamic)
     {
         import dpq2.dynloader: ReferenceCounter;

         private ReferenceCounter dynLoaderRefCnt;
     }

     // ResultContainer allows only one copy of PGresult* due to 
avoid
     // double free. For the same reason this class is declared as 
final.
     private PGresult* result;
     alias result this;       //<---- Deprecation Warning Here
     package this(immutable PGresult* r)
     {
         assert(r);

         result = r;
         version(Dpq2_Dynamic) dynLoaderRefCnt = 
ReferenceCounter(true);
     }

     ...
```

Thanks for any pointers,
May 07 2023
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 5/7/23 10:55, Chris Piker wrote:

 According to dmd 2.103, alias this is
 deprecated for classes, so I'd like to correct the problem.
alias this is for implicit type conversions, which can be achieved explicitly as well. Given the following old code: class C { int* result; alias result this; } void foo(int*) { } auto main() { auto c = new C(); // Implicit type conversion from C to int*: foo(c); } One can use a member function instead: class C { int* result; auto asIntPtr() { return result; } } void foo(int*) { } auto main() { auto c = new C(); // The same type conversion is now explicit: foo(c.asIntPtr); } Ali
May 07 2023
next sibling parent reply Chris Piker <chris hoopjump.com> writes:
On Sunday, 7 May 2023 at 18:19:04 UTC, Ali Çehreli wrote:
 auto main() {
     auto c = new C();

     // The same type conversion is now explicit:
     foo(c.asIntPtr);
 }
Hi Ali Ah, very clear explanation, thanks! So basically to fix the problem I just delete the alias this line from dpq2, see what unit tests and app code it breaks, then fix each of those. Actually seems straightforward, for a limited code base anyway. On a side note, with all the free help you've provided, it's about time I gave back. Since I've no free time or expertise to offer, I picked up a hardcover copy of "Programming in D" for a bright young programming student I know in appreciation. Cheers,
May 07 2023
parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 5/7/23 13:44, Chris Piker wrote:

 to fix the problem I
 just delete the alias this line from dpq2, see what unit tests and app
 code it breaks, then fix each of those.
Yes but I neglected the lvalue/rvalue issue. In some cases the code won't compile if the return type of the newly written explicit function is not 'ref'. For example, the 'ref' below is essential for the following use case: class C { int* result; // HERE ref asIntPtr() { return result; } } auto main() { auto c = new C(); int i; c.asIntPtr = &i; assert(c.result == &i); }
 "Programming in D" for a bright young programming
 student I know in appreciation.
Very kind of you! :) Ali
May 07 2023
prev sibling parent reply Inkrementator <anon anon.org> writes:
On Sunday, 7 May 2023 at 18:19:04 UTC, Ali Çehreli wrote:
 alias this is for implicit type conversions, which can be 
 achieved explicitly as well.
Open question to everybody: What you're opinion on using opCast for this? Since it's a type conversion, it seems fitting to me. And another suggestion: Wrap the class in a struct that has visibility on the class members via the "package" access specifier and continue using "alias this".
May 07 2023
next sibling parent reply Chris Piker <chris hoopjump.com> writes:
On Sunday, 7 May 2023 at 21:04:05 UTC, Inkrementator wrote:
 On Sunday, 7 May 2023 at 18:19:04 UTC, Ali Çehreli wrote:
 alias this is for implicit type conversions, which can be 
 achieved explicitly as well.
Open question to everybody: What you're opinion on using opCast for this? Since it's a type conversion, it seems fitting to me. And another suggestion: Wrap the class in a struct that has visibility on the class members via the "package" access specifier and continue using "alias this".
Hi Inkrementator In this case, ResponseContainer is already a wrapper structure, so on the surface, putting a wrapper on a wrapper feels like over-engineering. On the other hand, your first suggestion of using opCast() does seem like a reasonable choice to me. Can you provide a short code snippet using opCast to achieve the same result?
May 07 2023
parent reply Inkrementator <invalid email.com> writes:
On Sunday, 7 May 2023 at 21:12:22 UTC, Chris Piker wrote:
 On the other hand, your first suggestion of using opCast() does 
 seem like a reasonable choice to me.  Can you provide a short 
 code snippet using opCast to achieve the same result?
I've never used it, and particularly I know that I don't know whether making the the return type of opCast ref can lead to issues down the road and whether the template specialization is dangerous, as it will trigger for all types implicitly convertible to int* (whatever they are) Another potential future pitfall I discovered is that code might break if you change your class into a struct. Code example is in a gist since the system thinks I've added HTML entities to it and won't let me post it: https://gist.github.com/run-dlang/9b7aec72710b1108fc8277789776962a
May 10 2023
parent reply Chris Piker <chris hoopjump.com> writes:
On Wednesday, 10 May 2023 at 14:42:50 UTC, Inkrementator wrote:
 On Sunday, 7 May 2023 at 21:12:22 UTC, Chris Piker wrote:

 https://gist.github.com/run-dlang/9b7aec72710b1108fc8277789776962a
Thanks for posting that. Reading over the code I'm reminded that I never cared whether something was an rvalue or lvalue before writing D code. It's off topic, but I forget why managing memory for rvalues* was pushed onto the programmer and not handled by the compiler. I'm sure there is a good reason but it does seem like a symmetry breaking requirement. -- *or was it lvalues, I can never keep the two separate. Wish the some other terminology was adopted long ago, such as "named" vs. "ephemeral".
May 10 2023
parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Wed, May 10, 2023 at 03:24:48PM +0000, Chris Piker via Digitalmars-d-learn
wrote:
[...]
 It's off topic, but I forget why managing memory for rvalues* was
 pushed onto the programmer and not handled by the compiler.  I'm sure
 there is a good reason but it does seem like a symmetry breaking
 requirement.
 
 --
 *or was it lvalues, I can never keep the two separate.  Wish the some
 other terminology was adopted long ago, such as "named" vs.
 "ephemeral".
x = y; ^ ^ | | lvalue rvalue An lvalue is simply something that can appear on the *l*eft side of an assignment statement, and an rvalue is something that appears on the *r*ight side of an assignment statement. It seems trivially obvious, but has far-reaching consequences. For one thing, to be an lvalue means that you must be able to assign a value to it. I.e., it must be a variable that exists somewhere in memory; `1 = x;` is illegal because `1` is a literal with no memory associated with it, so you cannot assign a new value to it. For something to be an rvalue means that it's a value like `1` that may not necessarily have a memory address associated with it. For example, the value of a computation is an rvalue: // This is OK: x = y + 1; // This is not OK: (y + 1) = x; The value of a computation cannot be assigned to, it makes no sense. Therefore, given an rvalue, you are not guaranteed that assignment is legal. Note however, that given an lvalue, you can always get an rvalue out of it. In the first example above, `y` can be an lvalue because it's a variable with a memory location. However, it can also be used as an rvalue. Or, if you like, `x = y;` contains an implicit "cast" of y to an rvalue. But you can never turn an rvalue back into an lvalue. T -- It's bad luck to be superstitious. -- YHL
May 10 2023
parent reply Chris Piker <chris hoopjump.com> writes:
On Wednesday, 10 May 2023 at 16:01:40 UTC, H. S. Teoh wrote:
 	x   =   y;
 	^       ^
 	|       |
 	lvalue  rvalue
...
 	// This is OK:
 	x = y + 1;

 	// This is not OK:
 	(y + 1) = x;
Thanks for the clear explanation. My problem with the terms lvalue and rvalue is much more basic, and is just a personal one that only affects probably 0.1% of people. I just can't keep left vs. right straight in real life. "Right" in my head always means "correct". My daughter hates it when I'm telling her which way to turn the car since I've said the wrong direction so many times. :)
May 10 2023
parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Wed, May 10, 2023 at 07:56:10PM +0000, Chris Piker via Digitalmars-d-learn
wrote:
[...]
 My problem with the terms lvalue and rvalue is much more basic, and is
 just a personal one that only affects probably 0.1% of people.  I just
 can't keep left vs. right straight in real life.  "Right" in my head
 always means "correct".
 
 My daughter hates it when I'm telling her which way to turn the car
 since I've said the wrong direction so many times. :)
I also suffer from left/right confusion, and always have to pause to think about which is the right(!) word before uttering it. :-D Would compass directions be more helpful? (wvalue vs. evalue) Or would it suffer from the same problem? (One could retroactively rationalize it as *w*ritable value vs. *e*phemeral value. :-P) T -- By understanding a machine-oriented language, the programmer will tend to use a much more efficient method; it is much closer to reality. -- D. Knuth
May 10 2023
parent reply Chris Piker <chris hoopjump.com> writes:
On Wednesday, 10 May 2023 at 20:25:48 UTC, H. S. Teoh wrote:
 On Wed, May 10, 2023 at 07:56:10PM +0000, Chris Piker via 
 Digitalmars-d-learn wrote: [...]
 I also suffer from left/right confusion, and always have to 
 pause to think about which is the right(!) word before uttering 
 it.
Oh, I though was the only one with that difficulty. Glad to hear I'm not alone. :-) I have a tendency to think of things by their purpose when programming but not by their location on the line or page. So terms such as "writable" versus "ephemeral" or "addressable" versus "temporary" (or "register"), make so much more sense to me. Back on the ref issue for a moment... I'd imagine that asking the compiler to delay creating a writable variable until it finds out that a storage location is actually needed by subsequent statements, is a tall order. So D chose to introduce programmers to lvalues and rvalues head-on, instead of creating a leaky abstraction.
May 10 2023
parent "H. S. Teoh" <hsteoh qfbox.info> writes:
On Wed, May 10, 2023 at 10:57:13PM +0000, Chris Piker via Digitalmars-d-learn
wrote:
 On Wednesday, 10 May 2023 at 20:25:48 UTC, H. S. Teoh wrote:
 On Wed, May 10, 2023 at 07:56:10PM +0000, Chris Piker via
 Digitalmars-d-learn wrote: [...]
 I also suffer from left/right confusion, and always have to pause to
 think about which is the right(!) word before uttering it.
Oh, I though was the only one with that difficulty. Glad to hear I'm not alone. :-)
:-)
 I have a tendency to think of things by their purpose when programming
 but not by their location on the line or page.  So terms such as
 "writable" versus "ephemeral" or "addressable" versus "temporary" (or
 "register"), make so much more sense to me.
Yeah TBH I was never a fan of the lvalue/rvalue terminology. In a hypothetical language where the arguments to an assignment operator is reversed, the terminology would become needlessly confusing. E.g., if there was an operator `X => Y;` that means "assign the value of X to Y", then the roles of lvalues/rvalues would be reversed.
 Back on the ref issue for a moment... I'd imagine that asking the
 compiler to delay creating a writable variable until it finds out that
 a storage location is actually needed by subsequent statements, is a
 tall order. So D chose to introduce programmers to lvalues and rvalues
 head-on, instead of creating a leaky abstraction.
It depends on how you look at it. The very concept of a variable in memory is actually already an abstraction. Modern compilers may enregister variables or even completely elide them. Assignments may be reordered, and the CPU may execute things out-of-order (as long as semantics are preserved). Intermediate values may not get stored at all, but get folded into the larger computation and perhaps merged with some other operation with the resulting compound operation mapped to a single CPU instruction, etc.. So in that sense the compiler is quite capable of figuring out what to do... But what it can't do is read the programmer's mind to deduce the intent of his code. Exact semantics must be somehow conveyed to the compiler, and sad to say humans aren't very good at being exact. Often we *think* we know exactly what the computation is, but in reality we gloss over low-level details that will make a big difference in the outcome of the computation in the corner cases. The whole rvalue/lvalue business is really more a way of conveying to the compiler what exactly must happen, rather than directly corresponding to any actual feature in the underlying physical machine. T -- Computerese Irregular Verb Conjugation: I have preferences. You have biases. He/She has prejudices. -- Gene Wirchenko
May 10 2023
prev sibling parent reply Salih Dincer <salihdb hotmail.com> writes:
On Sunday, 7 May 2023 at 21:04:05 UTC, Inkrementator wrote:
 Open question to everybody: What you're opinion on using opCast 
 for this? Since it's a type conversion, it seems fitting to me.
Can't converting without explicitly specifying in D is a big shortcoming in my opinion. There is such a thing as implicitly convertion though, but it's very confusing. I don't see that simplicity in C++ in the D codes! ```CPP #include <iostream> using namespace std; struct Fraction { int num, den; Fraction(int n, int d) { num = n; den = d; } // Conversion operator: return float value of fraction operator float() const { return float(num) / float(den); } }; int main() { Fraction f(2, 5); float val = f; cout << val << '\n'; // 0.4 return 0; } ``` You should do the same in D like this: ```d struct Fraction { int num, den; this(int n, int d) { num = n; den = d; } // Cast Expression : convert float value of fraction auto opCast(T : float)() const { return cast(float)(num) / cast(float)(den); } } import std.stdio; int main() { auto f = Fraction(2, 5); float val = cast(float)f; val.writeln; //0.4 return 0; } ``` SDB 79
May 12 2023
parent reply Dom DiSc <dominikus scherkl.de> writes:
On Friday, 12 May 2023 at 15:00:48 UTC, Salih Dincer wrote:

 ```d
 struct Fraction {
 	int num, den;

 	this(int n, int d)
 	{
 		num = n;
 		den = d;
 	}

 	// Cast Expression : convert float value of fraction
 	auto opCast(T : float)() const
 	{
 		return cast(float)(num) / cast(float)(den);
 	}
 }
 ```
If you want auto-conversion, you should be more explicit: ```d float opCast() { } ``` because if you return "auto" it is not the highest-prio fit and therefore not chosen. If you have multiple choices, you still don't need to use "auto". Instead you can return T: ```d T opCast(T)() if(isFloatingPoint!T) { return cast(T)num / cast(T)den; // float, double or real } ``` Kind of ironic, but especially "auto" does NOT fit automatically :-)
May 17 2023
parent Salih Dincer <salihdb hotmail.com> writes:
On Wednesday, 17 May 2023 at 08:00:17 UTC, Dom DiSc wrote:
 If you want auto-conversion, you should be more explicit:
 ```d
 float opCast() { }
 ```
 because if you return "auto" it is not the highest-prio fit and 
 therefore not chosen.
 If you have multiple choices, you still don't need to use 
 "auto". Instead you can return T:
 ```d
 T opCast(T)() if(isFloatingPoint!T)
 {
    return cast(T)num / cast(T)den; // float, double or real
 }
 ```
 Kind of ironic, but especially "auto" does NOT fit 
 automatically :-)
Sorry for not expressing myself better. Let me try to explain with another example: ```d struct RightShift {  int num, shr;  T opCast(T : char)()   => cast(T)(num >> shr); } import std.stdio; void main() { auto f = RightShift(128, 2);  char chr = cast(char)f | '\1'; assert(chr == '!');    //char test = f; // Error: cannot implicitly convert expression  //assert(test == ' '); // `f` of type `RightShift` to `char` } ``` This snippet works fine. But hidden the code gives an error. The reason is that the compiler ignores the explicitly specified lines without using auto. SDB 79
May 17 2023