www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Is opCast need, we have to!

reply Jesse Phillips <jessekphillips+D gmail.com> writes:
I'm not going to say it should be removed, but I'm just wondering if this is
really needed anymore, or maybe to! should instead make use of it.

opCast provides a means to convert an object/struct of one type to another
through an explicit cast operation. This is good as it allows generic code to
perform conversions through a standard call, and the result is a usable type.

I would like to advocate the use of to!() more often in D code than cast(). It
is much more robust, though has slightly different behavior (throws an
exception on class conversions error).

The main issue I see is that we can now convert classes in two forms. To will
make use of both on opCast function and a to function that is found in the
class. (opCast taking precedence in to!). The behavior for using these are
quite different, and be a reason to keep them distinct. In which case this is
just an informative message about their behavior and I'll add it to a wiki.

The example below shows 3 things:

 * order of precedence is: opCast, cast, to
 * defining opCast can disable casting to any type (its existence prevents
normal casting operations from happening)
 * the declared to!() function is not used when an opCast exists but doesn't
match.

Maybe the last one should be fixed.

Here is the code:

import std.conv;

void main() {
    B b = new B;
    b.i = 5;
    A ab = b;

    B bcast = cast(B) ab;
    assert(b.i == bcast.i); // Fail: used opCast function

    B bto   = to!B(ab);
    assert(b.i == bto.i); // Fail: used opCast function

    C c = new C;
    c.i = 5;
    A ac = c;
    //C ccast = cast(C) ac; // Doesn't compile: uses opCast
    //C cto   = to!C(ac); // Doesn't compile: uses opCast
    //
    // test.d(13): Error: template instance opCast!(C) 
    //             does not match template declaration opCast(T) if (is(T == B))
    //
    //assert(c.i == ccast.i);
    //assert(c.i == cto.i);

    E e = new E;
    e.i = 5;
    D de = e;

    E ecast = cast(E) de;
    assert(e.i == ecast.i); // Pass: uses cast

    E eto   = to!E(de);
    assert(e.i == eto.i); // Pass: uses cast 
}

class A {
    B opCast(T)() if(is(T == B)) {
        return new B;
    }

    C to(T)() if(is(T == C)) {
        auto b = new B;
        b.i = 5;
        return b;
    }
}

class B : A {
    int i;
}

class C : A {
    int i;
}

class D {
    E to(T)() if(is(T == E)) {
        return new E;
    }
}

class E : D {
    int i;
}
Dec 01 2010
next sibling parent reply foobar <foo bar.com> writes:
Jesse Phillips Wrote:

 I'm not going to say it should be removed, but I'm just wondering if this is
really needed anymore, or maybe to! should instead make use of it.
 
 opCast provides a means to convert an object/struct of one type to another
through an explicit cast operation. This is good as it allows generic code to
perform conversions through a standard call, and the result is a usable type.
 
 I would like to advocate the use of to!() more often in D code than cast(). It
is much more robust, though has slightly different behavior (throws an
exception on class conversions error).
 
 The main issue I see is that we can now convert classes in two forms. To will
make use of both on opCast function and a to function that is found in the
class. (opCast taking precedence in to!). The behavior for using these are
quite different, and be a reason to keep them distinct. In which case this is
just an informative message about their behavior and I'll add it to a wiki.
 
 The example below shows 3 things:
 
  * order of precedence is: opCast, cast, to
  * defining opCast can disable casting to any type (its existence prevents
normal casting operations from happening)
  * the declared to!() function is not used when an opCast exists but doesn't
