www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Casts and conversions done right

reply "Lars T. Kyllingstad" <public kyllingen.NOSPAMnet> writes:
The subject of casts has come up in various forms now and then, and with 
D2 nearing completion (or whatever you'd like to call it) I think it 
should be discussed properly.

Basically, we can divide (explicit) casts into two groups: safe and 
unsafe. The safe ones include

   - checked base-to-derived casting of class references
   - checked casts between struct types
   - numeric conversions

while the unsafe are things like

   - pointer reinterpretation
   - casting away constness
   - cast(public), cf. dsimcha's recent proposal

There are probably others I have forgotten. It pains me that I can use 
the same keyword to do these two things:

   double pi = 3.14;
   int i = cast(int) pi;
   int j = *cast(int*) &pi;

The former is very common and ought to be done using some (library) 
function with a specific rounding mode, not with a cast. The spec 
doesn't even say how numbers are rounded when cast is used.

The latter is a very unsafe operation, which should of course be allowed 
in a language like D, but it should be clearly marked as unsafe.


Instead, I propose the above operations be written like this:

   int i = to!int(pi);    // The to function already exists in std.conv
   int j = cast!int(pi);

I've never liked the syntax of cast expressions in D -- they look like 
nothing else in the entire language -- and changing it like this would 
make sure no invalid or dangerous casts are left lying around in old 
code causing trouble.

What do you think?

-Lars
Jul 20 2009
next sibling parent reply Michiel Helvensteijn <m.helvensteijn.remove gmail.com> writes:
Lars T. Kyllingstad wrote:

    int i = to!int(pi);    // The to function already exists in std.conv
But what does the to function do, exactly? I'd prefer to use one of floor, round or ceil. Wouldn't you? I'm sure they are available as well. -- Michiel Helvensteijn
Jul 20 2009
parent "Lars T. Kyllingstad" <public kyllingen.NOSPAMnet> writes:
Michiel Helvensteijn wrote:
 Lars T. Kyllingstad wrote:
 
    int i = to!int(pi);    // The to function already exists in std.conv
But what does the to function do, exactly? I'd prefer to use one of floor, round or ceil. Wouldn't you? I'm sure they are available as well.
I could perhaps have found a better example than rounding conversions since there are so many ways to perform those. But that is beside the point, which was that safe type conversions should be clearly separated from unsafe ones. Regarding your question, currently I think std.conv.to just checks for over-/underflow and performs a cast. :) If such casts were to be disallowed, it would of course need to be rewritten. In any case, the documentation for both cast and std.conv.to should say which rounding mode is used. -Lars
Jul 20 2009
prev sibling next sibling parent bearophile <bearophileHUGS lycos.com> writes:
Lars T. Kyllingstad:
 Instead, I propose the above operations be written like this:
    int i = to!int(pi);    // The to function already exists in std.conv
    int j = cast!int(pi);
