Const correctness revisited (proposal)

Oliver Dathe o.dathe at gmx.de
Mon Mar 24 17:15:22 PDT 2008


Walter Bright wrote:
> That would be of very limited utility because I could not pass a 
> const(T)[] array to the function. It's weird to have a function that 
> doesn't modify its parameters where one could not pass const data to.

Ok I'll try something...

Definition:
If f has a 'tail const paramater' t of type T, then f must be compilable 
if it also is compilable for typeof(t)=const(T) on data operations. 
Otherwise it is an error.

   T[] f(T[] t const) {
     T sum;
     foreach (i, ti; t) { sum+=ti; }     // ok
     auto h = t.toHash();                // ok, invariant data operation
     static assert (is(typeof(t)==T[])); // ok, no data operation
     // t[2] = ...;                      // error
     // t.length = 17;                   // error
     return t;
   }
   ...
   char[]        a, y;
   const(char)[] b;
   const(char[]) c;
   ...
   // all following three calls will succeed
   // note: for all three returntype==typeof(y)==char[] holds
   y=f(a);
   y=f(b);
   y=f(c);

You can see: If f is compilable, then the call to f succeeds if 
T_param_at_call is implicitly convertible to const(T_param_at_func).

So a call to a function char[] foo(char[] x const) {...} with a 
const(char)[] parameter also succeeds.

Would this also imply the call is sound? Btw: How or where is sound in 
this context defined?


In general: That we define types with const properties where it makes 
sense is really fine, no doubt! However, the problem is if we want to 
pass an immutable view of usual mutable data to a function, the real 
"solution" is apparently not to just use some const(char)[] as input 
type because
a.) the Ts behind input usually are not really const but we just want an 
immutable view on them. By using const(char)[]
b.) we are forced to use an evil const-away cast when we want to mutate 
the result afterwards. To some degree that is contradictory and 
counterproductive (cast-away-const==cast-away-sound right?).

I simply aim to be able to prove:

   STATE before = STATE(x);          // x + anything reachable through x
   y=foo(x);
   static assert (STATE(x)==before); // must be provable at compile time

This currently can be ensured by x beeing const. That is the way D2 
currently forces. But this constness must not necessarily be a natural 
attribute of the data! In most scenarios we may simply want to ensure 
foo() is not mutating x without artificially constifying the data. I 
think that is the heart of the ever upcoming 'const debacle'.


Also imagine: You have a lot of D1 code with 'in T[]' parameters but 
you'd like to use them with a lot of const(T)[] variables where it makes 
sense. Note: This is the same scenario as you mentioned in your post 
before. Currently that will fail for the same reason. Now, let us try to 
let calls to D1 functions succeed that do not explicitly declare const 
parameters or tail const parameters. In order use them with our const(T) 
  input variable we could either
a.) call foo(const input) as proposed in my original post (enforce 
immutability of the function regarding the parameter) or
b.) since input is const anyways, parameter tail constancy may 
automatically be forced.
If we do so, we would also have a huge gain of D1 code that is usable in 
a D2 const styled world! Not at least the optimizer will appreciate this 
but also having tail const parameters in declarations will be of 
valueble information to the user/reader of the code/docs.


Another Question: The transitive behaviour of const will also hold for 
the planned D tail const methods? This would have great impact on a D2 
version of the following dirty C++ code:

   #include<cassert>
   class A {
     char* p;
     public:
     A() : p(new char[50]) {}
     ~A() { delete[] p; }
     // within tail const methods typeof(this) is (A const *)
     // but typeof(this->ptr) remains char* in C++, (nontransitive)
     char* getp() const { return p; }
     void mutate() const { p[5]=42; }
   };
   int main() {
     A a;
     char* ptr = a.getp();
     ptr[5] = 17;
     a.mutate();
     assert(ptr[5]==42);
     return 0;
   }

So in D2 within tail const methods typeof(this.p)==const(char*) and 
typeof(this.p[0])==const(char) holds? Did I get this right? That'd be 
consistent with the rest and pretty cool I think.



More information about the Digitalmars-d mailing list