Accidentally killing immutable is too easy in D (Was: cast()x - a valid expression?)

Steven Schveighoffer schveiguy at yahoo.com
Thu Jun 2 19:01:39 PDT 2011


On Thu, 02 Jun 2011 19:41:18 -0400, Jonathan M Davis <jmdavisProg at gmx.com>  
wrote:

> On 2011-06-02 16:22, Steven Schveighoffer wrote:

>> this is not complication, this is expressiveness. Something that D's
>> casting does not have. D's casting conflates multiple things together,
>> and because of the "I trust you" nature that casts have, you can easily
>> make mistakes without complaint from the compiler. The fact that a
>> dynamic cast is inherently unsafe in D is unnecessary. I don't know if  
>> it
>> will change, but I can't say it's better than C++. It can be made better
>> than C++, but I think I'd rather have the four cast types than what D  
>> has
>> now.
>
> It is definitely added complexity. Now, it allows you to be more  
> explicit and
> what you want and so it does add expressiveness as well, but with one  
> cast,
> you know which cast you need to use.

Think about ref.  Ref has two purposes, passing a value by reference  
because you are going to use it as a return value, and passing a value by  
reference because it's cheaper to pass by reference.  But the compiler  
cannot tell what you mean, so things like rvalues cannot be passed by  
reference, even if it's for the latter case (because it's cheaper).  In  
C++, they "fixed" this by allowing const references to bind to rvalues,  
but the problem there is, you don't know if the person wanted to allow  
rvalues through.  There is no way to specify "I only want cheaply passed  
lvalues here!"

What results is, it's difficult to determine the *intentions* of the user  
given the code he wrote, both for the compiler and the reviewer.  This  
leads to possible reliance on documentation, and no enforcement.  This is  
what I'd call false simplicity.  Yes, you didn't need to add another  
keyword, but is it really simpler?  What happens is, it's simpler to make  
bugs, and impossible to express what you mean in some cases.

So is giving more expressive meaning to casts adding complexity?  I look  
at it this way: it results in more thinking on the front end (which cast  
should I use?) vs. 10x more thinking on the back end (what the hell did  
the author mean by this cast?  Is it really supposed to strip out  
const?).  Note also, the front end rules are defined by the C++ standard,  
whereas the reasoning for using a generic all-in-one cast is only locked  
inside the author's head.  Hopefully he's documented that for you.

> With 4, you have to figure out which one
> to use in a particular instance. Honestly, in discussing the C++ special  
> casts
> with other programmers in the past, I've gotten the impression that your
> average C++ programmer does not understand them all, and many of them  
> just use
> C-style casts all the time.

Such easy-outs are part of the problem -- if you make the more dangerous  
path easier than the more expressive and safer path, people give up trying  
to learn the system.  They might even think they are using clever  
shortcuts.  Without such an easy out, you will learn the other casts  
eventually.  I've been a part of teams where C-style casts are not an  
option, and you just figure it out.

> I care about such details in a language and try
> and understand them, and I confess that I still am not really sure of the
> exact difference between static_cast and reinterpret_cast and when you're
> supposed to use one or the other - especially when casting pointers.

I admit too, I was not correct in remembering how reinterpret_cast works  
exactly (my C++ skills are suffering from bit-rot) -- it does not allow  
casting away of const.  However, the rest of my description is accurate.   
It basically treats the data as if it was the given type without any  
implicit or explicit conversions.  For example, if you static_cast an int  
to a float, it create a float out of an int (i.e 5 turns into 5.0).   
However, if you reinterpret_cast an int to a float, it will not change the  
bit pattern at all, what you get is what a float would be with that bit  
pattern.

BTW, as far as I can tell, static or reinterpret casting pointers is  
identical, you should prefer static_cast however because it's safer.

The hilarious thing about reinterpret_cast is, using the casted data is  
undefined.  All you are allowed to do with it is cast back to the original  
data.  This may seem useless but gets around some static typing  
limitations (for example, if you have functions that accept void * data,  
and you have to override that function, you reinterpret_cast the data to  
void *, then reinterpret_cast it back on the other end).

I feel D does not need an equivalent reinterpret_cast.  You can achieve  
the same thing via pointer casting (e.g. *(cast(T*)&u) ).  Note this is  
entirely possible in D because of the inability to override the & operator.

D already has a clearly delineated const_cast (the subject of this  
thread).  The problem is, all other cast types are munged together, with  
const_cast thrown in for good measure.  I think if we stopped debating the  
value of C++ casts, and simply tried to find a decent syntax for  
separating out dynamic_cast, we could have all these options without the  
verbosity.  I'd even go as far to say that dynamic_cast can be a library  
function -- it's not really a cast.  It can even be made a part of  
std.conv.to.

-Steve

P.S. I used the information from this SO article to help me write this:   
http://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-and-reinterpret-cast-be-used


More information about the Digitalmars-d mailing list