www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Casting away const and invariant

reply "Janice Caron" <caron800 googlemail.com> writes:
I've noticed that you can do this in D2.0, without knowing the type of p.

auto q = cast(const) p;

The type of q is now the type of p, but with added constness. However,
if you want to take the constness away, you must explicitly specify
the full type, as in:

auto r = cast(int *) q;

Seems to me, this is a good place for coding errors to creep in, and
it's the exact reason that const_cast<> was introduced into C++. So,
how about allowing

auto r = cast(!const) q; /* currently will not compile */

which would eliminate that sort of error.
Sep 09 2007
next sibling parent reply Matti Niemenmaa <see_signature for.real.address> writes:
Janice Caron wrote:
 I've noticed that you can do this in D2.0, without knowing the type of p.
 
 auto q = cast(const) p;
 
 The type of q is now the type of p, but with added constness. However,
 if you want to take the constness away, you must explicitly specify
 the full type, as in:
 
 auto r = cast(int *) q;
 
 Seems to me, this is a good place for coding errors to creep in, and
 it's the exact reason that const_cast<> was introduced into C++. So,
 how about allowing
 
 auto r = cast(!const) q; /* currently will not compile */
 
 which would eliminate that sort of error.

Casting away const leads to undefined behaviour and should not be done. In general, whenever you cast anything, you need to look carefully at exactly what it is you're casting from, to, and why. The whole reason why C++'s const is completely useless for optimization purposes is that you can cast it away at any time. One goal of D const was to allow optimization. Read the arguments against C++'s const here and think carefully about whether you really want a const_cast in D: http://www.digitalmars.com/d/const.html -- E-mail address: matti.niemenmaa+news, domain is iki (DOT) fi
Sep 09 2007
parent reply Matti Niemenmaa <see_signature for.real.address> writes:
Janice Caron wrote:
 Actually, forget the specific example. What I'm getting at is that,
 right now, it's possible to cast away const /accidentally/.
 
 My change would ensure that that was no longer the case.
 
 And you miss one of the major points about C++'s const_cast<>. Yes, we
 all agree that it's a BAD THING to do. But the point about C++'s
 const_cast<> is not that it will make constness go away, it's that
 static_cast<> /won't/.

The major point about C++'s const_cast<> is that it is defined behaviour. In D, if you cast away const, no matter how, it is undefined behaviour, and something you shouldn't do. I can understand disallowing casting away const, and I'm fine with that. But adding a mechanism specifically for that purpose (your "cast(!const)") is something I don't think is a good idea, because it breaks one of the principles on which D's new const semantics were built. It appears that what you're actually after is for the compiler to be smarter about catching errors which lead to undefined behaviour, which is fine. In this case, you want a warning/error on casting from a const type to a non-const type. But fact is, the DMD frontend isn't very good at such things, and probably won't improve significantly any time soon. -- E-mail address: matti.niemenmaa+news, domain is iki (DOT) fi
Sep 09 2007
parent reply James Dennett <jdennett acm.org> writes:
Matti Niemenmaa wrote:
 Janice Caron wrote:
 Actually, forget the specific example. What I'm getting at is that,
 right now, it's possible to cast away const /accidentally/.

 My change would ensure that that was no longer the case.

 And you miss one of the major points about C++'s const_cast<>. Yes, we
 all agree that it's a BAD THING to do. But the point about C++'s
 const_cast<> is not that it will make constness go away, it's that
 static_cast<> /won't/.

The major point about C++'s const_cast<> is that it is defined behaviour.

Only if the underlying object was not declared const in the first place. (This is also a point that Walter generally elides when he critizes C++ const as not being useful for optimization, and he coins the term "storage class" to apply to those uses of const in C++ which *do* permit optimization, though const is not a storage class in C++ terminology.) If casting away const in D always involves undefined behavior (and not only when the resulting object is modified) then it should certainly be detected and disallowed at compile time. -- James
Sep 09 2007
parent reply James Dennett <jdennett acm.org> writes:
Janice Caron wrote:
 If casting away const in D always involves undefined behavior
 (and not only when the resulting object is modified) then it
 should certainly be detected and disallowed at compile time.

Of course. However, according to Walter, I quote: "Because a static type system can be a straitjacket, there needs to be a way to circumvent it for special cases." and... "The ability to cast away invariant-correctness is necessary in some cases where the static typing is incorrect and not fixable, such as when referencing code in a library one cannot change. Casting is, as always, a blunt and effective instrument, and when using it to cast away invariant-correctness, one must assume the responsibility to ensure the invariantness of the data, as the compiler will no longer be able to statically do so." Both of those quotes indicate that D requires a way to do the unthinkable. Therefore, as stated above, and (somewhat better) on another thread, I argue that the solution is to disallow it - except with some special, unambiguous syntax, which makes plain you are deliberately breaking the rules. Preferably something greppable. See other thread.

