June 21, 2007
Don Clugston wrote:
> Walter Bright wrote:
>> With D, you can cast away const-ness, that is legal. But if you subsequently modify the underlying data, that is undefined behavior.
> 
> It sounds that in D, it will be too easy to cast away constness accidentally.
> With C++, at least you can grep for const_cast and detect potentially dangerous code, and you get a strong visual clue.
> Suppose I've written a D function like this:
> 
> void f(int *b, uint c)
> {
>   // maybe I'm avoiding a compiler warning or something.
>   uint *d = cast(uint *)b;
>   d += c;
> }
> 
> Months later, I'm refactoring the code, and I convert the int * parameter to an invariant, without recognising that it's changing the value of b. Oops.
> 
> C++'s const would catch this mistake, but if I understand correctly, D will compile it without error. Suddenly the function has moved into the realm of undefined behaviour.
> 
> I hope I'm wrong. Or did I miss something?

No, you're not missing something. It is a general problem with cast - cast is a blunt instrument which can easily hide problems.
June 21, 2007
Walter Bright wrote:
> Don Clugston wrote:
>> Walter Bright wrote:
>>> With D, you can cast away const-ness, that is legal. But if you subsequently modify the underlying data, that is undefined behavior.
>>
>> It sounds that in D, it will be too easy to cast away constness accidentally.
>> With C++, at least you can grep for const_cast and detect potentially dangerous code, and you get a strong visual clue.
>> Suppose I've written a D function like this:
>>
>> void f(int *b, uint c)
>> {
>>   // maybe I'm avoiding a compiler warning or something.
>>   uint *d = cast(uint *)b;
>>   d += c;
>> }
>>
>> Months later, I'm refactoring the code, and I convert the int * parameter to an invariant, without recognising that it's changing the value of b. Oops.
>>
>> C++'s const would catch this mistake, but if I understand correctly, D will compile it without error. Suddenly the function has moved into the realm of undefined behaviour.
>>
>> I hope I'm wrong. Or did I miss something?
> 
> No, you're not missing something. It is a general problem with cast - cast is a blunt instrument which can easily hide problems.

This means that cast() has just become even more unsafe. So for 2.0, it will be even more important to provide ways to avoid usage of cast().
The .ptr, .re, and .im properties were a huge help; maybe the idea can be extended to other cases where a cast is perfectly safe.

Usage of invariants inside unions is suspect too.
At least where some members are invariant and others are not const, it's asking for trouble -- cast() by stealth.

union U {
 invariant C *c;
 C *d;
}
Though arguably unions are always a blunt, dangerous instrument as well.
June 21, 2007
On Thu, 21 Jun 2007 11:32:53 +0400, Walter Bright <newshound1@digitalmars.com> wrote:

> Don Clugston wrote:
>> Suppose I've written a D function like this:
>>  void f(int *b, uint c)
>> {
>>   // maybe I'm avoiding a compiler warning or something.
>>   uint *d = cast(uint *)b;
>>   d += c;
>> }
>>  Months later, I'm refactoring the code, and I convert the int * parameter to an invariant, without recognising that it's changing the value of b. Oops.
>>  C++'s const would catch this mistake, but if I understand correctly, D will compile it without error. Suddenly the function has moved into the realm of undefined behaviour.
>>  I hope I'm wrong. Or did I miss something?
>
> No, you're not missing something. It is a general problem with cast - cast is a blunt instrument which can easily hide problems.

May be it is better to define two new cast operators: const_cast, that removes only const, and invariant_cast, that removes only invariant. Ordinal cast() can't remove const/invariantness.

In such case Don Clugston's example will produce compile-time error (because cast() can't remove invariant). And all potentially dangerous places in a program can be easily detected by simply greeping /(const|invariant)_cast/


-- 
Regards,
Yauheni Akhotnikau
June 21, 2007
Don Clugston wrote:
> It sounds that in D, it will be too easy to cast away constness accidentally.
> With C++, at least you can grep for const_cast and detect potentially dangerous code, and you get a strong visual clue.
> Suppose I've written a D function like this:
> 
> void f(int *b, uint c)
> {
>   // maybe I'm avoiding a compiler warning or something.
>   uint *d = cast(uint *)b;
>   d += c;
> }
> 
> Months later, I'm refactoring the code, and I convert the int * parameter to an invariant, without recognising that it's changing the value of b. Oops.

