May 09, 2008
On 09/05/2008, BCS <BCS@pathlink.com> wrote:
>  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.

Good point.

>  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).

OK, so how about just making class!(T) return null, and do away with
is!(T)? That's what C++ does with dynamic_cast<T>, after all.
May 09, 2008
On 09/05/2008, Janice Caron <caron800@googlemail.com> wrote:
>  >  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).
>
> OK, so how about just making class!(T) return null, and do away with
>  is!(T)? That's what C++ does with dynamic_cast<T>, after all.

One goal of the proposal was to avoid introducing more keywords. Some of these conversions don't have to be keywords, of course. to! is already implemented in std.conv, and this proposal only suggests extending its reach. But others kinds of transformation are better done by the compiler, and I'd say that includes RTTI, so for that reason, I went for keyword reuse.
May 09, 2008
I'm sorry if this post sounds argumentative, but I disagree strongly with pretty much everything in this post. So no hard feelings, peace, respect, all that ;-P.

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. 

I doubt any of the suggestions include "at the expense of the current cast". If all these were implemented, it would be a significant cognitive load on the programmer, and D is already a fairly complex language.

Also, should getting rid of keywords really be a design goal? Only Walter (and you, apparently; no disrespect) seem to think so.

> 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.

Ick! That means another identifier that needs to be a compiler built-in thus increasing coupling between the compiler and the standard library. Since it's not something the template can actually handle, why should it be in there rather a special construct in the core language?

> (*) 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.

But all this is possible with the current cast system. All you gain is a more complex syntax, and since "cast" is a term well-known to programmers, you lose a meaningful bit of syntax.

> (*) 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)

Ditto as above. Just means the person needs to think about something else. And why "union", that seems like an arbitrary keyword. WHy not "foreach_reverse!(T)(x)" which makes just as much sense.

> (*) 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)

How is that better than "cast(T) x;". I guess just because it creates the distinction?

> (*) 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)

Again, how is that any better?
---
Okay, sorry that was so negative. But it just seems that cast() works perfectly fine for _all_ those things right now. If what you want is safer casting, you can write your own templates to do that.
May 09, 2008
Robert Fraser wrote:
>  [...]

Also, I find the cast-returns-null-on-failure behavior quite useful. In your system, this piece of code:

Shape s = getShape();
if(auto r = cast(Rectangle) s) {
    return r.length * r.width;
}
else if(auto c = cast(Circle) s) {
    return PI * c.radius * c.radius;
}

... would become ...

Shape s = getShape();
if(is!(Rectangle)(s)) {
    auto r = class!(Rectangle) s;
    return r.length * r.width;
}
else if(is!(Rectangle)(s)) {
    auto c = class!(Circle) s;
    return PI * c.radius * c.radius;
}

Which is more typing and uglier IMO. Since if you want this behavior, you can already make templates to do it, why force it down people's throats?
May 09, 2008
janderson wrote:
> 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

I think Janice mentioned that it would work exactly like that.
May 09, 2008
On 09/05/2008, Robert Fraser <fraserofthenight@gmail.com> wrote:
> I'm sorry if this post sounds argumentative, but I disagree strongly with pretty much everything in this post. So no hard feelings, peace, respect, all that ;-P.

All is peaceful here on the newsgroup. This is just a nice, friendly chat. Nothing has been suggested "officially", and nothing will be, if folk here don't like the idea.


> >    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.
>
>  Ick! That means another identifier that needs to be a compiler built-in

Um. No. std.conv.to!(T) already exists.

I guess what you're saying is, it's not possible to write a function like toInt(double) if the cast operator is gone. I hadn't thought of that. In that case, maybe it should be

    cast!(T) // lossy conversion
    to!(T) // conversion that always succeeds, or throws an exception


> And why "union", that seems like an arbitrary keyword.

It wasn't entirely arbitrary. A union /is/ a reinterpretation of bits. It's one memory layout painted over another. (And it's a lot shorter than "reinterpret_cast").


> >    auto!(T)(x)
>
>  How is that better than "cast(T) x;". I guess just because it creates the
> distinction?

I guess I just couldn't think of anything better. :-) D doesn't have a word for "mutable", and C++'s "const_cast" is too long. (And also misleading, since its primary use is to /remove/ constancy, not add it).


>  Okay, sorry that was so negative. But it just seems that cast() works
> perfectly fine for _all_ those things right now. If what you want is safer
> casting, you can write your own templates to do that.

You can? I confess, my template writing skills aren't up to the job. If it could be done, that would be interesting. Obviously, you'd have to use non-reserved words for the names though.

I'm curious to know if C++'s dynamic_cast<T>, const_cast<T> are implemented with templates. They certainly /look/ like C++ templates.
May 09, 2008
Janice Caron wrote:
> On 09/05/2008, Janice Caron <caron800@googlemail.com> wrote:
> 
>> >  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).
>>
>>OK, so how about just making class!(T) return null, and do away with
>> is!(T)? That's what C++ does with dynamic_cast<T>, after all.
> 
> 
> One goal of the proposal was to avoid introducing more keywords. Some
> of these conversions don't have to be keywords, of course. to! is
> already implemented in std.conv, and this proposal only suggests
> extending its reach. But others kinds of transformation are better
> done by the compiler, and I'd say that includes RTTI, so for that
> reason, I went for keyword reuse.

I think it you go with the __!(T) syntax it would be better to /not/ used keywords at all. Make them either "magic" template that are defined in the compiler or as part of the compiler specific runtime.
May 09, 2008
On Fri, 09 May 2008 19:58:49 +0100, Janice Caron <caron800@googlemail.com> wrote:

