www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Casts, especially array casts

reply bearophile <bearophileHUGS lycos.com> writes:
This comes from a recent small discussion on the D.learn newsgroup, and from an
answer by Daniel Keep:
http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D.learn&article_id=18915

There are many possible kinds of casts in D:
- To interpret a sequence of bits in a different way, like a uint as a float, a
double as a fixed array of two ints, a fixed array of 8 bytes into a fixed
array of 2 ints, etc, with no need of an intermediate union definition (And
sometimes I want to convert a class reference to a different class, currently
you can do it with: cast(Foo)cast(Void*)bar).
- To perform a safe dynamic cast between two objects, that can return null.
This operation is a bit slow.
- To perform a conversion, for example from double to an int, with truncation.
In future I can even want a way to tell the compiler how to perform such N to M
conversions, so I can convert an int to its string equivalent, etc.
- To perform a cast between two arrays, where I want to convert (reinterpret)
each item, using a true conversion.
- Maybe there are other kinds of casting (like turning an immutable into a
mutable, an impure into a pure, arg!, etc).

Conflating so much different things in the same cast() syntax is less unsafe
and is less flexible.

In C++ there are 4+1 kinds of casts, they add some complexity. Maybe in D2 just
two different casts can be enough:
- A cast that works at compile time only, with no run time penality (the first
and last type in my list).
- A cast that has to perform some operations at run time (the other kinds of
casts in my list).

I think this is an important enough thing for D2, and it's not a backwards
compatible change, it's not an additive change if you want to do it well
(otherwise if you want to follow the route used by C++ of just discouraging the
usage of the old C cast it can be an additive change, but it increases the
language complexity).

If you want to do this in a good way, you can for example use:
- static_cast() or scast() for the purely compile-time ones.
- dcast() or dynamic_cast(), or just cast() for the run-time ones. Such casts
are a bit safer, so they can use a shorter name.

Another good thing is to make the cast semantics more tidy, aligning the
effects of the casts done on an array literal with the effect of the same cast
done on a run-time array (I think they are a bit different now).

Bye,
bearophile
Feb 24 2010
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
 - A cast that has to perform some operations at run time (the other kinds of
casts in my list).

This was not precise enough: such conversion operations count if they are done at compile-time too. So you can use a static or dynamic cast at compile-time too, according to what you need. Bye, bearophile
Feb 24 2010
prev sibling parent reply Jonathan M Davis <jmdavisProg gmail.com> writes:
bearophile wrote:

 This comes from a recent small discussion on the D.learn newsgroup, and
 from an answer by Daniel Keep:
 

 
 There are many possible kinds of casts in D:
 - To interpret a sequence of bits in a different way, like a uint as a
 float, a double as a fixed array of two ints, a fixed array of 8 bytes
 into a fixed array of 2 ints, etc, with no need of an intermediate union
 definition (And sometimes I want to convert a class reference to a
 different class, currently you can do it with: cast(Foo)cast(Void*)bar). -
 To perform a safe dynamic cast between two objects, that can return null.
 This operation is a bit slow. - To perform a conversion, for example from
 double to an int, with truncation. In future I can even want a way to tell
 the compiler how to perform such N to M conversions, so I can convert an
 int to its string equivalent, etc. - To perform a cast between two arrays,
 where I want to convert (reinterpret) each item, using a true conversion.
 - Maybe there are other kinds of casting (like turning an immutable into a
 mutable, an impure into a pure, arg!, etc).
 
 Conflating so much different things in the same cast() syntax is less
 unsafe and is less flexible.
 
 In C++ there are 4+1 kinds of casts, they add some complexity. Maybe in D2
 just two different casts can be enough: - A cast that works at compile
 time only, with no run time penality (the first and last type in my list).
 - A cast that has to perform some operations at run time (the other kinds
 of casts in my list).
 
 I think this is an important enough thing for D2, and it's not a backwards
 compatible change, it's not an additive change if you want to do it well
 (otherwise if you want to follow the route used by C++ of just
 discouraging the usage of the old C cast it can be an additive change, but
 it increases the language complexity).
 
 If you want to do this in a good way, you can for example use:
 - static_cast() or scast() for the purely compile-time ones.
 - dcast() or dynamic_cast(), or just cast() for the run-time ones. Such
 casts are a bit safer, so they can use a shorter name.
 
 Another good thing is to make the cast semantics more tidy, aligning the
 effects of the casts done on an array literal with the effect of the same
 cast done on a run-time array (I think they are a bit different now).
 
 Bye,
 bearophile