a syntactical search would be needed instead of a grep. until refactoring tools for D surface, an optional compiler warning for casts resulting in undefined behaviour would be nice...
June 21, 2007
Don Clugston skrev:
> Walter Bright wrote:
>> Don Clugston wrote:
>>> Walter Bright wrote:
>>>> With D, you can cast away const-ness, that is legal. But if you subsequently modify the underlying data, that is undefined behavior.
>>>
>>> It sounds that in D, it will be too easy to cast away constness accidentally.
>>> With C++, at least you can grep for const_cast and detect potentially dangerous code, and you get a strong visual clue.
>>> Suppose I've written a D function like this:
>>>
>>> void f(int *b, uint c)
>>> {
>>>   // maybe I'm avoiding a compiler warning or something.
>>>   uint *d = cast(uint *)b;
>>>   d += c;
>>> }
>>>
>>> Months later, I'm refactoring the code, and I convert the int * parameter to an invariant, without recognising that it's changing the value of b. Oops.
>>>
>>> C++'s const would catch this mistake, but if I understand correctly, D will compile it without error. Suddenly the function has moved into the realm of undefined behaviour.
>>>
>>> I hope I'm wrong. Or did I miss something?
>>
>> No, you're not missing something. It is a general problem with cast - cast is a blunt instrument which can easily hide problems.
> 
> This means that cast() has just become even more unsafe. So for 2.0, it will be even more important to provide ways to avoid usage of cast().
> The .ptr, .re, and .im properties were a huge help; maybe the idea can be extended to other cases where a cast is perfectly safe.

I had look at the places I use casts in some of my projects, and found some categories (in no particular order):

1. Cast needed by the archaic typeinfo system, example:

class C {
  int opCmp(Object o) { auto c = cast(C) o; assert(c !is null); ... }
}

typeid(char[]).compare(cast(void *) &str1, cast(void *) &str2);

2. casts from void[], void*

3. Unsigned/signed casts and working around stupid promotion rules:

template T(int n) { ... }

uint n = ...;
T!(cast(int) n) // cast required

// Spot the bug # 1
double randomDelta() {
    return (rand() % 3) - 1;
}

// spot the bug #2
void fun(int[] arr) {
    long d = arr.length - 10;
    while (d > 0)
        arr[--d] = 0;
}

4. casts to subclasses (c++ dynamic_cast) (personally very seldom used)

5. casts needed for overload resolution:

std.math.pow(cast(real) doubleVar, intVar); // sigh

6. rational -> floating point

cast(double) a.length / b.length

I can't really see how most of those casts can go away (except #1). If D got something like generic method injection, one could replace

cast(double) intVar

with

intVar.toDouble, or intVar.to!(double)

but is that any better?

/Oskar
June 21, 2007
Walter Bright Wrote:
> Don Clugston wrote:
> > Walter Bright wrote:
> >> With D, you can cast away const-ness, that is legal. But if you subsequently modify the underlying data, that is undefined behavior.
> > 
> > It sounds that in D, it will be too easy to cast away constness
> > accidentally.
> > With C++, at least you can grep for const_cast and detect potentially
> > dangerous code, and you get a strong visual clue.
> > Suppose I've written a D function like this:
> > 
> > void f(int *b, uint c)
> > {
> >   // maybe I'm avoiding a compiler warning or something.
> >   uint *d = cast(uint *)b;
> >   d += c;
> > }
> > 
> > Months later, I'm refactoring the code, and I convert the int * parameter to an invariant, without recognising that it's changing the value of b. Oops.
> > 
> > C++'s const would catch this mistake, but if I understand correctly, D will compile it without error. Suddenly the function has moved into the realm of undefined behaviour.
> > 
> > I hope I'm wrong. Or did I miss something?
> 
> No, you're not missing something. It is a general problem with cast - cast is a blunt instrument which can easily hide problems.

So.. we're going to have to put up with this potential nasty bug?

What about a new cast which only removes 'const' and/or 'invariant and prohibit normal cast from removing it.

Perhaps calling it 'vary', eg.

