May 10, 2008
Janice Caron Wrote:

> 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

With much interest I followed this thread. Forgive my ignorance, but I can not understand one thing. Could this functionality be implemented as library functions all using the built in cast? The syntax will be clumsier though. I see from std.typecons and std.typetraits that D has powerful meta programming. Functions should be able to detect different cases.

Coding standards could prescribe that cast must not be used and only the library functions can be used. Dee Girl
May 10, 2008
On 10/05/2008, Dee Girl <deegirl@noreply.com> wrote:
> With much interest I followed this thread. Forgive my ignorance, but I can not understand one thing. Could this functionality be implemented as library functions all using the built in cast?

Not really, since part of the proposal is that

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

should not compile. I see ruling this out as an important part of const correctness.

If you make it optional (e.g. have some sort of template,
static_cast!(T)), then people are just going to write "cast" instead
of "static_cast!" because (a) it's shorter, and (b) the programmer's
inherent belief that everything they write is bug free. That's exactly
what happens in C++.

Ruling out the ability of cast(T) to remove constancy is exactly the same philosophy as that of not allowing const objects to be passed to mutable functions.

Naturally, there must be a way of removing constancy if you really, really want to, because D is a systems programming language. That's the reason Yigal suggested cast(mutable), and I suggested cast(!const). Either way, it tells the compiler "I'm doing this on purpose".
May 10, 2008
Janice Caron Wrote:

> On 10/05/2008, Dee Girl <deegirl@noreply.com> wrote:
> > With much interest I followed this thread. Forgive my ignorance, but I can not understand one thing. Could this functionality be implemented as library functions all using the built in cast?
> 
> Not really, since part of the proposal is that
> 
>    const c = new C;
>    auto d = cast(C)c; // ERROR - cast cannot remove constancy
> 
> should not compile. I see ruling this out as an important part of const correctness.
> 
> If you make it optional (e.g. have some sort of template,
> static_cast!(T)), then people are just going to write "cast" instead
> of "static_cast!" because (a) it's shorter, and (b) the programmer's
> inherent belief that everything they write is bug free. That's exactly
> what happens in C++.

But in C++ I can not grep for old-style cast. In D I can (but see my other message...)

Another thing I noticed:

class C {}

void main(string[] args)
{
    auto c = new const C;
}

Does not compile. But if I change to const(C) it does compile. It is a bit unusual that parens make such a big difference. Thank you, Dee Girl
May 10, 2008
On 10/05/2008, Dee Girl <deegirl@noreply.com> wrote:
> But in C++ I can not grep for old-style cast. In D I can.

That still doesn't help. You can stare at a bug for ages, and still not see it. But if the compiler tells you it's there, then you can slap your head and say "Doh! Of course! Why didn't I see that!?"

The point being that it is way, way, /way/ better to catch bugs at compile-time, than at run-time. Any word for "catching bugs at run time" is "crashing". :-)

We had exactly this situation recently with

    class C {}
    C c;
    if (c == null) // now a compile-time error.

This was a run-time error for ages. ("c == anything" is translated to "c.opEquals(anything)", which will crash if c is unassigned. But the coder was attempting to test whether or not c was unassigned). Finally it's a compile-time error. Moving the bug detection to compile-time not only saved a lot of people a lot of hair-pulling out, it also exposed many bugs in Phobos.

You can grep for "== null", but, so what? People don't. Sometimes its hard to see your own bugs. Having the compiler tell you that they're there is just fantastic. That's the reason for this proposal (the latest version of which is, I think, pretty reasonable).

1) convert or downcast: cast(Foo)bar
2) reinterpret: cast!(Foo)bar
3) constancy: cast(invariant)bar, cast(!const)bar
May 10, 2008
thanks for the added examples and explanations to the proposal.
I want to note two things though:
a) I did suggest cast(!invariant) instead of cast(mutable) but I'm not
sure it's more readable. maybe cast(!const) is a bit better (and
shorter) but the lack of symmetry bothers me.
b) Downcasts - the danger with them is that they can fail if you
downcast to the wrong derived class. that is why I've suggested:
class B : A;
class C : A;
A b = new B;
C c1 = cast!(C)b;
C c2 = cast(C)b;

