Const sucks

Daniel Keep daniel.keep.lists at gmail.com
Tue Sep 11 01:44:13 PDT 2007



Walter Bright wrote:
> Const, final, invariant, head const, tail const, it's grown into a
> monster. It tries to cover all the bases, but in doing so is simply not
> understandable.

Does that mean I'm a genius, or insane? :P

> [snip]
> 
> o  no more final

Not 100% sure what you mean by "no more".  But then, "final" was always
kind of like the appendix of the new const system; I always viewed it as
something of a side issue since it didn't affect the type of declarations.

> o  const and invariant now mean "fully const" and "fully invariant",
> where fully means "head and tail combined":
> 
> const int x = 0;   // x is constant
> const int* p = &x;  // neither p nor *p can be changed
> const(int*) p = &x;  // neither p nor *p can be changed
> const(int)* p = &x;  // p can change, *p cannot be changed

Ok.

> o  const and invariant are transitive. There is no way to specify a
> (pointer to)(const pointer to)(mutable int).
> 
> o  tail invariant for an array or pointer type can be done using the
> existing syntax:
> 
>  invariant(char)[] s;   // string, i.e. an array of invariant chars
>  const(T)* p;  // pointer to tail const T

This makes me happy, because although I understood the what, I never
could grasp the why of "invariant(char)[]" meaning that you couldn't
change the elements of the array.

> o  tail const of a struct would have to be done by making the struct a
> template:
> 
>   struct S(T) { T member; }
>   S!(int)   // tail mutable
>   S!(const(int)) // tail const

This is... not so good.  I'll come back to this...

> o  one can construct a template to generically produce tail const or
> tail invariant versions of a type.

This makes me rather nervous; it seems like a bad thing that we can no
longer do tail-const, and have to resort to wrapping templates to do it.

I mean, in the end, you can make a const version of any type by
replacing all fields with read-only properties; but then you can't
cleanly cast between them.

> o  it will be illegal to attempt to change the key value of a foreach
> loop, but the compiler will not be able to diagnose all attempts to do so

I assume this means that for built-in types, instead of being

  ( ref key, ref value ; aggregate )

We'll have

  ( const ref key, ref value ; aggregate )

If so; I'll be happy about that.  So long as I can do evil cr*p like:

  ( ref idx, ref chr ; string.foo ) ++idx;

Why, you ask?  Er, you don't wanna know... :P

> o  static const/invariant means the initializer is evaluated at compile
> time. non-static const/invariant means it is evaluated at run time.

Ok.

> o  no initializer means it is assigned in the corresponding constructor.

Sounds good.

> o  const/invariant declarations will always allocate memory (so their
> addresses can be taken)

I guess this is good from a consistency standpoint.

> o  So, we still need a method to declare a constant that will not
> consume memory. We'll co-opt the future macro syntax for that:
> 
>     macro x = 3;
>     macro s = "hello";

The syntax doesn't *really* speak to me; honestly, I was expecting you
to use "alias" before I scrolled down and saw "macro".  Thing is, if
this works for arbitrary expressions, does that mean this will be possible:

    macro string = char[];

My main problem with this is the loss of tail const on structs/classes.
 Others have already listed several useful things that you won't be able
to do because of this, so I won't bother with that.

This problem seems to be unique to structs and classes.  This is because
every other reference type is written in such a way that you can "split"
the type in such a way as to get tail-const semantics.  For example:
const(char[]) versus const(char)[].  This can't be done with structs or
classes since their types are single identifiers.

So what we need is some way to write that split.  Really, what we're
saying is "I don't want *this* to be const, but I do want what it
references to be const".

Back when I was writing my article on the upcoming const changes, I gave
each kind of const a different, longer name to help differentiate them;
the names I gave const and invariant were "reference const" and
"reference immutable".

So, stealing a little bit of C++ syntax, what about "const& T" being a
tail-const version of some struct or class T?  Since this could
potentially introduce synonyms in other places, I would also propose the
following:

  const& int a; // Error: cannot use reference const on an atomic type
  const& int* b; // Error: equivalent to const(int)*
  struct V { int a; }
  const& V c; // Warning: V does not have any references
  struct R { int* a; }
  const& R d; // OK
  class C {}
  const& C e; // OK

Yes, this means there is a distinction between structs, classes and
"everything else".  But honestly, I don't think we can have tail const
without this distinction.  Maybe disallowing const& on arrays and
pointers isn't a good idea; but I do think we need some kind of
tail-const for structs and classes.

Thoughts?

	-- Daniel



More information about the Digitalmars-d mailing list