It may well be necessary; if it is, the behaviour must be defined so that it _works_. -- James
Sep 09 2007
next sibling parent Lutger <lutger.blijdestijn gmail.com> writes:
James Dennett wrote:
...
 It may well be necessary; if it is, the behaviour must be defined
 so that it _works_.
 
 -- James

I thought that is the case? Maybe it should be stated (more?) explicitly though.
Sep 09 2007
prev sibling next sibling parent Lutger <lutger.blijdestijn gmail.com> writes:
Janice Caron wrote:
 It may well be necessary; if it is, the behaviour must be defined
 so that it _works_.

No, it's undefined for a reason. It should stay undefined.

Just to be clear: I assume the behavior of casting away const and then reading the ex-const variable is defined, otherwise it doesn't make much sense. The only thing undefined is writing a const casted variable.
Sep 09 2007
prev sibling next sibling parent reply James Dennett <jdennett acm.org> writes:
Janice Caron wrote:
 It may well be necessary; if it is, the behaviour must be defined
 so that it _works_.

No, it's undefined for a reason. It should stay undefined.

You may be missing my point. I'll try to explain more clearly below. The key is *what* is defined/undefined.
 Circumvention is necessary so that you can call library functions
 which are incorrectly declared. For example, if a library function is
 declared as
 
 int strlen(char[] s); /* erroneous declaration - function does not
 modify the bytes of s */
 
 then circumventing the normal casting rules would be harmless, because
 no undefined behaviour is being invoked. The key point here is that
 you would have to /know/, with absolute certainty, that strlen() was
 not going to modify the bytes of your string. If you made the wrong
 call ... well, then that's when the undefined behaviour would kick in.

So the cast was *not* undefined at all -- only the act of modifying after that is undefined. That's fine.
 No, Walter is absolutely right to make it undefined. And also
 absolutely right to allow circumvention.

You can't do both. The behavior of the cast must be well defined, otherwise it does not allow circumvention. The result of modification likely should be undefined, but that is a different matter.
 My only argument is that circumvention should require a different syntax.

I'd agree with that; it's something C++ has almost right (though backwards with C means that you can const_cast without being explicit). D's single cast syntax is rather a step backwards from C++. -- James
Sep 09 2007
next sibling parent Daniel Keep <daniel.keep.lists gmail.com> writes:
Janice Caron wrote:
 D's single cast syntax is rather a step backwards from C++.

D's single cast syntax dates from a time before there was const. If there's no const, you don't need a const_cast. Only with the advent of const in 2.0 do we start to encounter problems.

Actually, there's one other deficiency in only having a single cast that trips up newbies from time to time: Why does cast(int) 42.31 == 42, but cast(int[]) [42.31] fail? Because one is doing a *cast* and the other is doing a *conversion.* Once you involve things like opCast and opImplicitCast, it gets even harder to tell which it's doing. Of course, I'm *dying* to get my hands on opImplicitCast, so I'll just be quiet now... :3 -- Daniel
Sep 09 2007
prev sibling parent James Dennett <jdennett acm.org> writes:
Janice Caron wrote:
 D's single cast syntax is rather a step backwards from C++.

D's single cast syntax dates from a time before there was const. If there's no const, you don't need a const_cast. Only with the advent of const in 2.0 do we start to encounter problems.

There are other problems with a single cast notation; constness is not the only one. Casts on numeric types fall into various categories; pure widening casts are entirely safe, narrowing casts between integrals types can be safe except for truncation, casts from some integral types to suitable floating point types are value preserving, casts from floating point values to integral types tend to lose information and risk range errors. Casting up a class hierarchy (in the presence of single inheritance only) is safe; downcasts can fail. Converting between pointers and integral types can often be problematic. Even C++'s classification of different types of cast is not as granular as I'd like, and I've found an implicit_cast<T> to be useful while a lossless_cast<T> has theoretical appeal. (The absence of MI from D means that there is less need for something akin to a full-blown dynamic_cast: there's no cross-casting in a single-inheritance chain.) -- James
Sep 09 2007
prev sibling parent Jeff Nowakowski <jeff dilacero.org> writes:
Janice Caron wrote:
 Circumvention is necessary so that you can call library functions
 which are incorrectly declared. For example, if a library function is
 declared as
 
 int strlen(char[] s); /* erroneous declaration - function does not
 modify the bytes of s */
 
 then circumventing the normal casting rules would be harmless, because
 no undefined behaviour is being invoked. The key point here is that
 you would have to /know/, with absolute certainty, that strlen() was
 not going to modify the bytes of your string. If you made the wrong
 call ... well, then that's when the undefined behaviour would kick in.