the first cast will fail and return a null. the exclamation mark should remind the programmer that this can happen and therefore it's his responsibility to check the returned value of the cast. This is current D behavior.

OTOH, the second cast is a "safe" cast and must always work, therefore the failure to cast will result in an exception being thrown.

another thing: the second cast does not have to fail if the user defined
 his own cast from B to C.

"cast" can be defined by the user, but I'm not sure whether it would be
a good idea to allow the same for "cast!"
what do you think?

--Yigal
May 10, 2008
On 10/05/2008, Yigal Chripun <yigal100@gmail.com> wrote:
>  b) Downcasts - the danger with them is that they can fail if you
>  downcast to the wrong derived class. that is why I've suggested:
> <snip>

Right. But in my view cast! (with an exclamation mark) can never fail as such, because it's the programmer telling the compiler, "Yes, I know you think this is wrong, but dammit I know what I'm doing, so deal with it!". It means that the compiler makes no checks, and trusts the programmer. (It's reinterpret_cast<T>). Therefore, it can never return null.

But you are certainly correct in that there is a fundamental difference between static_cast<T> and dynamic_cast<T>. The former is a compile-time check, and the latter is a run-time check, so when the user types

    cast(T)x;

the compiler does a compile-time check to determine whether the static type, typeof(x), could be downcast to T, and if so, generates code which performs a runtime check, which might return null. Although this works, the user is never entirely sure (a) whether the compiler has generated a straight conversion which will always succeed, or (b) whether the compiler has generated a runtime check, which is slower, and might fail.

For that reason, there is merit in wanting an explicit dynamic_cast<T> equivalent. But it can't be cast!(T), because we've already used that for reinterpret_cast<T> - a cast which performs no checks at all, not even at compile time. Maybe we need a different syntax still for dynamic_cast<T>. One possibility (thought I don't recommend it) is:

    cast(class T)x

The problem with that is that all the existing code which expects cast(T)x to be a dynamic cast will still compile, give the wrong result, and never return null. The change would introduce bugs, which is the exact opposite of the intent. For that reason, perhaps dynamic cast should be the default (so that existing code will still compile and give the /right/ result), with static_cast<T> having a different syntax.

One really, really obvious syntax for static_cast<T>(x) is

    static cast(T)x

which I think is pretty cool. It tells the compiler "I definitely want to bypass the run-time check, but I still want the compile-time check".


>  "cast" can be defined by the user, but I'm not sure whether it would be
>  a good idea to allow the same for "cast!"
>  what do you think?

I think that cast! should mean reinterpret_cast<T>, and therefore always succeeds, bypassing all checks. Since it merely reinterprets the bits, it cannot be overloadable.
May 10, 2008
BCS 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.

current implementation of cast already does the typecheck, so there will be no performance issue.
May 10, 2008
I do not agree with the premise that a reinterpret cast always succeeds, even the C++ version can throw an exception!

maybe, my use of the word "fail" was not accurate. let me re-phrase my
thoughts:
there are only two ways to coerce one type into another:
a) you convert the value (i.e you change the underlying memory) - this
should always produce the expected result [I mark this with cast(T)]
b) you do _not_ convert the value, you just change the type tag attached
to the value - this can produce bad results which do not conform to the
underlying memory scheme of the type. [I mark this with cast!(T)]
let's look at an example of the latter:
int a = ...;
auto c = cast!(dchar) a;

suppose the bit layout of a is illegal utf-32 encoding. would you prefer
D allowed storing such an illegal value in a dchar?
IMO, a strongly typed language (like D) must enforce at all times that
its variables are valid. I do not want D to allow storing illegal values
like that. that must be an error.

Am I making any sense?

--Yigal
May 10, 2008
Robert Fraser Wrote:

> 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;
> }
> 

... should become ... :))))

Shape s = getShape();
return s.square();
May 10, 2008
Robert Fraser Wrote:

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

I agree here. I think that language should be simple and understandable, I see no difference between new construct and new keyword, if new construct is easy to understand, then it's good, but if it's not, no mysterious goal of minimizing number of keywords should be pursued at the cost of strange constructs.