May 09, 2008
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
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
and I like existing cast keyword, it's short enough to write and long enough to spot.
May 09, 2008
And I hate those exclamation marks and chains of brackets.
May 09, 2008
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
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
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
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
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
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 6 7 8 9 10 11
Top | Discussion index | About this forum | D home