And then what happens when you use a new version of the library, where your assumption is no longer valid? Will the vast majority of libraries be diligent in specifying const? Is const going to clutter the code everywhere? What's the *good* reason that const isn't the default, at least for function parameters? -Jeff
Sep 10 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
You have misunderstood my suggestion. Please stop implying that I have
not read Walter's documentation. It is a little insulting.

Suppose there exists a function with a prototype something like

void makeUppercase(ubyte[] s);

It's declared as ubyte[], not char[], correctly, because it's intended
for some encoding other than UTF-8. It does its conversion in place.

Now suppose that a user of that library at first writes this:

string s;
makeUppercase(s);

The compiler will correctly complain that you can't cast from string
to ubyte[]. So now, to fix that, suppose that the writer of that code
changes it to:

string s;
makeUppercase(cast(ubyte[])s);

Currently, that will compile.

What *I am suggesting* is to make it /not/ compile. This makes things
/safer/, not less safe. Do you see now?

By insisting that the /only/ way to remove const be with an explicit
cast(!const), the world becomes a safer place. (Well - maybe that last
claim was too bold! :-) )
Sep 09 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
Actually, forget the specific example. What I'm getting at is that,
right now, it's possible to cast away const /accidentally/.

My change would ensure that that was no longer the case.

And you miss one of the major points about C++'s const_cast<>. Yes, we
all agree that it's a BAD THING to do. But the point about C++'s
const_cast<> is not that it will make constness go away, it's that
static_cast<> /won't/.
Sep 09 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
Oh sorry - I started another thread before I read this.

Yes, I want an error on casting from a const type to a non-const type.
I guess we should use the new thread now that I've started it, coz
it's more straight-to-the-point.
Sep 09 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
 If casting away const in D always involves undefined behavior
 (and not only when the resulting object is modified) then it
 should certainly be detected and disallowed at compile time.

Of course. However, according to Walter, I quote: "Because a static type system can be a straitjacket, there needs to be a way to circumvent it for special cases." and... "The ability to cast away invariant-correctness is necessary in some cases where the static typing is incorrect and not fixable, such as when referencing code in a library one cannot change. Casting is, as always, a blunt and effective instrument, and when using it to cast away invariant-correctness, one must assume the responsibility to ensure the invariantness of the data, as the compiler will no longer be able to statically do so." Both of those quotes indicate that D requires a way to do the unthinkable. Therefore, as stated above, and (somewhat better) on another thread, I argue that the solution is to disallow it - except with some special, unambiguous syntax, which makes plain you are deliberately breaking the rules. Preferably something greppable. See other thread.
Sep 09 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
 It may well be necessary; if it is, the behaviour must be defined
 so that it _works_.

No, it's undefined for a reason. It should stay undefined. Circumvention is necessary so that you can call library functions which are incorrectly declared. For example, if a library function is declared as int strlen(char[] s); /* erroneous declaration - function does not modify the bytes of s */ then circumventing the normal casting rules would be harmless, because no undefined behaviour is being invoked. The key point here is that you would have to /know/, with absolute certainty, that strlen() was not going to modify the bytes of your string. If you made the wrong call ... well, then that's when the undefined behaviour would kick in. No, Walter is absolutely right to make it undefined. And also absolutely right to allow circumvention. My only argument is that circumvention should require a different syntax.
Sep 09 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
 Just to be clear: I assume the behavior of casting away const and then
 reading the ex-const variable is defined, otherwise it doesn't make much
 sense. The only thing undefined is writing a const casted variable.

Yes. It's undefined for all sort of reasons ... either because it might not be /possible/ to write it (because it might be in ROM); or because changing might violate thread-safety; etc.. But you can always read it, whether it's const or not.
Sep 09 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
 D's single cast syntax is rather a step backwards from C++.

D's single cast syntax dates from a time before there was const. If there's no const, you don't need a const_cast. Only with the advent of const in 2.0 do we start to encounter problems.
Sep 09 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

From the sneaky tricks department...

You know, is it actually possible to circumvent const/invariant correctness
*without using a cast at all*. Of course, as with cirucumvention by any
other means, this will lead to undefined behaviour, but, just for a laugh...


string s = "cat"; /* invariant */

/* now let's modify the string, without using cast */
char * p;
p += &s[0] - null;

/* drum roll... */

*p = 'b';
writefln(s); /* writes "bat" on Windows */



Never, ever do this! :-)
Sep 10 2007
prev sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 9/10/07, Jeff Nowakowski <jeff dilacero.org> wrote:

 And then what happens when you use a new version of the library, where
 your assumption is no longer valid?

Now you're just trying to cause trouble. :-) A new version of strlen that modifies your string? Come on! If /that/ assumption is no longer valid then you are the victim of a malicious attack. You can't blame D for that, and nor can you protect against it. A malicious attacker could declare the function as strlen(const(char)[]) and /still/ modify the string.
 What's the *good* reason that const isn't the default, at least for
 function parameters?

Passing classes around, for one.
Sep 10 2007