match.
 
 Maybe the last one should be fixed.
 
 Here is the code:
 
 import std.conv;
 
 void main() {
     B b = new B;
     b.i = 5;
     A ab = b;
 
     B bcast = cast(B) ab;
     assert(b.i == bcast.i); // Fail: used opCast function
 
     B bto   = to!B(ab);
     assert(b.i == bto.i); // Fail: used opCast function
 
     C c = new C;
     c.i = 5;
     A ac = c;
     //C ccast = cast(C) ac; // Doesn't compile: uses opCast
     //C cto   = to!C(ac); // Doesn't compile: uses opCast
     //
     // test.d(13): Error: template instance opCast!(C) 
     //             does not match template declaration opCast(T) if (is(T ==
B))
     //
     //assert(c.i == ccast.i);
     //assert(c.i == cto.i);
 
     E e = new E;
     e.i = 5;
     D de = e;
 
     E ecast = cast(E) de;
     assert(e.i == ecast.i); // Pass: uses cast
 
     E eto   = to!E(de);
     assert(e.i == eto.i); // Pass: uses cast 
 }
 
 class A {
     B opCast(T)() if(is(T == B)) {
         return new B;
     }
 
     C to(T)() if(is(T == C)) {
         auto b = new B;
         b.i = 5;
         return b;
     }
 }
 
 class B : A {
     int i;
 }
 
 class C : A {
     int i;
 }
 
 class D {
     E to(T)() if(is(T == E)) {
         return new E;
     }
 }
 
 class E : D {
     int i;
 }
 

IMHO, coercions in D should be redesigned. They are a tiny bit better than C but C is a weekly (and poorly) typed language. I personally prefer the properly strongly typed ML family of languages. My preference is as follows: 1. static_cast: a. Remove ALL implicit coercions inherited from c such as double -> int, b. I don't see the need for an operator for conversions since they can have different parameters, e.g.: - converting to string can have formatting specified - converting string to numeric types with optional base parameter - converting integer to floating point specifies round/floor/ceiling/etc.. 2. const_cast: should be a _separate_ operator in order to prevent removing const by mistake. const Base obj1 = new Derived(); auto obj2 = cast(Derived)(obj1); // oops: meant to only down cast 3. dynamic_cast: the language should provide a down cast operator for OO. 4. reinterpret_cast: unsafe and should be restricted as much as possible (Not available in safe code) maybe not provide it at all since it's implementable via union. A restricted library solution for when you really want to play with bits and bytes? the above means that: double pi = 3.14; int p = pi; // compile-time error int p = floor(pi); // ok and also: int x = ??; // some number double y = x; //compile-time error double z = double(x); // explicit double r = 5 / 2; // compile error choose either: a. double r1 = double(5/2); // 2.0 b. double r2 = double (5) / 2; // 2.5
Dec 01 2010
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
foobar:

 1. static_cast: 
   a. Remove ALL implicit coercions inherited from c such as double -> int,
   b. I don't see the need for an operator for conversions since they can have
different parameters, e.g.:
   - converting to string can have formatting specified
   - converting string to numeric types with optional base parameter
   - converting integer to floating point specifies round/floor/ceiling/etc..
 2. const_cast: should be a _separate_ operator in order to prevent removing
const by mistake.
   const Base obj1 = new Derived();
   auto obj2 = cast(Derived)(obj1); // oops: meant to only down cast
 3. dynamic_cast: the language should provide a down cast operator for OO. 
 4. reinterpret_cast: unsafe and should be restricted as much as possible (Not
available in  safe code) maybe not provide it at all since it's implementable
via union. A restricted library solution for when you really want to play with
bits and bytes? 

