digitalmars.D - Is opCast need, we have to!
- Jesse Phillips <jessekphillips+D gmail.com> Dec 01 2010
- foobar <foo bar.com> Dec 01 2010
- bearophile <bearophileHUGS lycos.com> Dec 01 2010
- Sclytrack <sclyrack idiot.com> Dec 01 2010
- Jonathan M Davis <jmdavisProg gmx.com> Dec 01 2010
- Jesse Phillips <jessekphillips+D gmail.com> Dec 01 2010
- foobar <foo bar.com> Dec 02 2010
- Jesse Phillips <jessekphillips+D gmail.com> Dec 01 2010
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
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
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
== Quote from bearophile (bearophileHUGS lycos.com)'s articlefoobar: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
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
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,
Doneb. 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
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,
Doneb. 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 example2. 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
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









Sclytrack <sclyrack idiot.com> 