Thread overview | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
May 09, 2008 Safer casts | ||||
---|---|---|---|---|
| ||||
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? |
May 09, 2008 Re: Safer casts | ||||
---|---|---|---|---|
| ||||
Posted in reply to Janice Caron | Janice Caron Wrote:
> 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
1) I don't think you can throw away the cast keyword, this will break all existing code.
2) how about interfaces?
3) I believe reinterpret_cast was introduced as analog of C cast, D already has cast for this purpose. I don't think D needs union!().
4) D already has cast to add/remove constancy/invariance.
My proposal was just adding safe cast for downcasts with the syntax of existing cast, nothing more.
|
May 09, 2008 Re: Safer casts | ||||
---|---|---|---|---|
| ||||
Posted in reply to Janice Caron | and I like existing cast keyword, it's short enough to write and long enough to spot. |
May 09, 2008 Re: Safer casts | ||||
---|---|---|---|---|
| ||||
Posted in reply to Janice Caron | And I hate those exclamation marks and chains of brackets. |
May 09, 2008 Re: Safer casts | ||||
---|---|---|---|---|
| ||||
Posted in reply to terranium | 2008/5/9 terranium <spam@here.lot>: > 1) I don't think you can throw away the cast keyword, this will break all existing code. This is exactly the problem C++ had. C++ introduced new, better cast types: static_cast<T>, dynamic_cast<T>, const_cast<T>, and reinterpret_cast<T>. However, where C++ went wrong is they failed to outlaw the original cast - presumably for the same reason, that it would break existing code. The problem is that unless you outlaw the old "one cast for all purposes" cast, then people will continue to use it, when what you really want it to force everyone to migrate to the new, "safe" ways. > 2) how about interfaces? They're a run-time thing, so is!(T) and class!(T) should work for interfaces just as for classes. This is where D differs from C++, of course, because we have interfaces where C++ has multiple inheritance. But yeah - just as C++'s dynamic_cast<T> works for multiple inheritance, so class!(T) should work for interfaces - though I note that the name is now less apt. > 3) I believe reinterpret_cast was introduced as analog of C cast, It was introduced to replace /one/ kind of transformation which old-style casts did, but not all of them. For example // C++ double x; int y = reinterpret_cast<int>(x); // won't compile int y = static_cast<int>(x); // OK > D already has cast for this purpose. I don't think D needs union!(). D already has cast for /all/ of the purposes I listed, so you could argue that D doesn't need any of them. The point is, if you wanted to be explicit about exactly what kind of transformation you wanted, then you would need it. > 4) D already has cast to add/remove constancy/invariance. Again, D already has cast, which can do each and every transformation I listed. What it can't do is emit an error if the wrong kind of transformation is performed. > My proposal was just adding safe cast for downcasts with the syntax of existing cast, nothing more. I wasn't disputing that. Consider this a separate proposal. |
May 09, 2008 Re: Safer casts | ||||
---|---|---|---|---|
| ||||
Posted in reply to terranium | 2008/5/9 terranium <spam@here.lot>:
> and I like existing cast keyword, it's short enough to write and long enough to spot.
I like it too. But it has its disadvantages. For example, you might write a dynamic cast as
void f(in A a)
{
B b = cast(B) a;
if (b !is null)
{
/* ... */
}
/*...*/
}
and it would compile. Unfortunately, you have just accidently cast away const, and that kind of bug is hard to spot. But replace it with
void f(in A a)
{
if (is!(B)(a))
{
B b = class!(B)(a);
/* ... */
}
/*...*/
}
and you have a compile-time error, because class!(T) cannot change
const.into mutable.
So I guess there's a tradeoff between "nice" syntax, like cast(T), and "safe" syntax. I'm just wondering if we should err on the side of safe
|
May 09, 2008 Re: Safer casts | ||||
---|---|---|---|---|
| ||||
Posted in reply to Janice Caron | Janice Caron Wrote: > I note > that the name is now less apt. that's what I meant. > > 3) I believe reinterpret_cast was introduced as analog of C cast, > > It was introduced to replace /one/ kind of transformation which old-style casts did, but not all of them. For example > > // C++ > double x; > int y = reinterpret_cast<int>(x); // won't compile > int y = static_cast<int>(x); // OK > > D already has cast for /all/ of the purposes I listed, so you could argue that D doesn't need any of them. The point is, if you wanted to be explicit about exactly what kind of transformation you wanted, then you would need it. My opinion is we don't need to be explicit when casting double to int. reinterpret_cast is used usually to cast to/from void* - that is for pointer types - here it has no difference with D cast. Adding new construct adds complexity and requires extra time to learn and extra info to keep in mind, I believe these constructs' safeness is minor and it's not worth its complexity. > I wasn't disputing that. Consider this a separate proposal. I meant my proposal is better :))) We need only minor extra safeness. This can be achieved just by standardizing safe cast. |
May 09, 2008 Re: Safer casts | ||||
---|---|---|---|---|
| ||||
Posted in reply to Janice Caron | 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
|
May 09, 2008 Re: Safer casts | ||||
---|---|---|---|---|
| ||||
On 09/05/2008, janderson <askme@me.com> wrote:
> I think that up casting for classes should be implicit for up cast, 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.
Yes, you are completely correct, of course.
|
May 09, 2008 Re: Safer casts | ||||
---|---|---|---|---|
| ||||
Posted in reply to Janice Caron | Janice Caron wrote:
> (*) 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.
>
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. (and performance snobs will start finding way to skip the second test (like your union) and then get it wrong.)
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).
|
Copyright © 1999-2021 by the D Language Foundation