There are some ideas here, like the separation from const_cast, dynamic cast, and other casts. In past I too have asked for something similar, but I think Walter was not interested. I guess the idea of having a single cast is to keep the language simple. But those _are_ different kinds of casts, they have different semantics. Lumping different semantics into the same syntax is not a good way to simplify a language, in my opinion. It just creates obscurity and maybe some other troubles too. Bye, bearophile
Dec 01 2010
parent Sclytrack <sclyrack idiot.com> writes:
== Quote from bearophile (bearophileHUGS lycos.com)'s article
 foobar:
 1. static_cast:
   a. Remove ALL implicit coercions inherited from c such as double -> int,
   b. I don't see the need for an operator for conversions since they can have


   - converting to string can have formatting specified
   - converting string to numeric types with optional base parameter
   - converting integer to floating point specifies round/floor/ceiling/etc..
 2. const_cast: should be a _separate_ operator in order to prevent removing


   const Base obj1 = new Derived();
   auto obj2 = cast(Derived)(obj1); // oops: meant to only down cast
 3. dynamic_cast: the language should provide a down cast operator for OO.
 4. reinterpret_cast: unsafe and should be restricted as much as possible (Not


union. A restricted library solution for when you really want to play with bits and bytes?
 There are some ideas here, like the separation from const_cast, dynamic cast,

Walter was not interested.
 I guess the idea of having a single cast is to keep the language simple. But

different semantics into the same syntax is not a good way to simplify a language, in my opinion. It just creates obscurity and maybe some other troubles too.
 Bye,
 bearophile

How about the following. T1 a; T2 b; b = cast(T2, T1) a; The difference between the two types would be clearly visible.
Dec 01 2010
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, December 01, 2010 14:32:12 bearophile wrote:
 foobar:
 1. static_cast:
   a. Remove ALL implicit coercions inherited from c such as double ->
   int, b. I don't see the need for an operator for conversions since
   they can have different parameters, e.g.: - converting to string can
   have formatting specified
   - converting string to numeric types with optional base parameter
   - converting integer to floating point specifies
   round/floor/ceiling/etc..
 
 2. const_cast: should be a _separate_ operator in order to prevent
 removing const by mistake.
 
   const Base obj1 = new Derived();
   auto obj2 = cast(Derived)(obj1); // oops: meant to only down cast
 
 3. dynamic_cast: the language should provide a down cast operator for OO.
 4. reinterpret_cast: unsafe and should be restricted as much as possible
 (Not available in  safe code) maybe not provide it at all since it's
 implementable via union. A restricted library solution for when you
 really want to play with bits and bytes?

There are some ideas here, like the separation from const_cast, dynamic cast, and other casts. In past I too have asked for something similar, but I think Walter was not interested. I guess the idea of having a single cast is to keep the language simple. But those _are_ different kinds of casts, they have different semantics. Lumping different semantics into the same syntax is not a good way to simplify a language, in my opinion. It just creates obscurity and maybe some other troubles too.

And how many programmers do you know who actually, really know the differences between the 4 C++ cast types. _I_'m not sure that _I_ know, and I've studied it. And honestly, in most cases - if not in _all_ cases - as far as I can tell, the compiler should be able to determine the correct cast type. So, forcing that on the programmer is just stupid. If you actually _need_ separate cast types, then we should have them, but if we can avoid that, we definitely should. I think that the fact that C++ has so many types of casts is horrible. I try and use them correctly, but I don't think that there are very many programmers (percentage- wise at least) who do. I know plenty of programmers who just use C-style casts everywhere. - Jonathan M Davis
Dec 01 2010
prev sibling parent reply Jesse Phillips <jessekphillips+D gmail.com> writes:
foobar Wrote:

 IMHO, coercions in D should be redesigned. They are a tiny bit better than C
but C is a weekly (and poorly) typed language. I personally prefer the properly
strongly typed ML family of languages.
 
 My preference is as follows:
 1. static_cast: 
   a. Remove ALL implicit coercions inherited from c such as double -> int,

Done
   b. I don't see the need for an operator for conversions since they can have
different parameters, e.g.:
   - converting to string can have formatting specified
   - converting string to numeric types with optional base parameter
   - converting integer to floating point specifies round/floor/ceiling/etc..

This was kind of my point, to! already specifies how to generically (an important part) to other types. And opCast overrides everything (which I don't wish to claim is incorrect, just asking).
 2. const_cast: should be a _separate_ operator in order to prevent removing
const by mistake.
   const Base obj1 = new Derived();
   auto obj2 = cast(Derived)(obj1); // oops: meant to only down cast

I think to! should be changed such that this would be a ConvException.
 3. dynamic_cast: the language should provide a down cast operator for OO. 

Then the example above is invalid, though still valid when casting const double to int...
 4. reinterpret_cast: unsafe and should be restricted as much as possible (Not
available in  safe code) maybe not provide it at all since it's implementable
via union. A restricted library solution for when you really want to play with
bits and bytes? 
 
 the above means that:
 double pi = 3.14; 
 int p = pi; // compile-time error

Currently is an error.
 int p = floor(pi); // ok

Maybe std.math.floor should return an int, since they are implicitly converted to real...
 and also:
 int x = ??; // some number
 double y = x; //compile-time error
 double z = double(x); // explicit

Other than overflow, which isn't fixed if made explicit, I don't see an issue with it being implicit.
 double r = 5 / 2; // compile error

Well this might be one, if 5 and 2 are variables.
 choose either:
 a.  double r1 = double(5/2); // 2.0
 b. double r2 = double (5) / 2; // 2.5

Assuming variables here, wouldn't that need to be: double r2 = cast(double) 5 / cast(double) 2; // not implicit casting remember.
Dec 01 2010
parent foobar <foo bar.com> writes:
Jesse Phillips Wrote:

 foobar Wrote:
 
 IMHO, coercions in D should be redesigned. They are a tiny bit better than C
but C is a weekly (and poorly) typed language. I personally prefer the properly
strongly typed ML family of languages.
 
 My preference is as follows:
 1. static_cast: 
   a. Remove ALL implicit coercions inherited from c such as double -> int,

Done
   b. I don't see the need for an operator for conversions since they can have
different parameters, e.g.:
   - converting to string can have formatting specified
   - converting string to numeric types with optional base parameter
   - converting integer to floating point specifies round/floor/ceiling/etc..

This was kind of my point, to! already specifies how to generically (an important part) to other types. And opCast overrides everything (which I don't wish to claim is incorrect, just asking).

I don't follow as to how this is important to have a generic interface. Can I specify parameters for conversion with to! as in my examples? I'd appreciate an example
 2. const_cast: should be a _separate_ operator in order to prevent removing
const by mistake.
   const Base obj1 = new Derived();
   auto obj2 = cast(Derived)(obj1); // oops: meant to only down cast

I think to! should be changed such that this would be a ConvException.

This should not even compile. a run-time exception is better than current D but is still very weak.
 
 3. dynamic_cast: the language should provide a down cast operator for OO. 

Then the example above is invalid, though still valid when casting const double to int...

a down cast should _only_ perform dynamic down casts in the OO sense. so: Base foo = new Derived; Derived bar = downCast(foo); // compiles. performed at run-time [const] double -> int is not a down cast, it is a conversion.
 
 4. reinterpret_cast: unsafe and should be restricted as much as possible (Not
available in  safe code) maybe not provide it at all since it's implementable
via union. A restricted library solution for when you really want to play with
bits and bytes? 
 
 the above means that:
 double pi = 3.14; 
 int p = pi; // compile-time error

Currently is an error.
 int p = floor(pi); // ok

Maybe std.math.floor should return an int, since they are implicitly converted to real...
 and also:
 int x = ??; // some number
 double y = x; //compile-time error
 double z = double(x); // explicit

Other than overflow, which isn't fixed if made explicit, I don't see an issue with it being implicit.

besides the overflow issue you have mentioned, I also don't want special cases. No implicit conversions should be applied equally everywhere.
 
 double r = 5 / 2; // compile error

Well this might be one, if 5 and 2 are variables.
 choose either:
 a.  double r1 = double(5/2); // 2.0
 b. double r2 = double (5) / 2; // 2.5

Assuming variables here, wouldn't that need to be: double r2 = cast(double) 5 / cast(double) 2; // not implicit casting remember.

This depends on the definition of the "/" operator. (double / int) always returns a double so no casting is required.
Dec 02 2010
prev sibling parent Jesse Phillips <jessekphillips+D gmail.com> writes:
Looking at the code it seems the Doc is actually in need of updating[1].

But I feel that using to! should be a much safer choice and have thus suggested
an improvement along with a patch[2].

1. http://d.puremagic.com/issues/show_bug.cgi?id=5308
2. http://d.puremagic.com/issues/show_bug.cgi?id=5307
Dec 01 2010