View mode: basic / threaded / horizontal-split · Log in · Help
May 09, 2008
Safer casts
Better, safer casts have been requested before - in fact I routinely
see that request on the monthly "D wishlist" that goes round. But
here's a fairly concrete suggestion as to what the syntax might be.
And as a bonus, we'd get rid of the "cast" keyword, thus reducing the
keyword count by one. So, here are the various types of transformation
I suggest:


(*) Build a new instance with a different memory layout, but with the
same value. (For example, casting double to int). For lossless
conversions, this can happen implicitly, but for lossy conversions, an
explicit cast is necessary. For D, I suggest

   to!(T)(x)

where to! is a template function provided by std.conv (not a keyword).
to! already performs many conversions. Throwing in the basics like
to!(int)(double) would be an obvious extension.



(*) Use RTTI to cast up and down the class heirarchy. In C++, this
would be dynamic_cast<T>(x). For D, I suggest two things. Firstly, the
cast:

   class!(T)(x)

to do that actual upcast or downcast - but RTTI dynamic casts can
fail, so the question arises: What should we do if the cast fails?
Should we return null, or should we throw an exception. My view is
that we should throw an exception, but also introduce a new construct
to test whether or not the cast would be possible in the first place:

   is!(T)(x)

which returns bool. is!(T) would be like Java's instanceof. Thus, one
could write

   void f(A a)
   {
       if (is!(B)a)
       {
           B b = class!(B)(a);
           /*...*/
       }
       else if (is!(C)a)
       {
           C c = class!(C)(a);
           /*...*/
       }
   }

etc.


(*) Reinterpret a bit pattern. In C++, this would be
reinterpret_cast<T>(x). Without changing the memory layout, or the
constancy, reinterpret the bits to mean something else. For D, I
suggest

   union!(T)(x)


(*) Change const to mutable, or invariant to mutable. In C++, this
would be const_cast<T>(x). There is no equivalent in D, however, in D,
one can currently write cast(T)x, and constancy will be magically (and
dangerously) waved away. In the new scheme, I suggest:

   auto!(T)(x)



(*) Change const to invariant, or mutable to invariant. Currently, the
syntax for this is cast(invariant)x, however this has two
disadvantages: (i) the entire type is made invariant, and you might
want to invariantize only part of the type, and (ii) cast(T)x also
works. In other words, (i) is not powerful enough, and (ii) is too
dangerous. There is no equivalent for this in C++, since C++ has no
invariance. For D, I suggest:

   invariant!(T)(x)



I think that's all bases covered. So in summary we'd have:

   to!(T)(x)        // convert
   is!(T)(x)        // is upcast or downcast possible?
   class!(T)(x)    // upcast or downcast
   union!(T)(x)    // reinterpret the bits
   auto!(T)(x)        // remove constancy
   invariant!(T)(x)    // add invariance


Those seem reasonably obvious word choices to me. Thoughts?
May 09, 2008
Re: Safer casts
Janice Caron Wrote:

>     to!(T)(x)        // convert
>     is!(T)(x)        // is upcast or downcast possible?
>     class!(T)(x)    // upcast or downcast
>     union!(T)(x)    // reinterpret the bits
>     auto!(T)(x)        // remove constancy
>     invariant!(T)(x)    // add invariance

1) I don't think you can throw away the cast keyword, this will break all existing code.
2) how about interfaces?
3) I believe reinterpret_cast was introduced as analog of C cast, D already has cast for this purpose. I don't think D needs union!().
4) D already has cast to add/remove constancy/invariance.

My proposal was just adding safe cast for downcasts with the syntax of existing cast, nothing more.
May 09, 2008
Re: Safer casts
and I like existing cast keyword, it's short enough to write and long enough to spot.
May 09, 2008
Re: Safer casts
And I hate those exclamation marks and chains of brackets.
May 09, 2008
Re: Safer casts
2008/5/9 terranium <spam@here.lot>:
> 1) I don't think you can throw away the cast keyword, this will break all existing code.

This is exactly the problem C++ had. C++ introduced new, better cast
types: static_cast<T>, dynamic_cast<T>, const_cast<T>, and
reinterpret_cast<T>. However, where C++ went wrong is they failed to
outlaw the original cast - presumably for the same reason, that it
would break existing code.

