const(FAQ)

Kevin Bealer kevinbealer at gmail.com
Mon Mar 31 00:58:21 PDT 2008


guslay Wrote:

> Kevin Bealer Wrote:
> 
> > guslay Wrote
> > ... logical const ...
> 
> I still don't see how dividing a const composite object (ie a class) in two
> separate parts (mutable and immutable) would prevent any optimization
> or thwart future development.
>
> I can see the optimization benefit of an eventual pure qualifier. With no
> possible side effects, it would allow crazy functional programming tricks
> (reordering, parallel dispatch, memoizing).
> 
> But const (or invariant) doesn't mean pure. The following does compile, and
> is bitwise const. It's legal and it's full of side effects.

But only because it reaches outside the object.  For safety / correctness, I could
argue that if you want the "dim" data to be const, you need to declare it as such,
or wrap it in an object and declare a const one of those, but I guess that's beside
the point.

You may be right about optimization and purity.

> static int dim = 0;
> class Vector
> {
>      public const int size()
>      {
>          return ++.dim;
>      }
> 
>      public const void printElement( int i )
>      {
>          // print element i to stdout
>      }
> };
> 
> As opposed to pure function, the only "mathematical" guarantee that the
> const qualifiers gives on a method is that its immutable bits will not be
> modified. Nothing else. So what is the problem if I say that only half the
> bits are immutable?

(You can, technically, by making half of the fields const individually.)

The question is, what does "const" mean -- if it means that all the bits are
immutable, you get a certain benefit.  If it means that some of them are,
you get a different benefit.  I think the idea is that the "all bits" guarantee
is more useful for the purpose of reasoning about whether it is safe to call
a method from inside / outside a locked / synchronized() section.

A normal .size() method will operate on the fields of the object in question.
If it is const, it does not need to modify those fields.  This means that you
can safely call such methods in parallel from multiple threads on the same
object.  Is this an absolute guarantee?  No -- side effects as shown above
break this assumption.  But only the external ".dim" field can be corrupted,
and it should have its own object and/or locking if that is an issue.

But calling multiple non-const methods is not safe unless proper locking is
performed.

Now here is the critical distinction:  this applies to the D concept of const,
but not the C++ concept since mutable fields tend to reintroduce the need
for locking even in the presence of C++ "logical const" correctness.

The caller may need to get locks when calling the callee's methods (if the
callee does not do its own locking), but only for non-const objects.  If the
callee does its own locking then another problems arises -- the caller must
be careful of deadlock.  So you are hemmed in from both directions -- it
is dangerous to do too much locking (due to deadlock) or too little (due
to race conditions), which is why a compiler-enforced guarantee is useful.

In either case, knowing the object is const will be more useful if it is an
enforced transitive const rather than a logical const, since the former
implies something about the need for locking while the latter does not.

(Of course, this assumes again that const methods are pure, i.e. don't
reach outside the object to change things in important ways.  It does
require that much cooperation at any rate, although I guess I'm the
one to suggest doing otherwise...)

> Const/invariant on data enables aggressive optimizations; const/invariant
> on a method does not.

Probably true.
 
> Or am I missing something?

I think in C++ you have a kind of contract with the std library, other developers, etc,
to not change the observable value of a const object so that it is different after the
call than before it.  In D you have a similar contract not to change the bitwise value
of the object.

In C++ this contract is non-enforceable -- if you make all the fields mutable, you
have essentially discarded the const qualifier.  In D you have a different contract,
to not modify the actual bits of an object.  This contract is enforceable and solid
because the compiler can truly enforce it.

The code we've been discussing violates the (C++) contract in any case because
it changes the conceptual value of the object from a const method.  The compiler
can't enforce this because the ++.dim is indistinguishable from the ++ inside a
buffered stream when you output a character, or any other external effects.  It
doesn't violate the D "const contract", because the D contract talks about the bit
state of this object (and no other) rather than the observable value.

D has a const contract that the compiler really can enforce, and do so completely,
whereas C++ has a const contract that the compiler can't enforce.  It can make
sure you fill out the proper paperwork when you ignore the contract, but it doesn't
stop you from ignoring it.

Kevin




More information about the Digitalmars-d mailing list