> On 09/05/2008, Robert Fraser <fraserofthenight@gmail.com> wrote:
>> I'm sorry if this post sounds argumentative, but I disagree strongly with
>> pretty much everything in this post. So no hard feelings, peace, respect,
>> all that ;-P.
>
> All is peaceful here on the newsgroup. This is just a nice, friendly
> chat. Nothing has been suggested "officially", and nothing will be, if
> folk here don't like the idea.
>
>
>> >    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.
>>
>>  Ick! That means another identifier that needs to be a compiler built-in
>
> Um. No. std.conv.to!(T) already exists.
>
> I guess what you're saying is, it's not possible to write a function
> like toInt(double) if the cast operator is gone. I hadn't thought of
> that. In that case, maybe it should be
>
>     cast!(T) // lossy conversion
>     to!(T) // conversion that always succeeds, or throws an exception
>
>
>> And why "union", that seems like an arbitrary keyword.
>
> It wasn't entirely arbitrary. A union /is/ a reinterpretation of bits.
> It's one memory layout painted over another. (And it's a lot shorter
> than "reinterpret_cast").
>
>
>> >    auto!(T)(x)
>>
>>  How is that better than "cast(T) x;". I guess just because it creates the
>> distinction?
>
> I guess I just couldn't think of anything better. :-) D doesn't have a
> word for "mutable", and C++'s "const_cast" is too long. (And also
> misleading, since its primary use is to /remove/ constancy, not add
> it).
>
>
>>  Okay, sorry that was so negative. But it just seems that cast() works
>> perfectly fine for _all_ those things right now. If what you want is safer
>> casting, you can write your own templates to do that.
>
> You can? I confess, my template writing skills aren't up to the job.
> If it could be done, that would be interesting. Obviously, you'd have
> to use non-reserved words for the names though.
>
> I'm curious to know if C++'s dynamic_cast<T>, const_cast<T> are
> implemented with templates. They certainly /look/ like C++ templates.

They look like templates deliberately because they do a similar job
but are not (in any implementation I have seen). The same could be made
true for any "cast!" magic. I think this is one place where breaking
the rules is more acceptable. That said, I think the reason we still have C style
casts in C++ is because they are the only way to implement cast templates. Probably
in the mysts of time this was how it was done. Does anyone have a copy of
"the design and evolution of C++" to hand? I bet the answer is in there.

On another note I like that reinterpret_cast and const_cast are hard to type.
It slows you down and hopefully encourages you to think about why you might
be doing something evil.
Another poster's comment that reinterpret_casts are mostly to and from void* hit home
for me. I don't think this is as much of a problem when your hierarchy is based on
object though. I also don't like your choice of union. I can see the C connection but it
doesn't say re-interpret to me. Something more like good=force!(evil) appeals.
When reviewing and correcting C++ code I make use of the _cast anchor as something
(a code smell) to search for. This would be harder for single words. My language design
axiom here would be longer names for more dangerous uses.
Otherwise I like the proposal. I hope the discussion leads to something more concrete.

Regards,

Bruce.


May 10, 2008
I too do not like the proposed syntax. I'd suggest the following scheme:
1) convert: cast(foo)bar
2) reinterpret: cast!(foo)bar
3) constancy: cast(invariant), cast(mutable)
4) downcast: cast!(foo)bar

explanation:
all casts use the word "cast" so it's easy to grep for. more dangerous
casts use the "cast!" form. reinterpret and downcast both do the same
thing therefore there's no need to separate the two (both do not change
the actual memory). Constancy is cast explicitly with its own cast instance.

example of use:
invariant int a = ...;
double a = cast!(double)cast(mutable)(b);

the above removes invariance and then reinterprets the bit pattern to double.

class B : A {}
class C : A {}
A b = new B;
C c = cast!(C)b; // c is null

cast! is more dangerous and thus can fail and return a null as in the above example. OTOH, cast is safe and expected to always work, so if you use cast instead of cast! to downcast, an exception will be thrown.

have I left anything out?

-- Yigal

PS - note that my proposal only adds the "cast!" form to D.
cast(invariant) is already defined in D, and cast(mutable) can be
replaced by cast(!invariant) although it's less readable IMO.
May 10, 2008
On 10/05/2008, Yigal Chripun <yigal100@gmail.com> wrote:
> I too do not like the proposed syntax. I'd suggest the following scheme:
>  1) convert: cast(foo)bar
>  2) reinterpret: cast!(foo)bar
>  3) constancy: cast(invariant), cast(mutable)
>  4) downcast: cast!(foo)bar

Your proposal is better than mine, but I think I can improve it even more:

1) convert: cast(Foo)bar
2) reinterpret: cast!(Foo)bar
3) constancy: cast(invariant)bar, cast(!const)bar
4) downcast: cast(Foo)bar

There are just two differences. I suggest cast(!const) to remove
constancy, because cast(mutable) could be ambiguous if there happened
to be a type called "mutable" (unless you're suggesting "mutable" be a
reserved word, but Walter would never allow that).

Downcasting isn't particularly dangerous, providing you always check the return value, so no exclamation mark needed there.

The important point about this proposal is that the following will /not/ compile:

    const c = new C;
    auto d = cast(C)c; // ERROR - cast cannot remove constancy

But this will

    const c = new C;
    auto d = cast(!const)c; // OK

Likewise with downcasting

    void foo(in A a)
    {
        B b = cast(B)a; // ERROR - cast cannot remove constancy
        if (b !is null) ...
    }

but the following would be OK

    void foo(in A a)
    {
        B b = cast(B)cast(!const)a; // OK
        if (b !is null) ...
    }

although of course, what you probably should be doing is really:

    void foo(in A a)
    {
        const B b = cast(const B)a; // OK
        if (b !is null) ...
    }