The problem is that unless you outlaw the old "one cast for all
purposes" cast, then people will continue to use it, when what you
really want it to force everyone to migrate to the new, "safe" ways.


> 2) how about interfaces?

They're a run-time thing, so is!(T) and class!(T) should work for
interfaces just as for classes. This is where D differs from C++, of
course, because we have interfaces where C++ has multiple inheritance.
But yeah - just as C++'s dynamic_cast<T> works for multiple
inheritance, so class!(T) should work for interfaces - though I note
that the name is now less apt.



> 3) I believe reinterpret_cast was introduced as analog of C cast,

It was introduced to replace /one/ kind of transformation which
old-style casts did, but not all of them. For example

   // C++
   double x;
   int y = reinterpret_cast<int>(x); // won't compile
   int y = static_cast<int>(x); // OK



> D already has cast for this purpose. I don't think D needs union!().

D already has cast for /all/ of the purposes I listed, so you could
argue that D doesn't need any of them. The point is, if you wanted to
be explicit about exactly what kind of transformation you wanted, then
you would need it.


> 4) D already has cast to add/remove constancy/invariance.

Again, D already has cast, which can do each and every transformation
I listed. What it can't do is emit an error if the wrong kind of
transformation is performed.


> My proposal was just adding safe cast for downcasts with the syntax of existing cast, nothing more.

I wasn't disputing that. Consider this a separate proposal.
May 09, 2008
Re: Safer casts
2008/5/9 terranium <spam@here.lot>:
> and I like existing cast keyword, it's short enough to write and long enough to spot.

I like it too. But it has its disadvantages. For example, you might
write a dynamic cast as

   void f(in A a)
   {
       B b = cast(B) a;
       if (b !is null)
       {
           /* ... */
       }
       /*...*/
   }

and it would compile. Unfortunately, you have just accidently cast
away const, and that kind of bug is hard to spot. But replace it with

   void f(in A a)
   {
       if (is!(B)(a))
       {
           B b = class!(B)(a);
           /* ... */
       }
       /*...*/
   }

and you have a compile-time error, because class!(T) cannot change
const.into mutable.

So I guess there's a tradeoff between "nice" syntax, like cast(T), and
"safe" syntax. I'm just wondering if we should err on the side of safe
May 09, 2008
Re: Safer casts
Janice Caron Wrote:

> I note
> that the name is now less apt.
that's what I meant.

> > 3) I believe reinterpret_cast was introduced as analog of C cast,
> 
> It was introduced to replace /one/ kind of transformation which
> old-style casts did, but not all of them. For example
> 
>     // C++
>     double x;
>     int y = reinterpret_cast<int>(x); // won't compile
>     int y = static_cast<int>(x); // OK
> 
> D already has cast for /all/ of the purposes I listed, so you could
> argue that D doesn't need any of them. The point is, if you wanted to
> be explicit about exactly what kind of transformation you wanted, then
> you would need it.

My opinion is we don't need to be explicit when casting double to int. reinterpret_cast is used usually to cast to/from void* - that is for pointer types - here it has no difference with D cast. Adding new construct adds complexity and requires extra time to learn and extra info to keep in mind, I believe these constructs' safeness is minor and it's not worth its complexity.

