Safer casts

Janice Caron caron800 at googlemail.com
Sun May 11 17:28:46 PDT 2008


On 12/05/2008, Yigal Chripun <yigal100 at gmail.com> wrote:
>  if [re]interpret_cast can be implemented by the user and it is quite
>  dangerous, than i think people who need that should implement that
>  themselves with union as you showed. no need for D to provide that
>  functionality by default.

Agreed. As a general principle, if it can be implemented in a library,
then there's no need for new syntax. /However/ - it /should/ be
implemented in a library, because we'd want to outlaw

    void* p;
    char* q = cast(char*) p; /* ERROR -- cannot cast from void* to char* */

reinterpret_cast should be the only kind of cast to allow this:

    char* q = reinterpret_cast!(char*)(p); /* OK */

[EDIT - see below]


>  I'm still not sure about constancy casts. but in order to provide the
>  minimal solution, I'd prefer cast!(T) to do constancy casts over adding
>  another construct for that.

OK.


>  also, I don't like the static cast(), and it
>  might cause ambiguity problems. what is this good for?

There can't be any ambiguity problems (yet), because you can't (yet)
call functions which take a "this" parameter at compile time. If you
could, then "static cast(int)x", might invoke typeof(x)'s opCast()
function, so arguably there might be ambiguity problems in the future
-- if you're entering an obfuscated D contest! Let's ignore that for
now.

What's it good for? Performance. If B is a subclass of A, and I *know*
that my A is actualy a B, static_cast<B> allows me avoid the runtime
penalty that I'd get with dynamic_cast<B>. There would also be no need
to check the return value, because static_cast always succeeds.

Obviously, reinterpret_cast<B> will work everywhere that
static_cast<B> works, but static_cast<B>(a) gets me a compile-time
check that typeof(a) is a subclass or superclass of B, wheras with
reinterpret_cast<B>(a) there's no compile-time checking at all.

static_cast<T> can also do plain conversion, e.g.

    double d;
    int n = static_cast<int>(d);

So, if the old-fashioned default cast were ever outlawed in C++,
static_cast is the one you'd use to replace it for "plain" casts.

However - given that we choose to define cast(T) to mean "if T is a
class then use RTTI, otherwise don't", then, for non-classes, our
cast(T) achieves what static_cast<T> does for C++. For classes ...
well, who needs static cast anyway? Let's ditch it.

So that leaves us with:

    cast(T)x   // (T is a class) ? dynamic_cast : static_cast
    cast!(T)x   // const_cast
    reinterpret_cast!(T)(x)    // reinterpret_cast, implemented in library

That pretty much covers it.



>  open question: if this proposal does not provide a reinterpret_cast, can
>  we split the cast!(T) to perform either a type change /or/ a constancy
>  change?

I don't see how that could work in general.

We could allow it for specific cases though - e.g casting from void*
to T*, or from void[] to T[], etc. I think in those special cases, it
could work.

So, to turn an S* into a T* (where both are structs), you would do

    S* s = cast!(S*)cast(void*)t



>  // below case uses the same bit pattern, exception on invalid double
>  // value
>  auto d2 = cast!(double)n;

I don't like that, and I see no need for it. reinterpret_cast!(double)
can do that job. And there's no need for exception throwing - that
would entail a runtime check, and at this level, we must assume the
programmer knows what they're doing.


>  same goes for classes with user defined cast and cast! operators.

There should never be any such thing as a user-defined cast! operator.
It's meaningless. Look at it like this - C++ has four different kinds
of cast, but only one kind of cast operator. It's all you need.


>  downcast can be treated as a compiler pre-defined conversion from a
>  class to its derived classes. uses the cast(Derived) form and throws
>  exception on error.

It's not an error to fail an RTTI cast! e.g.

    void f(A a)
    {
        B1 b1 = cast(B1)a;
        if (b1 !is null) { ... }

        B2 b2 = cast(B2)a;
        if (b2 !is null) { ... }

        B3 b3 = cast(B3)a;
        if (b3 !is null) { ... }
    }

So no need for throwing exceptions.


>  constancy is performed via cast!(T).

OK.


>  pointers are cast with cast!(T)

Only pointers to void.

>  so to convert const(int)* to const(long*) you'll need something like:
>  auto res = cast!(const(long*))cast(const(long)*)x;

I would have said

    cast!(const(long*))cast(const(void)*)x;

or

    reinterpret_cast!(const(long*))x;



More information about the Digitalmars-d mailing list