casting away const and then mutating
Jonathan M Davis via Digitalmars-d
digitalmars-d at puremagic.com
Fri Jul 24 12:35:40 PDT 2015
On Friday, 24 July 2015 at 14:07:46 UTC, Steven Schveighoffer
wrote:
> On 7/23/15 11:58 PM, Jonathan M Davis wrote:
>> On Friday, 24 July 2015 at 03:02:30 UTC, Steven Schveighoffer
>> wrote:
>>> Basically, if we say this is undefined behavior, then inout is
>>> undefined behavior.
>>
>> inout is done by the compiler. It knows that it's safe to cast
>> the
>> return type to mutable (or immutable), because it knows that
>> the return
>> value was either the argument that it passed in or something
>> constructed
>> within the function and thus safe to cast. The compiler knows
>> what's
>> going on, so it can ensure that it doesn't violate the type
>> system and
>> is well-defined.
>
> The compiler knows everything that is going on inside a
> function. It can see the cast and knows that it should execute
> it, and also that the original variable is mutable and could be
> the one being mutated. This isn't any different.
You're assuming that no separate compilation is going on, which
in the case of a template like you get with RedBlackTree, is
true, because the source has to be there, but in the general
case, separate compilation could make it so that the compiler
can't see what's going on inside the function. It relies on the
separate compilation step having verified the inout attribute
appropriately when the function's body was compiled, and all it
has to go on is the inout in the signature. If you were to try
and implement inout yourself in non-templated code, the compiler
wouldn't necessarily be able to see any of what's going on inside
the function when it compiles the calling code. Without inout, in
non-templated code, even if the compiler were being very smart
about this, it wouldn't have a clue that when you cast away const
on the return value that it was the same one that was passed in.
>>> An example of what the compiler can "start doing more than it
>>> does
>>> now" would be helpful. I can't see how it can do anything
>>> based on this.
>>
>> Well, it could remove dead code. For instance, if you had
>>
>> const(Foo) bar(T t)
>> {
>> const myFoo = getFoo(t);
>> auto value1 = pureFunc(myFoo);
>> auto value2 = pureFunc2(myFoo);
>> auto value3 = pureFunc3(value1, value2);
>> return myFoo;
>> }
>>
>> All of the lines with pureFunc* could be removed outright,
>> because
>> they're all pure function calls, and they can't possibly have
>> mutated
>> myFoo. I wouldn't expect a lot of dead code like that, and
>> maybe
>> something like that would never be implemented in the
>> compiler, but it
>> could be as long as the compiler can actually rely on const
>> not being
>> mutated.
>
> And my interpretation of the spec doesn't change this. You can
> still elide those calls as none of them should be casting away
> const and mutating internally.
You seem to be arguing that as long as you know that a const
reference refers to mutable data, it is defined behavior to cast
away const and mutate it. And if that were true, then if you knew
that myFoo referred to mutable data, it would be valid to cast
away const and mutate it inside of one of the pureFunc*
functions, because you know that it's mutable and not immutable.
And this shows why that isn't enough. And that is the major
objection I have with what you're arguing here. In the general
case, even if immutable is not used in the program even once,
casting away const and mutating is not and cannot be defined
behavior, or const guarantees nothing - just like in C++.
The exact use case that you're looking for - essentially inout -
works only because when you cast it back, no const reference that
was generated by calling the function with a mutable reference
escaped that function except via the return value, so there's no
way for the compiler to optimize based on the const reference,
because there isn't one anymore. And that's _way_ more restricted
than saying that it's defined behavior to cast away const and
mutate as long as you know that the underlying data is actually
mutable.
So, I don't object to the behavior you're arguing for being
well-defined. It's that you're arguing that the fact that you
know that it's mutable underneath is enough to make it valid to
cast away const and mutate. And that cannot be the case,
regardless of what the spec does or doesn't say, or we have C++'s
const - except that it's transitive. And Walter has made it
abundantly clear that his intention is that D's const be physical
const and provide actual guarantees. For that to be the case,
casting away and mutating const cannot be well-defined except in
very specific cases where we can guarantee that we're not
violating the guarantee that const objects are not mutated via a
const reference.
Your case works, because the const reference is gone after the
cast, and there are no others that were created from the point
that it temporarily became const. So, it's a very special case.
And maybe the rule can be worded in a way that incorporates that
nicely, whereas simply saying that it's undefined behavior to
cast away const and mutate would not allow it. But we cannot say
that it's defined behavior to cast away const and mutate simply
because you know that the data is mutable, or we do not have
physical const, and const provides no real guarantees.
It sounds to me like we need to come up with a way to word the
rule that allows for what you're trying to do while not allowing
the mutating of const in general (even if the data is actually
mutable) and get it approved by Walter and in the spec. Walter
has made it clear on several occasions that const is supposed to
be physical const with real guarantees, and if that is not what
the spec says (or if it does say that but not clearly), then it
needs to be updated. And I do agree that the case that you have
here should be well-defined, but it could be tricky to come up
with a way to word the rule that doesn't require a lot of
ancillary explanation about what exactly it means. The spec needs
to be clear, not wish-washy.
In either case, I think that it's clear that we need Walter to
say something on the matter.
> These kinds of "maybe someone someday can think of something"
> arguments are quite unconvincing.
The point is that if you're relying on undefined behavior to do
whatever you're doing, what the compiler is doing could change
later. Depending on how the compiler works now or on what
improvements will or won't be able to be made later means risking
writing code that will not work later. So, using undefined
behavior and relying on the compiler's current behavior is just
asking for trouble. I don't think that I should need to come up
with a convincing argument about "someone someday can think of
something," because that's the whole point of undefined behavior.
You can't rely on it, because it's not defined.
And in the case of const, the compiler is supposed to be able to
rely on data not being mutated via a const reference (that _is_
in the spec), so in almost all cases, casting away const and
mutating cannot possibly be defined, because it would make it
impossible for the compiler to rely on data not being mutated via
a const reference.
In any case, clearly, I need to figure out how to improve the
spec's explanation of const so that the situation is clear and
get that approved by Walter. The simple fact that we're arguing
over this shows that the spec isn't clear enough.
- Jonathan M Davis
More information about the Digitalmars-d
mailing list