void f(const int *b, uint c)
{
   int *d = vary() b;
}

Not sure about the (), if they're needed, or if vary(b) would be a better syntax.

The basic point being that cast cannot then cause the nasty bug and vary can be searched/grepped for.

Regan
June 21, 2007
Regan Heath wrote:
> Walter Bright Wrote:
>> Don Clugston wrote:
>>> Walter Bright wrote:
>>>> With D, you can cast away const-ness, that is legal. But if you subsequently modify the underlying data, that is undefined behavior.
>>> It sounds that in D, it will be too easy to cast away constness accidentally.
>>> With C++, at least you can grep for const_cast and detect potentially dangerous code, and you get a strong visual clue.
>>> Suppose I've written a D function like this:
>>>
>>> void f(int *b, uint c)
>>> {
>>>   // maybe I'm avoiding a compiler warning or something.
>>>   uint *d = cast(uint *)b;
>>>   d += c;
>>> }
>>>
>>> Months later, I'm refactoring the code, and I convert the int * parameter to an invariant, without recognising that it's changing the value of b. Oops.
>>>
>>> C++'s const would catch this mistake, but if I understand correctly, D will compile it without error. Suddenly the function has moved into the realm of undefined behaviour.
>>>
>>> I hope I'm wrong. Or did I miss something?
>> No, you're not missing something. It is a general problem with cast - cast is a blunt instrument which can easily hide problems.
> 
> So.. we're going to have to put up with this potential nasty bug?
> 
> What about a new cast which only removes 'const' and/or 'invariant and prohibit normal cast from removing it.
> 
> Perhaps calling it 'vary', eg.
> 
> void f(const int *b, uint c)
> {
>    int *d = vary() b;
> }

To avoid a new keyword...

int* d = cast(break const) b;

IMHO, we want something that looks really nasty.
June 21, 2007
Don Clugston skrev:

> To avoid a new keyword...
> 
> int* d = cast(break const) b;
> 
> IMHO, we want something that looks really nasty.

Reusing break is brilliant! Another alternative:

int *d = break(const) b;

/Oskar
June 21, 2007
Don Clugston Wrote:

> Regan Heath wrote:
> > Walter Bright Wrote:
> >> Don Clugston wrote:
> >>> Walter Bright wrote:
> >>>> With D, you can cast away const-ness, that is legal. But if you subsequently modify the underlying data, that is undefined behavior.
> >>> It sounds that in D, it will be too easy to cast away constness
> >>> accidentally.
> >>> With C++, at least you can grep for const_cast and detect potentially
> >>> dangerous code, and you get a strong visual clue.
> >>> Suppose I've written a D function like this:
> >>>
> >>> void f(int *b, uint c)
> >>> {
> >>>   // maybe I'm avoiding a compiler warning or something.
> >>>   uint *d = cast(uint *)b;
> >>>   d += c;
> >>> }
> >>>
> >>> Months later, I'm refactoring the code, and I convert the int * parameter to an invariant, without recognising that it's changing the value of b. Oops.
> >>>
> >>> C++'s const would catch this mistake, but if I understand correctly, D will compile it without error. Suddenly the function has moved into the realm of undefined behaviour.
> >>>
> >>> I hope I'm wrong. Or did I miss something?
> >> No, you're not missing something. It is a general problem with cast - cast is a blunt instrument which can easily hide problems.
> > 
> > So.. we're going to have to put up with this potential nasty bug?
> > 
> > What about a new cast which only removes 'const' and/or 'invariant and prohibit normal cast from removing it.
> > 
> > Perhaps calling it 'vary', eg.
> > 
> > void f(const int *b, uint c)
> > {
> >    int *d = vary() b;
> > }
> 
> To avoid a new keyword...
> 
> int* d = cast(break const) b;

what about?
int* d =cast(!const)b;
> 
> IMHO, we want something that looks really nasty.

June 21, 2007

Don Clugston wrote:
> To avoid a new keyword...
> 
> int* d = cast(break const) b;
> 
> IMHO, we want something that looks really nasty.

That's inspired, Don.  No need for new keywords, *and* it perfectly conveys what it's doing.  I also like that it's somewhat longer than a regular cast, since this is not the sort of thing you want to do lightly.

	-- Daniel