www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Casting by assigning to the right ...

reply Manfred Nowak <svv1999 hotmail.com> writes:
... is already builtin via properties.

After declaring
```
struct S{ int data;}
S s;
int i;
```

one often would like to implicitly cast the value of the 
structure by typing
```
i= s;
```

According to the logged changes ( 
https://digitalmars.com/d/2.0/changelog.html) a thought on this 
was published in 2008/03 for D2.012: `opImplicitCast', but not 
implemented since.

Therefore one still has to introduce an `opCast' into `S' and 
then write
```
i= cast( int)  s;
```

I do not like to always correctly examine the type of the 
variable I am assigning to ( `i' in this case) for using that 
type in the `cast()' and came up with the idea to no more assign 
to the left but to the right:
```
s.castTo =i;
```

Of course one has to define the property `castTo' within `S', but 
that has the signature
   `void castTo( ref int arg)'
which is as simple as the signature of `opCast'. A runnable 
example follows. What do you think?


```
struct S{
   string data;
   int opCast( T: int)(){
       import std.conv: to;
     return to!int( data);
   }
   void castTo( ref int arg){
     arg= cast( int)  this;
   }
}
void main(){
   auto s= S( "42");
   int i;
   s.castTo =i;
     import std.stdio;
   writeln( i);
}
```
Apr 13
next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 4/14/20 1:56 AM, Manfred Nowak wrote:
 According to the logged changes ( 
 https://digitalmars.com/d/2.0/changelog.html) a thought on this was 
 published in 2008/03 for D2.012: `opImplicitCast', but not implemented 
 since.
The feature was implemented in a way, it's called alias this: struct S { int i; alias i this; } auto s = S(5); int i = s; assert(i == 5); Note that at this time, only one alias this is allowed per structure. -Steve
Apr 13
parent reply Manfred Nowak <svv1999 hotmail.com> writes:
On Tuesday, 14 April 2020 at 06:10:33 UTC, Steven Schveighoffer 
wrote:
 Note that at this time, only one alias this is allowed per 
 structure.
This restriction falls by the suggested scheme: ``` struct S{ string data; import std.conv: to; void castTo( ref int arg){ arg= to!int( data); } void castTo( ref string arg){ arg= data; } } void main(){ auto s= S( "42"); int i; s.castTo =i; string str; s.castTo =str; import std.stdio; writeln( i, ":", str); // 42:42 } ```
Apr 13
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 4/14/20 2:44 AM, Manfred Nowak wrote:
 On Tuesday, 14 April 2020 at 06:10:33 UTC, Steven Schveighoffer wrote:
 Note that at this time, only one alias this is allowed per structure.
This restriction falls by the suggested scheme: ``` struct S{   string data;     import std.conv: to;   void castTo( ref int arg){     arg= to!int( data);   }   void castTo( ref string arg){     arg= data;   } } void main(){   auto s= S( "42");   int i;   s.castTo =i;   string str;   s.castTo =str;     import std.stdio;   writeln( i, ":", str); // 42:42 } ```
Yes, I was responding to the point talking about opImplicitCast. Instead of that, we got alias this. Proposals have been started to add multiple alias this. I think many consider alias this in general to be a troublesome feature that could have been done better. I don't know what will happen with it. I would recommend not using the = syntax to denote the opposite of what it normally means (that the rhs is being assigned). You are better off I think just calling it like a function: s.castTo(i); s.castTo(str); Without knowing what castTo is supposed to do, if I saw s.castTo = i, I would not expect at all that i is going to change. -Steve
Apr 14
next sibling parent Manfred Nowak <svv1999 hotmail.com> writes:
On Tuesday, 14 April 2020 at 12:28:00 UTC, Steven Schveighoffer 
wrote:

 Without knowing what castTo is supposed to do, if I saw 
 s.castTo = i, I would not expect at all that i is going to 
 change.
What would you expect when you see ``` i ._= s; ``` which is also currently legal code in Dlang?
Apr 14
prev sibling parent 12345swordy <alexanderheistermann gmail.com> writes:
On Tuesday, 14 April 2020 at 12:28:00 UTC, Steven Schveighoffer 
wrote:
 On 4/14/20 2:44 AM, Manfred Nowak wrote:
 On Tuesday, 14 April 2020 at 06:10:33 UTC, Steven 
 Schveighoffer wrote:
 Note that at this time, only one alias this is allowed per 
 structure.
This restriction falls by the suggested scheme: ``` struct S{   string data;     import std.conv: to;   void castTo( ref int arg){     arg= to!int( data);   }   void castTo( ref string arg){     arg= data;   } } void main(){   auto s= S( "42");   int i;   s.castTo =i;   string str;   s.castTo =str;     import std.stdio;   writeln( i, ":", str); // 42:42 } ```
Yes, I was responding to the point talking about opImplicitCast. Instead of that, we got alias this. Proposals have been started to add multiple alias this. I think many consider alias this in general to be a troublesome feature that could have been done better. I don't know what will happen with it. I would recommend not using the = syntax to denote the opposite of what it normally means (that the rhs is being assigned). You are better off I think just calling it like a function: s.castTo(i); s.castTo(str); Without knowing what castTo is supposed to do, if I saw s.castTo = i, I would not expect at all that i is going to change. -Steve
I consider alias this to be a mistake. There are simply too many bugs for it, as it does more than implicit conversion. The dlang would be better off if it were to implement opImplicitCast for lvalues while implicit constructors for rvalues. While putting alias this into maintenance mode, and discouraging future use of it. We make it opt-in for functions to avoid the same mistake as c++ did.
Apr 14
prev sibling next sibling parent reply Mathias LANG <geod24 gmail.com> writes:
On Tuesday, 14 April 2020 at 05:56:39 UTC, Manfred Nowak wrote:
 ... is already builtin via properties.

 After declaring
 ```
 struct S{ int data;}
 S s;
 int i;
 ```

 one often would like to implicitly cast the value of the 
 structure by typing
 ```
 i= s;
 ```
There's two easy ways to accomplish this: ``` struct S { int data_; version (OpAssign) ref S opAssign (int v) { this.data_ = v; return this; } version (AliasThis) { property ref int data (int v) { this.data_ = v; return this.data_; } alias data this; } } void main () { S s; int i; s = i; } ``` Which one you pick depends on your design. I would recommend the `OpAssign` version by default.
Apr 13
parent Manfred Nowak <svv1999 hotmail.com> writes:
On Tuesday, 14 April 2020 at 06:15:14 UTC, Mathias LANG wrote:
[...]
       ref S opAssign (int v) { this.data_ = v; return this; }
[...]
     s = i;
With `opAssign' the value of `i' is assigned to `this.data_', but that is not the intended direction. From `this.data_' to `i' is intended ... and for several more types of variables. Please see my reply to Steven: https://forum.dlang.org/post/qdzvuduycklkhjesnyei forum.dlang.org
Apr 14
prev sibling next sibling parent reply aliak <something something.com> writes:
On Tuesday, 14 April 2020 at 05:56:39 UTC, Manfred Nowak wrote:
 ... is already builtin via properties.

 After declaring
 ```
 struct S{ int data;}
 S s;
 int i;
 ```

 one often would like to implicitly cast the value of the 
 structure by typing
 ```
 i= s;
 ```

 According to the logged changes ( 
 https://digitalmars.com/d/2.0/changelog.html) a thought on this 
 was published in 2008/03 for D2.012: `opImplicitCast', but not 
 implemented since.

 Therefore one still has to introduce an `opCast' into `S' and 
 then write
 ```
 i= cast( int)  s;
 ```

 I do not like to always correctly examine the type of the 
 variable I am assigning to ( `i' in this case) for using that 
 type in the `cast()' and came up with the idea to no more 
 assign to the left but to the right:
 ```
 s.castTo =i;
 ```

 Of course one has to define the property `castTo' within `S', 
 but that has the signature
   `void castTo( ref int arg)'
 which is as simple as the signature of `opCast'. A runnable 
 example follows. What do you think?


 ```
 struct S{
   string data;
   int opCast( T: int)(){
       import std.conv: to;
     return to!int( data);
   }
   void castTo( ref int arg){
     arg= cast( int)  this;
   }
 }
 void main(){
   auto s= S( "42");
   int i;
   s.castTo =i;
     import std.stdio;
   writeln( i);
 }
 ```
Always a fan of generalizing patterns as long as they are readable! So I would agree with Steven that s.castTo = i would be a bit confusing to review. And: s.castTo(i); would also be confusing in that it sounds like it's casting s to i as in, turning s in to an i. But it's hard to know what that can actually mean. Also, you can make it a generic free function if you want. I.e. turn this: struct S { void castTo( ref int arg){ arg= cast( int) this; } } to: void castTo(Source, Dest)(ref Source source, ref Dest dest) { dest = cast(Dest)source; } And now you can have your i on the left hand side and maybe name it something like castAssign and have code that reads like i.castAssign(s); // cast and assign s
Apr 14
parent reply Manfred Nowak <svv1999 hotmail.com> writes:
On Tuesday, 14 April 2020 at 13:03:19 UTC, aliak wrote:
 Also, you can make it a generic free function if you want.
[...]
 i.castAssign(s); // cast and assign s
And because the pair of parenthesis can by exchanged by a `=', giving `i .castAssign= s;' you just evaluated, that there is in addition to {+, -, *, /, %, ^^, &, |, ^, <<, >>, >>>, ~} an additional bunch of ops for the op-assignment-operator giving the resulting form in regular language parlor: '.' ident '=' Allowing ident to be an empty string would even include `.=' to be recognized as an assignment operator. This is very close to the assignment operator in pascal `:='. What is the better annotation of a coder knowing, that a cast is being executed under the hood: `._=' or `:='?
Apr 14
parent reply aliak <something something.com> writes:
On Tuesday, 14 April 2020 at 21:45:22 UTC, Manfred Nowak wrote:
 On Tuesday, 14 April 2020 at 13:03:19 UTC, aliak wrote:
 Also, you can make it a generic free function if you want.
[...]
 i.castAssign(s); // cast and assign s
And because the pair of parenthesis can by exchanged by a `=', giving `i .castAssign= s;' you just evaluated, that there is in addition to {+, -, *, /, %, ^^, &, |, ^, <<, >>, >>>, ~} an additional bunch of ops for the op-assignment-operator giving the resulting form in regular language parlor: '.' ident '=' Allowing ident to be an empty string would even include `.=' to be recognized as an assignment operator. This is very close to the assignment operator in pascal `:='. What is the better annotation of a coder knowing, that a cast is being executed under the hood: `._=' or `:='?
I don't think either of those tell me about a cast happening. Maaaaaybe .cast= but I'm not sure and it feels weird.
Apr 15
parent reply Manfred Nowak <svv1999 hotmail.com> writes:
On Wednesday, 15 April 2020 at 07:44:23 UTC, aliak wrote:
 [...] it feels weird.
Yes. But `._=' is legal Dlang. In C one would have to annotate the possibility of writing to a parameter by using `&' to get the address of the variable. Would the legal Dlang phrase `._=&' make the intention more clear? ------ struct S{ string data; import std.conv: to; void _( ref int arg){ arg= to!int( data) *10; } void _( real* arg){ *arg= to!real( data) *10 +1; } } void main(){ auto s= S( "42"); int i; s ._= i; real r; s ._=& r; import std.stdio; writeln( i, ":", r); // 420:421 }
Apr 15
next sibling parent reply aliak <something something.com> writes:
On Wednesday, 15 April 2020 at 09:56:02 UTC, Manfred Nowak wrote:
 On Wednesday, 15 April 2020 at 07:44:23 UTC, aliak wrote:
 [...] it feels weird.
Yes. But `._=' is legal Dlang.
Yes, but I'm not sure what the legality of syntax has to do with readability/maintainability/etc?
 In C one would have to annotate the possibility of writing to a 
 parameter by using `&' to get the address of the variable.

 Would the legal Dlang phrase `._=&' make the intention more 
 clear?
Not to me. Custom operators are generally very hard to get right. It's one of D's good decisions to limit them (not that they're perfect, there are still inconsistencies to fix but aside from that, they are a good decision) Elm recently (well, last year or two was it?) did away with allowing cryptic operators as well and there's a writeup by Elm's author: https://gist.github.com/evancz/769bba8abb9ddc3bf81d69fa80cc76b1
 ------

 struct S{
   string data;
     import std.conv: to;
   void _( ref int arg){
     arg= to!int( data) *10;
   }
   void _( real* arg){
     *arg= to!real( data) *10 +1;
   }
 }
 void main(){
   auto s= S( "42");

   int i;
   s  ._=  i;

   real r;
   s  ._=&  r;

     import std.stdio;
   writeln( i, ":", r); // 420:421
 }
Apr 15
parent Manfred Nowak <svv1999 hotmail.com> writes:
On Wednesday, 15 April 2020 at 12:30:43 UTC, aliak wrote:
 Yes, but I'm not sure what the legality of syntax has to do
 with readability/maintainability/etc?
That depends ... When character sequences, legal in a programming language, are causing disgust, then that language might have a design flaw not detectable by their _usual_ users.
 Elm recently (well, last year or two was it?) did away with 
 allowing cryptic operators [...]
On the other hand there still is seed7-lang (https://en.wikipedia.org/wiki/Seed7) claiming, that in addition to operators the whole syntax is under control of the coder. The root of failing to detect intention stems from structuring and partly hiding data. Structuring raises the need to somehow access the data stored within the structure, mainly done by qualifying the identifiers `s.i'. Partly hiding then raises the need to access the data via _other_ qualified identifiers, which are not hidden like `s.isNull', `s.nullify', `s.set( val)' ... Of course using any of those newly introduced identifiers implicitly calls a function, whose outcome the coder has to know; otherwise the coder is unable to implement his own intentions. Answers this enough of your question I cited at the beginning of this post?
Apr 15
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Wednesday, 15 April 2020 at 09:56:02 UTC, Manfred Nowak wrote:
 On Wednesday, 15 April 2020 at 07:44:23 UTC, aliak wrote:
 [...] it feels weird.
Yes. But `._=' is legal Dlang. In C one would have to annotate the possibility of writing to a parameter by using `&' to get the address of the variable. Would the legal Dlang phrase `._=&' make the intention more clear?
If you want to communicate the meaning of your code to other D programmers, the clearest way to do it is to use language features whose meaning they already know and understand: lhs = cast(typeof(lhs)) rhs; If that's unacceptable, the next clearest way is to use words: lhs.castAssign(rhs); If you insist on inventing a new symbol, no one will understand it without being taught. That's how symbols work. Just like you didn't know what + and × meant until you learned them in school, no one reading your code will know what `._=` means until they learn it by reading your documentation.
Apr 15
parent reply Manfred Nowak <svv1999 hotmail.com> writes:
On Wednesday, 15 April 2020 at 14:42:11 UTC, Paul Backus wrote:
 If that's unacceptable, the next clearest way is to use words:
   lhs.castAssign(rhs);
 If you insist on inventing a new symbol, [...]
I don't. As well as it is legal Dlang to write your lhs.castAssign(rhs) it is legal Dlang to write lhs._(rhs); or lhs._ = rhs; or rhs.castAssignToParameterByUsingLongWord(lhs); As long as the used identifiers have no predefined and unchangeable meaning every reader will anyway have to lookup the meaning of a particular identifier. Usually software engineers have the duty to build the dictionary for that lookup and it is the duty of the language to shield that dictionary against changes. But in Dlang even unchanged operators have no fixed meaning. For this please look for restrictions in the specs for the overloading of the assignment operator (https://dlang.org/spec/operatoroverloading.html#assignment). One might recognize, that there are no restrictions for structs. Therefore it is legal for an instance `lhs' of a struct to write lhs = rhs; and thereby de facto assigning to the variable rhs. Please note that the second requirement for (https://dlang.org/spec/expression.html#assignment_operator_expressions) is fulfilled too. --- struct S{ int data; void opAssign( ref int arg){ arg= data; } } void main(){ auto s= S( 42); int i= 101; s = i; import std.stdio; writeln( i); // 42 }
Apr 15
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Apr 15, 2020 at 04:56:35PM +0000, Manfred Nowak via Digitalmars-d wrote:
[...]
 But in Dlang even unchanged operators have no fixed meaning.
[...]
 One might recognize, that there are no restrictions for structs.
 Therefore it is legal for an instance `lhs' of a struct to write
     lhs = rhs;
 and thereby de facto assigning to the variable rhs.
This is precisely the kind of operator overloading abuse that Walter does not like, and which led to the ==, <, >, etc., operators not being overloadable individually, but only via opEquals and opCmp, as a measure to ensure some level of consistency in the meaning of the operators. It's not a perfect measure, of course, and the spec does not prevent every possible case of abuse that fulfills the letter of the spec but not the spirit, but that's only because it will also prevent useful applications of operator overloading, rather than an endorsement of this sort of operator abuse. Custom operators have repeatedly been proposed, and Walter's stance has always been that if you need unusual operators you should just use a DSL in a string mixin instead of altering the meaning of the language's surface syntax. Certainly, it's *possible* to overload opAssign such that `lhs = rhs;` assigns to rhs instead of lhs, but this sort of operator abuse leads to code becoming unreadable and unmaintainable because nobody knows what the surface syntax means anymore, like the hellhole of C++'s free-for-all, every-man-for-himself wild-wild-west operator overloading abuse where the following code can have completely divergent *parse trees*: fun<A, B>(a, b); gun<T, U>(a, b); Think you know that these two lines mean? Think again. See them in context: // Totally evil example of why C++ template syntax and // free-for-all operator overloading is a Bad, Bad Idea. #include <iostream> struct Bad { }; struct B { }; struct A { Bad operator,(B b) { return Bad(); } }; struct D { }; struct Ugly { D operator>(Bad b) { return D(); } } U; struct Terrible { } T; struct Evil { ~Evil() { std::cout << "Hard drive reformatted." << std::endl; } }; struct Nasty { Evil operator,(D d) { return Evil(); } }; struct Idea { void operator()(A a, B b) { std::cout << "Good idea, data saved." << std::endl; } Nasty operator<(Terrible t) { return Nasty(); } } gun; template<typename T, typename U> void fun(A a, B b) { std::cout << "Have fun!" << std::endl; } int main() { A a; B b; // What do these lines do? fun<A, B>(a, b); gun<T, U>(a, b); } You can argue all you want that the programmer ought to read and memorize all 1400+ pages of C++ spec plus however many hundreds of local project worth of docs in order to know what he's doing, but at the end of the day, this is just a huge waste of time and effort just so that the original code author can save a few keystrokes. The technical debt incurred is completely disproportionate with the meager benefits, and IMO makes no sense. Why go out of your way to make your code harder to read and maintain -- as if programming itself isn't already complex enough that basically every project has hundreds and thousands of bugs that will require so much effort to fix -- when you could just spend another 1/2 a second to type a few more keystrokes and make the code immediately understandable to anyone with a competent grasp of D? (Of course, if this is a personal project, then sure, go crazy and do whatever you want, I don't care. Just be warned that the code *will* be completely unmaintainable by anyone else -- and by "anyone else" I mean yourself after 5 months. :-P Speaking from experience here, btw. I've been down that road, and I can tell you that it does not lead to a good place, regardless of all initial appearances.) T -- In order to understand recursion you must first understand recursion.
Apr 15
parent Manfred Nowak <svv1999 hotmail.com> writes:
On Wednesday, 15 April 2020 at 17:22:51 UTC, H. S. Teoh wrote:
 This is precisely the kind of operator overloading abuse
[...]
 make the code immediately understandable to anyone with a 
 competent grasp of D?
Agreed and refusing to follow the content of the subject further.
Apr 15
prev sibling next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Tuesday, 14 April 2020 at 05:56:39 UTC, Manfred Nowak wrote:
 ```
 i= cast( int)  s;
 ```

 I do not like to always correctly examine the type of the 
 variable I am assigning to ( `i' in this case) for using that 
 type in the `cast()' and came up with the idea to no more 
 assign to the left but to the right:
You can avoid the need to look up the type by using `typeof`: i = cast(typeof(i)) s;
Apr 14
parent Manfred Nowak <svv1999 hotmail.com> writes:
On Tuesday, 14 April 2020 at 14:50:29 UTC, Paul Backus wrote:
 You can avoid the need to look up the type by using `typeof`:

 i = cast(typeof(i)) s;
Yes. But it is unnecessary bloat of about 15 chars. I would rather annotate my knowledge of implicit-cast-assigning by writing: `i:= s;'
Apr 14
prev sibling parent Manfred Nowak <svv1999 hotmail.com> writes:
On Tuesday, 14 April 2020 at 05:56:39 UTC, Manfred Nowak wrote:
[...]

Idea abandoned.

H. S. Teoh exhibited the final argument:
https://forum.dlang.org/post/mailman.2697.1586971381.31109.digitalmars-d puremagic.com
Apr 15