I think this topic needs quite more thinking, but I agree that the current casts of D are not good enough and have to be improved. Probably your solution isn't the best design, but it's looks better than the current one. Merging all different kinds of casts in a single syntax is bad. Bye, bearophile
Jul 20 2009
prev sibling parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
Lars T. Kyllingstad wrote:
 The subject of casts has come up in various forms now and then, and with
 D2 nearing completion (or whatever you'd like to call it) I think it
 should be discussed properly.
 
 ...
 
 Instead, I propose the above operations be written like this:
 
   int i = to!int(pi);    // The to function already exists in std.conv
   int j = cast!int(pi);
 
 I've never liked the syntax of cast expressions in D -- they look like
 nothing else in the entire language -- and changing it like this would
 make sure no invalid or dangerous casts are left lying around in old
 code causing trouble.
 
 What do you think?
 
 -Lars
This is a bit of a bug-bear of mine, too. I originally wrote Tango's to!(T) template with the express condition that it would only ever perform value conversions. We could probably write another template called reinterpret!T or recast!T or something that explicitly takes a collection of bits and reinterprets them as another type (ie: the *cast(int*)&pi case). If we also added, say, objcast!T that only accepted object types, then we could lock cast(T) away in a box and electrocute the lid. :D As for people wanting to control the rounding mode, etc., that's what functions are for. My personal position is that stuff like to!T should exist to do perform a sane default conversion; if you want more control, you should use a more specialised function.
Jul 20 2009
parent reply "Lars T. Kyllingstad" <public kyllingen.NOSPAMnet> writes:
Daniel Keep wrote:
 
 Lars T. Kyllingstad wrote:
 The subject of casts has come up in various forms now and then, and with
 D2 nearing completion (or whatever you'd like to call it) I think it
 should be discussed properly.

 ...

 Instead, I propose the above operations be written like this:

   int i = to!int(pi);    // The to function already exists in std.conv
   int j = cast!int(pi);

 I've never liked the syntax of cast expressions in D -- they look like
 nothing else in the entire language -- and changing it like this would
 make sure no invalid or dangerous casts are left lying around in old
 code causing trouble.

 What do you think?

 -Lars
This is a bit of a bug-bear of mine, too. I originally wrote Tango's to!(T) template with the express condition that it would only ever perform value conversions. We could probably write another template called reinterpret!T or recast!T or something that explicitly takes a collection of bits and reinterprets them as another type (ie: the *cast(int*)&pi case).
Wouldn't this by necessity have to be a built-in feature of the language?
 If we also added, say, objcast!T that only accepted object types, then
 we could lock cast(T) away in a box and electrocute the lid.  :D
 
 As for people wanting to control the rounding mode, etc., that's what
 functions are for.  My personal position is that stuff like to!T should
 exist to do perform a sane default conversion; if you want more control,
 you should use a more specialised function.
Recently, Julien Leclercq posted an enhancement request for Phobos entitled "'std.conv.to': check for a custom 'to' method in classes/structs". http://d.puremagic.com/issues/show_bug.cgi?id=3189 I think it would be a very good idea to make to!(T) *the* convention for built-in, standard library and user types. It would make for a unified, consistent approach to conversions: double x = 3.14; MyFloat y = 2.72; int i = to!int(x); int j = to!int(y); string s = to!string(x); string t = to!string(y); Beautiful, no? :) -Lars
Jul 21 2009
parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
Lars T. Kyllingstad wrote:
 Daniel Keep wrote:
 ...

 We could probably write another template called reinterpret!T or
 recast!T or something that explicitly takes a collection of bits and
 reinterprets them as another type (ie: the *cast(int*)&pi case).
Wouldn't this by necessity have to be a built-in feature of the language?
Why would it? ref T recast(T,U)(ref U v) if( T.sizeof <= U.sizeof ) { return *cast(T*)&v; } Or something similar should be possible.
 ...

 As for people wanting to control the rounding mode, etc., that's what
 functions are for.  My personal position is that stuff like to!T should
 exist to do perform a sane default conversion; if you want more control,
 you should use a more specialised function.
Recently, Julien Leclercq posted an enhancement request for Phobos entitled "'std.conv.to': check for a custom 'to' method in classes/structs". http://d.puremagic.com/issues/show_bug.cgi?id=3189
Tango already does this. Actually, it goes a few better than that: any user-defined type can have to(T), to_{type_name} or to{TypeName} members that it will use to do conversions as well as static from(T), from_{type_name} or from{TypeName} methods to do reverse conversions.
 I think it would be a very good idea to make to!(T) *the* convention for
 built-in, standard library and user types. It would make for a unified,
 consistent approach to conversions:
 
     double x = 3.14;
     MyFloat y = 2.72;
 
     int i = to!int(x);
     int j = to!int(y);
     string s = to!string(x);
     string t = to!string(y);
 
 Beautiful, no? :)
 
 -Lars
Again; why do you think I wrote Tango's to(T) template in the first place? :D
Jul 21 2009
parent "Lars T. Kyllingstad" <public kyllingen.NOSPAMnet> writes:
Daniel Keep wrote:
 Lars T. Kyllingstad wrote:
 Daniel Keep wrote:
 ...

 We could probably write another template called reinterpret!T or
 recast!T or something that explicitly takes a collection of bits and
 reinterprets them as another type (ie: the *cast(int*)&pi case).
Wouldn't this by necessity have to be a built-in feature of the language?
Why would it? ref T recast(T,U)(ref U v) if( T.sizeof <= U.sizeof ) { return *cast(T*)&v; } Or something similar should be possible.
Ah, I misunderstood your "lock cast(T) away in a box and electrocute the lid" comment to mean that the current cast(T) should be entirely removed from the language. (Which is something I personally wouldn't be opposed to.) -Lars
Jul 23 2009