> I wasn't disputing that. Consider this a separate proposal.
I meant my proposal is better :))) We need only minor extra safeness. This can be achieved just by standardizing safe cast.
May 09, 2008
Re: Safer casts
Janice Caron wrote:
> Better, safer casts have been requested before - in fact I routinely
> see that request on the monthly "D wishlist" that goes round. But
> here's a fairly concrete suggestion as to what the syntax might be.
> And as a bonus, we'd get rid of the "cast" keyword, thus reducing the
> keyword count by one. So, here are the various types of transformation
> I suggest:
> 
> 
> (*) Build a new instance with a different memory layout, but with the
> same value. (For example, casting double to int). For lossless
> conversions, this can happen implicitly, but for lossy conversions, an
> explicit cast is necessary. For D, I suggest
> 
>     to!(T)(x)
> 
> where to! is a template function provided by std.conv (not a keyword).
> to! already performs many conversions. Throwing in the basics like
> to!(int)(double) would be an obvious extension.
> 
> 
> 
> (*) Use RTTI to cast up and down the class heirarchy. In C++, this
> would be dynamic_cast<T>(x). For D, I suggest two things. Firstly, the
> cast:
> 
>     class!(T)(x)
> 
> to do that actual upcast or downcast - but RTTI dynamic casts can
> fail, so the question arises: What should we do if the cast fails?
> Should we return null, or should we throw an exception. My view is
> that we should throw an exception, but also introduce a new construct
> to test whether or not the cast would be possible in the first place:
> 
>     is!(T)(x)
> 
> which returns bool. is!(T) would be like Java's instanceof. Thus, one
> could write
> 
>     void f(A a)
>     {
>         if (is!(B)a)
>         {
>             B b = class!(B)(a);
>             /*...*/
>         }
>         else if (is!(C)a)
>         {
>             C c = class!(C)(a);
>             /*...*/
>         }
>     }
> 
> etc.
> 
> 
> (*) Reinterpret a bit pattern. In C++, this would be
> reinterpret_cast<T>(x). Without changing the memory layout, or the
> constancy, reinterpret the bits to mean something else. For D, I
> suggest
> 
>     union!(T)(x)
> 
> 
> (*) Change const to mutable, or invariant to mutable. In C++, this
> would be const_cast<T>(x). There is no equivalent in D, however, in D,
> one can currently write cast(T)x, and constancy will be magically (and
> dangerously) waved away. In the new scheme, I suggest:
> 
>     auto!(T)(x)
> 
> 
> 
> (*) Change const to invariant, or mutable to invariant. Currently, the
> syntax for this is cast(invariant)x, however this has two
> disadvantages: (i) the entire type is made invariant, and you might
> want to invariantize only part of the type, and (ii) cast(T)x also
> works. In other words, (i) is not powerful enough, and (ii) is too
> dangerous. There is no equivalent for this in C++, since C++ has no
> invariance. For D, I suggest:
> 
>     invariant!(T)(x)
> 
> 
> 
> I think that's all bases covered. So in summary we'd have:
> 
>     to!(T)(x)        // convert
>     is!(T)(x)        // is upcast or downcast possible?
>     class!(T)(x)    // upcast or downcast
>     union!(T)(x)    // reinterpret the bits
>     auto!(T)(x)        // remove constancy
>     invariant!(T)(x)    // add invariance
> 
> 
> Those seem reasonably obvious word choices to me. Thoughts?

I think that up casting for classes should be implicit, like it works in 
C++.  Its perfectly ok to upcast and there shouldn't be any warning 
casts around that.  That is actually a very useful feature for genetic 
style coding.  Of course the cast should still work for upcasting, but 
its superfluous.

-Joel
May 09, 2008
Re: Safer casts
On 09/05/2008, janderson <askme@me.com> wrote:
>  I think that up casting for classes should be implicit for up cast, like it
> works in C++.  Its perfectly ok to upcast and there shouldn't be any warning
> casts around that.  That is actually a very useful feature for genetic style
> coding.  Of course the cast should still work for upcasting, but its
> superfluous.

Yes, you are completely correct, of course.
May 09, 2008
Re: Safer casts
Janice Caron wrote:
> (*) Use RTTI to cast up and down the class heirarchy. In C++, this
> would be dynamic_cast<T>(x). For D, I suggest two things. Firstly, the
> cast:
> 
>     class!(T)(x)
> 
> to do that actual upcast or downcast - but RTTI dynamic casts can
> fail, so the question arises: What should we do if the cast fails?
> Should we return null, or should we throw an exception. My view is
> that we should throw an exception, but also introduce a new construct
> to test whether or not the cast would be possible in the first place:
> 
>     is!(T)(x)
> 
> which returns bool. is!(T) would be like Java's instanceof. Thus, one
> could write
> 
>     void f(A a)
>     {
>         if (is!(B)a)
>         {
>             B b = class!(B)(a);
>             /*...*/
>         }
>         else if (is!(C)a)
>         {
>             C c = class!(C)(a);
>             /*...*/
>         }
>     }
> 
> etc.
> 

The major issue I have with this is that the construct that actually 
does the downcast MUST also do the type check. Therefor it gets done 
twice and this is  a bit of a performance issue. (and performance snobs 
will start finding way to skip the second test (like your union) and 
then get it wrong.)

I'd rather see a testClass!(T) version that acts exactly like the 
current cast and a isClass!(T) that acts like your class!(T).
« First   ‹ Prev
1 2 3 4 5
Top | Discussion index | About this forum | D home