C++ is the only language that I'm aware of which has multiple types of casts, and I don't think that it really adds anything of benefit. C-style casts are, of course, unsafe, but that doesn't mean that you need a bucket- load of cast types in order to be safe. Languages like Java and C# manage safe casting with only one type of cast. Two is definitely better than four, but I question that we really need more than one. Can't the compiler figure out which cast is supposed to be used in a given situation and deal with it internally? Having multiple casts just confuses things (certainly, I don't think that I know anyone who fully understands the C++ casts). Really, casts should be quite rare in code anyway. And as much as I'd like to avoid performance penalties, I'd prefer a performance penalty in the rare case when I have to cast rather than having to worry about whether I or anyone else on a project that I'm working on is using the various cast types correctly. In most C++ code that I've seen, people don't bother to use anything other than a c-style cast unless they specifically need one of the C++ casts for a safety reason - like using dynamic_cast to check whether you can safely cast to a particular type. In almost all cases, they just use a c-style cast. I think that it would be a huge mistake for D to have more than one type of cast. And honestly, I would have thought that the compiler would be able to figure out the correct cast type internally. And if it can't, should you really be doing that cast in the first place? I say keep the current single cast type and leave it at that. - Jonathan M Davis
Feb 24 2010
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
Jonathan M Davis:

 Languages like Java and C# manage safe casting with only one type of cast.<

In C# you have implicit and explicit casts (used with the same syntax, I think).
 Can't the compiler figure out which cast is supposed to be used in a given 
 situation and deal with it internally?

In this example the conversion can be a trunc of the float, or it can be a reinterpret of the bits contained in the float as an int, the compiler can't tell them apart (currently D performs the trunc/round/ceil, according to the FP flags): float x = 10.5; int y = cast(int)x; The same happens in the conversions of arrays, as I have explained in the post.
 I'd prefer a performance 
 penalty in the rare case when I have to cast rather than having to worry 
 about whether I or anyone else on a project that I'm working on is using the 
 various cast types correctly.

It's not just a matter of performance, it's also a matter of keeping the semantics more tidy, because currently different operations are conflated in the same syntax. I think D programmers can appreciate to have a way to tell apart the two kinds of casts I have explained, also because 90% of the times you can use the normal (run-time) cast, that can be called just cast() or dcast(). In most cases that's what you want, you can use it as the default one, and use a scast or static_cast when you want to reinterpret the bits of a value, struct or array, pointer (or class reference). At the moment cast() performs a static cast on run time arrays, a dynamic cast on array literals, a dynamic cast on values, a dynamic cast on class references, and I think a static cast between signed and unsigned integers. And to convert an integer into a string you need to!(). In my opinion this situation is a bit too much messy. Bye, bearophile
Feb 24 2010
prev sibling parent reply Rainer Deyke <rainerd eldwood.com> writes:
On 2/24/2010 14:30, Jonathan M Davis wrote:
 C++ is the only language that I'm aware of which has multiple types of 
 casts, and I don't think that it really adds anything of benefit.

Really? I think the casts are one of the areas where C++ has a huge advantage over C. Maybe the new casts are not as important as templates and RAII, but they're up there in the same category. The best part of the C++ cast system is that it uses template function syntax, which allows user-defined casts to use the same syntax. Casts are yet another issue where moving from C++ to D feels like regressing to C.
 Can't the compiler figure out which cast is supposed to be used in a given 
 situation and deal with it internally?

That's not the point.
 Having multiple casts just confuses 
 things (certainly, I don't think that I know anyone who fully understands 
 the C++ casts).

If you don't understand C++ casts, then you don't understand D casts. They do the same thing; the C++ version is just more explicit about what it does (and therefore safer and easier to read). -- Rainer Deyke - rainerd eldwood.com
Feb 24 2010
parent reply bearophile <bearophileHUGS lycos.com> writes:
Rainer Deyke:

 Casts are yet another issue where moving from C++ to D feels like
 regressing to C.

Yet, currently D casts aren't like C ones, D casts performs dynamic casts of object references, etc.
 If you don't understand C++ casts, then you don't understand D casts.

C++ has 4+1 casts, they are a good amount of complexity, and it's not easy to learn their difference and purposes. So probably Jonathan is right when he says many C++ programmers don't use C++ casts well and probably tend to stick to one or two only. I think casts of C++ are too much complex, as most things in C++ :-) That's why I have suggested only two casts for D, that differ a lot in purposes, and where most times you can use just one of them (the one with the simpler and shorter name) and be happy. Bye, bearophile
Feb 24 2010
parent reply Rainer Deyke <rainerd eldwood.com> writes:
On 2/24/2010 17:09, bearophile wrote:
 C++ has 4+1 casts, they are a good amount of complexity, and it's not
 easy to learn their difference and purposes.

C++ casts perform three different logical functions: - Removing cv-qualifiers. (const_cast) - Reinterpreting raw bytes. (reinterpret_cast) - Converting values. (static_cast/dynamic_cast) These are clearly distinct function. Accidentally performing the wrong type of cast is clearly an error, and should be flagged by the compiler. Ideally, casts should be (distinct) library functions, not language features. The only thing vaguely confusing about the C++ system is the distinction between static_cast and dynamic_cast. I wouldn't mind seeing those two merged into a conversion_cast. -- Rainer Deyke - rainerd eldwood.com
Feb 24 2010
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
Rainer Deyke:
 C++ casts perform three different logical functions:

After your answer and more thinking I have lost most interest in my proposal... -.- Bye, bearophile
Feb 24 2010
prev sibling parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
Rainer Deyke wrote:

 C++ casts perform three different logical functions:
   - Removing cv-qualifiers. (const_cast)

Agreed.
   - Reinterpreting raw bytes. (reinterpret_cast)

Safely though: It won't allow any wild bit representations. For really wild stuff, you need to use the C-style cast.
   - Converting values. (static_cast/dynamic_cast)

I would like to separate static_cast and dynamic_cast: - static_cast does two things: a) can convert values: this does call the user-defined conversion operators if necessary b) static_cast also communicates "trust me, this super class *really* is this subclass." In this latter case, no value is converted; rather, the type of the pointer value is changed, so that the member access offsets will be adjusted accordingly later on, when this pointer is used - dynamic_cast performs only (b) above, but safely: "if this super class is actually this subclass, set the pointer to point to it". Otherwise the pointer is set to null. Again, no value conversion here. Ali
Feb 24 2010