www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Proposal - Revised Syntax for const and final

reply "Janice Caron" <caron800 googlemail.com> writes:
I've had a little think about the whole const syntax, and I've come up
with the following proposal. I don't know how well it will be
received, because it's different from what D2.0 is now, and so will
break 2.0 code if implemented. On the other hand, I don't think that's
a bad thing, because I think the benefits outweight that.

So, there is a rationale behind these choices. The philosophy is that
the same syntax should apply to all types, regardless of whether they
are value types or reference types. I also believe in the principle of
least surprise. That means, even though there are lots of different
things that need to be achieved, there should be a single, consistent
approach which applies to all of them. Where syntax needs to be
extended, it should be done in a way which is analogous to some
existing D syntax.

My proposal involves ditching altogether the const "storage class",
and a redefinition of the usages of const and final.

Specifically: (1) final would now use the bracket syntax, so that
final-ness can be applied to the nth level of indirection, not just
the top level, (2) const now implies total constness, and (3) the
symbol * placed immediately after const implies that the constness
applies one level of indirection down. This is best shown by example.


/* Definitions */

class C
{
    this() {p.length=1}
    int x;
    int p[];
}

struct S
{
    static opCall() { S s; s.p.length=1; return s; }
    int x;
    int p[];
}



Without const, everything works as normal

C c;
c = new C();           /* OK */
c.x = 1;               /* OK* /
c.p = new int[1];      /* OK */
c.p[0] = 1;            /* OK */

S s;
s = S();               /* OK */
s.x = 1;               /* OK* /
s.p = new int[1];      /* OK */
s.p[0] = 1;            /* OK */

cdouble z;
z = 1;                 /* OK */
z.re = 1;              /* OK */
z.im = 1;              /* OK */

int[][] a;
a.length = 1;           /* OK */
a[0].length = 1;        /* OK */
a[0][0] = 1;            /* OK */



TOTAL constness --
In general, if a variable x is declared to be of type const(T)
then x is const, and x.member is const (and x.member.member, etc.)

const(C) c;
c = new C();            /* Error */
c.x = 1;                /* Error */
c.p = new int[1];       /* Error */
c.p[0] = 1;             /* Error */

const(S) s;
s = S();                /* Error */
s.x = 1;                /* Error */
s.p = new int[1];       /* Error */
s.p[0] = 1;             /* Error */

const(cdouble) z;
z = 1;                   /* Error */
z.re = 1;                /* Error */
z.im = 1;                /* Error */

const(int[][]) a;        /* a is const array of const array of const int */
a.length = 1;            /* Error */
a[0].length = 1;         /* Error */
a[0][0] = 1;             /* Error */

const(int[])[] a;        /* a is array of const array of const int */
a.length = 1;            /* OK */
a[0].length = 1;         /* Error */
a[0][0] = 1;             /* Error */

const(int)[][] a;        /* a is array of array of const int */
a.length = 1;            /* OK */
a[0].length = 1;         /* OK */
a[0][0] = 1;             /* Error */



HEAD constness --
In general, if a variable x is declared to be of type final(T)
then x is const, but x.member is mutable (and x.member.member, etc.)

final(C) c;
c = new C();            /* Error */
c.x = 1;                /* OK* /
c.p = new int[1];       /* OK */
c.p[0] = 1;             /* OK */

final(S) s;
s = S();                /* Error */
s.x = 1;                /* OK* /
s.p = new int[1];       /* OK */
s.p[0] = 1;             /* OK */


final(cdouble) z;
z = 1;                  /* Error */
z.re = 1;               /* OK */
z.im = 1;               /* OK */

final(int[][]) a;       /* a is const array of array of int */
a.length = 1;           /* Error */
a[0].length = 1;        /* OK */
a[0][0] = 1;            /* OK */

final(int[])[] a;       /* a is array of const array of int */
a.length = 1;           /* OK */
a[0].length = 1;        /* Error */
a[0][0] = 1;            /* OK */

final(int)[][] a;       /* a is array of array of const int */
a.length = 1;           /* OK */
a[0].length = 1;        /* OK */
a[0][0] = 1;            /* Error */



TAIL constness --
In general, if a variable x is declared to be of type const*(T)
then x is mutable, but x.member is const (and x.member.member, etc.)

const*(C) c;
c = new C();             /* OK* /
c.x = 1;                 /* Error */
c.p = new int[1];        /* Error */
c.p[0] = 1;              /* Error */

const*(S) s;
s = S();                 /* OK* /
s.x = 1;                 /* Error */
s.p = new int[1];        /* Error */
s.p[0] = 1;              /* Error */

const*(cdouble) z;
z = 1;                  /* OK* /
z.re = 1;               /* Error */
z.im = 1;               /* Error */

const*(int[][]) a;      /* a is array of const array of const int */
a.length = 1;           /* OK */
a[0].length = 1;        /* Error */
a[0][0] = 1;            /* Error */

const*(int[])[] a;      /* a is array of array of const int */
a.length = 1;           /* OK */
a[0].length = 1;        /* OK */
a[0][0] = 1;            /* Error */

const*(int)[][] a;      /* a is array of array of int */
a.length = 1;           /* OK */
a[0].length = 1;        /* OK */
a[0][0] = 1;            /* OK */

Of course, const*(int) is meaningless, since int has no
assignable properties. That's why everything is allowed in the
last example.

But there's one thing I've missed out...

Walter wants to make it possible to to specify that the immediate
members of a struct, but not the struct itself, be const*.
You can't do that in C++, and it's the reason C++ has two different
types, iterator and const_iterator (not const iterator).

This omission seems to be the reason for the weird existing syntax
in D2.0. It's the reason reference types behave differently from
non-reference types. It's the reason that const(S) for structs does
what it does in D2.0. It seems to be very important to Walter that
that be allows. However, my notation extends naturally to cover this
circumstance. Thus:

const**(S) s;
s = S();                /* OK* /
s.x = 1;                /* OK */
s.p = new int[1];       /* OK */
s.p[0] = 1;             /* Error */


I considered whether or not the * notation should also be applicable
to final. I came to the conclusion, probably not. Though it is
logical, it is also pointless. Again, this is best shown by example:

final*(int[][]) a;      /* pointless, so lets disallow */
final(int[])[] a;       /* better way of achieving the same thing */
Sep 08 2007
next sibling parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
Having gone over your proposal a few times, I have to admit that I don't
like it much.  Sorry.

I just want to say, that this is intended to be constructive criticism,
not bashing. :)

Janice Caron wrote:
 I've had a little think about the whole const syntax, and I've come up
 with the following proposal. I don't know how well it will be
 received, because it's different from what D2.0 is now, and so will
 break 2.0 code if implemented. On the other hand, I don't think that's
 a bad thing, because I think the benefits outweight that.

Well, whether that's true is debatable, but D 2.0 is still very, *very* alpha; sudden, drastic changes to the syntax are perfectly fine, so long as they happen *now* rather than later. :)
 So, there is a rationale behind these choices. The philosophy is that
 the same syntax should apply to all types, regardless of whether they
 are value types or reference types. I also believe in the principle of
 least surprise. That means, even though there are lots of different
 things that need to be achieved, there should be a single, consistent
 approach which applies to all of them. Where syntax needs to be
 extended, it should be done in a way which is analogous to some
 existing D syntax.

I agree with your intentions here; having a simpler syntax would be great, and consistency[1] is always nice.
 [snip]
 
 TOTAL constness --
 In general, if a variable x is declared to be of type const(T)
 then x is const, and x.member is const (and x.member.member, etc.)

That's called "transitivity".
 [snip]

 HEAD constness --
 In general, if a variable x is declared to be of type final(T)
 then x is const, but x.member is mutable (and x.member.member, etc.)
 
 final(C) c;
 c = new C();            /* Error */
 c.x = 1;                /* OK* /
 c.p = new int[1];       /* OK */
 c.p[0] = 1;             /* OK */

That's fine.
 final(S) s;
 s = S();                /* Error */
 s.x = 1;                /* OK* /
 s.p = new int[1];       /* OK */
 s.p[0] = 1;             /* OK */

This is just silly. You're completely ignoring how structures work. auto y = &(s.x); // typeof(y) == int* *(cast(S*)y) = S(); I just bypassed the head constness of s. Yes, I casted to do it, but the point was that I *never* cast away the const-ness, because it was never there. Even then, I could do the same thing with foreach and s.tupleof, and you wouldn't even need a cast to do it. You *cannot* have a structure const and not have its members const; it doesn't make sense. You can either change it, or you can't.
 final(cdouble) z;
 z = 1;                  /* Error */
 z.re = 1;               /* OK */
 z.im = 1;               /* OK */

As above.
 final(int[][]) a;       /* a is const array of array of int */
 a.length = 1;           /* Error */
 a[0].length = 1;        /* OK */
 a[0][0] = 1;            /* OK */

Now you're contradicting yourself. You said that if something is final, you can't change *it*, but you *can* change it's members. An array is basically this: struct Array { void* ptr; size_t length; } Yet now you're saying that you can't change length. Assuming this is a typo, it still doesn't work. If you change length to be larger than the allocated capacity of the array, then D will reallocate it and will effectively do this[2]: a = new int[][](new_length); Again, you can't have something be both mutable and not mutable at the same time.
 [snip]
 
 TAIL constness --
 In general, if a variable x is declared to be of type const*(T)
 then x is mutable, but x.member is const (and x.member.member, etc.)
 
 const*(C) c;
 c = new C();             /* OK* /
 c.x = 1;                 /* Error */
 c.p = new int[1];        /* Error */
 c.p[0] = 1;              /* Error */

So this is basically the *current* behaviour of const.
 const*(S) s;
 s = S();                 /* OK* /
 s.x = 1;                 /* Error */
 s.p = new int[1];        /* Error */
 s.p[0] = 1;              /* Error */

s = S(1, [1].dup); I just bypassed the const*-ness.
 const*(cdouble) z;
 z = 1;                  /* OK* /
 z.re = 1;               /* Error */
 z.im = 1;               /* Error */
 
 const*(int[][]) a;      /* a is array of const array of const int */
 a.length = 1;           /* OK */
 a[0].length = 1;        /* Error */
 a[0][0] = 1;            /* Error */

Isn't this just const(int[])[]? It makes me uneasy that there's a whole set of synonyms for a particular const type.
 [snip]

Honestly, I appreciate why you're doing this. The less rules and mental exceptions you need to keep track of, the better. But D is a systems language; that means you need to understand what is actually going on. If we went and removed structs entirely from D, then this would actually be a pretty cool way of doing things. But since we do have POD types, I don't think it's going to work. Also, you've said nothing on invariant. I assume you mean to treat invariant in the same way as const? -- Daniel [1] That said, you have to be careful with consistency. You can easily take it to absurd extremes if you're not careful. [2] It also copies the old data across, but then the statement would have gotten ugly. :)
Sep 08 2007
next sibling parent reply Nathan Reed <nathaniel.reed gmail.com> writes:
Janice Caron wrote:
 final(T) would be a type that was similar to T except that it would be
 const, its members would be mutable, and all of its members' members
 would be mutable, etc..

I think this has to be modified to: final(T) would be a type similiar to T except that its value would be const, but anything accessed indirectly through its value would be mutable. This is the same problem already pointed out, that it doesn't make any sense to have the struct const but its members mutable. The struct *is* its members. With class references, you can have the reference be const but the members mutable (or vice versa), and you can do this for *pointers* to structs, but not for structs themselves. Hence, struct S { int a; int* p; } final(S) s; s = S(47, 0); // error, s is const s.a = 47; // error, s is const *s.p = 47; // ok, value pointed to by p is mutable Thanks, Nathan Reed
Sep 08 2007
next sibling parent reply Nathan Reed <nathaniel.reed gmail.com> writes:
Janice Caron wrote:
 All I know for sure is that the existing D2.0 scheme is too confusing.
 The fact that
 const(int**) s;
 const(int*)* s;
 both mean the same thing is highly counterintuitive. If we're stuck
 with the current scheme, I would prefer that the first version be
 declared a compile-time error (since s can in fact be assigned).

Agree. And for what it's worth, I agree with you about losing the storage-class version of const. I also agree with using some different syntax to denote tail-constness, since having const(S) for S a struct mean that S's members are mutable is unintuitive. Ultimately, though, I think constness is always going to work differently for value types than reference types. After all, the whole reason we have both kinds of types is that they are both useful, and have different semantics - hence we shouldn't try *too* hard to pretend they're the same thing. (A pointer to a struct should behave the same as a reference to an object though.) Thanks, Nathan Reed
Sep 08 2007
parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Nathan Reed wrote:
 Janice Caron wrote:
 All I know for sure is that the existing D2.0 scheme is too confusing.
 The fact that
 const(int**) s;
 const(int*)* s;
 both mean the same thing is highly counterintuitive. If we're stuck
 with the current scheme, I would prefer that the first version be
 declared a compile-time error (since s can in fact be assigned).

Agree. And for what it's worth, I agree with you about losing the storage-class version of const. I also agree with using some different syntax to denote tail-constness, since having const(S) for S a struct mean that S's members are mutable is unintuitive. Ultimately, though, I think constness is always going to work differently for value types than reference types. After all, the whole reason we have both kinds of types is that they are both useful, and have different semantics - hence we shouldn't try *too* hard to pretend they're the same thing. (A pointer to a struct should behave the same as a reference to an object though.)

I think there are some good points in what you're saying too. I mean I could pretty much *understand* your proposal, so that's a good thing right there. I do also particularly like sliding where the tail begins using *'s. It seemed kind of logical to me. The *'s let you specify how far down the tail starts relative to the part of the type inside parens. Easy to remember and reminiscent of the duality of P* x vs P *x in C: P* x -- "x is a pointer to P" vs P *x -- "a dereferenced x is a P" If you take a * off one side, you have to put it on the other to keep the same meaning. Similarly with your const(int*)**. If you move a * inside the parens, you have to balance it by putting one back after the const to maintain the same meaning. Or put another way const*(int**) means 'dereference once before applying the const to what's in parentheses' I'm not sure how Walter's syntax handles the same case but your way makes it very easy to create a const type that applies one level deeper than the original type by saying const*(T). Analogously, maybe const&(T) could be made to mean one level higher (== closer to head). --bb
Sep 08 2007
prev sibling parent reply Reiner Pope <some address.com> writes:
Janice Caron wrote:
 The fact that
 const(int**) s;
 const(int*)* s;
 both mean the same thing is highly counterintuitive.

I agree. However, I think this problem can be solved with much smaller changes. The syntax would be consistent if const(T) was a type constructor which gave a type like T, but with tail const. Consider the struct A, defined below: struct A { int x; } The types const(A) and A should behave identically, as there is no "tail" to be made constant. The following code is valid: const(A) a; a.x = 5; // nothing wrong here Now consider the array type constructor, [], which takes a type T and returns the array type T[]. For this to be a sensible type constructor, any operation permitted on T should be permitted on the elements of T[]. This is true of user-land type constructors (templated structs, etc) but not of pointers and arrays. Consider: struct MyArray(T) { T firstElem; } This is a valid type constructor, which takes a type T and returns an array of T (admittedly, the array isn't very interesting -- it only has one element -- but you can still call it an array). Now contrast the use: const(A)[] b; b[0].x = 5; // error, b[0].x is not mutable MyArray!(const(A)) c; c.firstElem.x = 5; // fine Now you might complain and point out that const(A)[] actually contains A behind a pointer, whereas MyArray!(const(A)) stores A directly. Well, we solve that problem by instead declaring MyArray as a class: class MyArray2(T) { T firstElem; } MyArray2!(const(A)) c2; c2.firstElem.x = 5; // still fine [Note that I'm showing the reference behaviour with classes and not with pointers because both pointers and arrays have this "taint" that I'm trying to show is inconsistent.] This is a very inconsistent behaviour. We used to have the property that any member accessible of an instance of T is also accessible of an element of T[]. This is no longer true, and it means that generic code may require extra special cases. The way to fix it is simply to remove the special cases, which exist for const(T)* and const(T)[] In both of these cases, the data pointed to should be tail const. Currently, it is head *and* tail const. -- Reiner
Sep 08 2007
parent Reiner Pope <some address.com> writes:
Reiner Pope wrote:
 Janice Caron wrote:
 The fact that
 const(int**) s;
 const(int*)* s;
 both mean the same thing is highly counterintuitive.

I agree. However, I think this problem can be solved with much smaller changes. The syntax would be consistent if const(T) was a type constructor which gave a type like T, but with tail const. Consider the struct A, defined below: struct A { int x; } The types const(A) and A should behave identically, as there is no "tail" to be made constant. The following code is valid: const(A) a; a.x = 5; // nothing wrong here Now consider the array type constructor, [], which takes a type T and returns the array type T[]. For this to be a sensible type constructor, any operation permitted on T should be permitted on the elements of T[]. This is true of user-land type constructors (templated structs, etc) but not of pointers and arrays. Consider: struct MyArray(T) { T firstElem; } This is a valid type constructor, which takes a type T and returns an array of T (admittedly, the array isn't very interesting -- it only has one element -- but you can still call it an array). Now contrast the use: const(A)[] b; b[0].x = 5; // error, b[0].x is not mutable MyArray!(const(A)) c; c.firstElem.x = 5; // fine Now you might complain and point out that const(A)[] actually contains A behind a pointer, whereas MyArray!(const(A)) stores A directly. Well, we solve that problem by instead declaring MyArray as a class: class MyArray2(T) { T firstElem; } MyArray2!(const(A)) c2; c2.firstElem.x = 5; // still fine [Note that I'm showing the reference behaviour with classes and not with pointers because both pointers and arrays have this "taint" that I'm trying to show is inconsistent.] This is a very inconsistent behaviour. We used to have the property that any member accessible of an instance of T is also accessible of an element of T[]. This is no longer true, and it means that generic code may require extra special cases. The way to fix it is simply to remove the special cases, which exist for const(T)* and const(T)[] In both of these cases, the data pointed to should be tail const. Currently, it is head *and* tail const. -- Reiner

I think I was a bit round-a-bout with that. Here's a simpler argument: for any type T, T* should mean "pointer to T". If you have struct A { int x; int* y; } then consider const(A)*. From the above definition, this should mean "pointer to const(A)", or in other words, "pointer to tail-const A". Yet experimentation clearly shows that const(A)* is not "pointer to tail-const A", but is actually "pointer to head- and tail-const A": const(A)* b; (*b).x = 5; // not allowed, but should be *(*b).y = 6; // shouldn't be allowed, and isn't -- Reiner
Sep 08 2007
prev sibling next sibling parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Janice Caron wrote:
 OK, I guess you're right about PODs. Let's try again, but more generally:
 
 Regardless of the type of T:
 
 final(T) would be a type that was similar to T except that it would be
 const, its members would be mutable, and all of its members' members
 would be mutable, etc..
 
 const(T) would be a type that was similar to T except that it would be
 const, its members would be const, and all of its members' members
 would be const, etc..
 
 const*(T) would be a type that was similar to T except that it would
 be mutable, its members would be const, and all of its members'
 members would be const, etc..
 
 const**(T)  would be a type that was similar to T except that it would
 be mutable, and its members would be mutable, but its members' members
 would be const, etc..
 
 Now, if some of those possibilities turn out to be nonsense (e.g
 final(int), then we can always make those possibilities compile-time
 errors.
 
 But it would be consistent, logical, and a boon to generic programming.
 No?

I think you need to be more specific when you say "members" because S and S* both have the same exact "members" syntactically. But I would expect const*(S) and const*(S*) to behave differently. So maybe you should be talking about levels of dereferencing instead? Also what happens with an alias like alias S* T; const*(S*) better behave the same as const*(T) or there'll be trouble. Replacing a type with an alias of the same type should never change behavior. But if T *does* continue to act like S*, then is that so different from what you have now with class references? They're called C but they act like C_* for some underlying (but inaccessible) base struct C_. --bb
Sep 08 2007
prev sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Janice Caron wrote:
 OK, I guess you're right about PODs. Let's try again, but more generally:
 
 Regardless of the type of T:
 
 final(T) would be a type that was similar to T except that it would be
 const, its members would be mutable, and all of its members' members
 would be mutable, etc..
 
 const(T) would be a type that was similar to T except that it would be
 const, its members would be const, and all of its members' members
 would be const, etc..
 
 const*(T) would be a type that was similar to T except that it would
 be mutable, its members would be const, and all of its members'
 members would be const, etc..
 
 const**(T)  would be a type that was similar to T except that it would
 be mutable, and its members would be mutable, but its members' members
 would be const, etc..
 
 Now, if some of those possibilities turn out to be nonsense (e.g
 final(int), then we can always make those possibilities compile-time
 errors.
 
 But it would be consistent, logical, and a boon to generic programming.
 No?

I also agree that current const/invariant semantics are overly strange and inconsistent. Dropping that idea that PODs and classes should work the same (with regards to const semantics), I agree your proposal would be more consistent and logical, but it still strikes me as too much complicated. Why the need for const**(T) ? You said: "Walter wants to make it possible to to specify that the immediate members of a struct, but not the struct itself, be const*." And it would work like this: " const**(S) s; s = S(); /* OK* / s.x = 1; /* OK */ s.p = new int[1]; /* OK */ s.p[0] = 1; /* Error */ " but now considering the struct the same as it's members, that would be what const*() does, . And would we also need const(T)?, given that const(T) is pretty much the same as: final const*(T). It would only be syntactic sugar. -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Sep 10 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 9/8/07, Daniel Keep <daniel.keep.lists gmail.com> wrote:
 final(S) s;
 s = S();                /* Error */
 s.x = 1;                /* OK* /
 s.p = new int[1];       /* OK */
 s.p[0] = 1;             /* OK */

This is just silly. You're completely ignoring how structures work.

Well, not ignoring as such, just ensuring that the same syntax works for both classes and structures - much as we have s.member for both structs and classes (and none of this -> malarchy as in C). Whichever way you go, something's going to be "silly" from a certain perspective.
 You *cannot* have a structure const and not have its members const; it
 doesn't make sense.

Well yeah, but bear in mind that that declaration used the (proposed) final keyword, not the const keyword, so it's not _intended_ to protect the members. The intent is just to allow structs to have the same syntax as classes, that's all. Basically, this possibility would be there just for completeness. I include it only because the compiler can't eliminate it on the basis of syntax alone. It could eliminate it at semantic analyis time, once it knows what S is, I guess - in which can the compiler just makes it an error.
 Now you're contradicting yourself.  You said that if something is final,
 you can't change *it*, but you *can* change it's members.

Fair enough. Call that a typo :) (Though it was really a mistake).
 TAIL constness --
 In general, if a variable x is declared to be of type const*(T)
 then x is mutable, but x.member is const (and x.member.member, etc.)

So this is basically the *current* behaviour of const.

Well yeah. But the syntax would be different coz of the extra asterisk.
 const*(S) s;
 s = S();                 /* OK* /
 s.x = 1;                 /* Error */
 s.p = new int[1];        /* Error */
 s.p[0] = 1;              /* Error */

s = S(1, [1].dup); I just bypassed the const*-ness.

Well, I see what you mean, but it doesn't really violate the intent of what I was getting at, which is kind of more like "You can change all of it at once, but not just bits of it". Again, it's just a side-effect of allowing identical syntax for structs and classes.
 const*(int[][]) a;      /* a is array of const array of const int */

Isn't this just const(int[])[]? It makes me uneasy that there's a whole set of synonyms for a particular const type.

Yeah but that's only true because int[][] happens to be a type for which you _can_ pull one level of indirection off by hand and rewrite it outside the brackets. In general, const*(T) a can't be rewritten in that way. It's only because T happened to be a special case (an array) that you were able to do that. So yeah, if T is an array, you can indirect by removing one level of []. But what if it isn't? That's where the asterisk notation comes in. So the price of generalised notation, is more than one way to write things, in some cases. Is that any worse than the current D2.0 scheme in which const (int**) p; const (int*)* p; are two different ways of writing the same thing?
 Also, you've said nothing on invariant.  I assume you mean to treat
 invariant in the same way as const?

I was trying to keep things simple. :-) I see no reason why it wouldn't work the same way, but <shrugs shoulders> who knows? Maybe there's something I haven't thought of.
Sep 08 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
OK, I guess you're right about PODs. Let's try again, but more generally:

Regardless of the type of T:

final(T) would be a type that was similar to T except that it would be
const, its members would be mutable, and all of its members' members
would be mutable, etc..

const(T) would be a type that was similar to T except that it would be
const, its members would be const, and all of its members' members
would be const, etc..

const*(T) would be a type that was similar to T except that it would
be mutable, its members would be const, and all of its members'
members would be const, etc..

const**(T)  would be a type that was similar to T except that it would
be mutable, and its members would be mutable, but its members' members
would be const, etc..

Now, if some of those possibilities turn out to be nonsense (e.g
final(int), then we can always make those possibilities compile-time
errors.

But it would be consistent, logical, and a boon to generic programming.
No?
Sep 08 2007
prev sibling parent "Janice Caron" <caron800 googlemail.com> writes:
 I think this has to be modified to: final(T) would be a type similiar to
 T except that its value would be const, but anything accessed indirectly
 through its value would be mutable.

Yes. I kind of hoped that was implicit in what I said, but that's what I meant anyway.
 Hence,

 struct S {
      int a;
      int* p;
 }

 final(S) s;
 s = S(47, 0);   // error, s is const
 s.a = 47;       // error, s is const
 *s.p = 47;      // ok, value pointed to by p is mutable

You're right. Although now we're back to square one, because now structs and classes behave differently once again. <Sigh>. Ah well - maybe this plan won't work. Or maybe it will and just needs a bit more refinement. Who knows? All I know for sure is that the existing D2.0 scheme is too confusing. The fact that const(int**) s; const(int*)* s; both mean the same thing is highly counterintuitive. If we're stuck with the current scheme, I would prefer that the first version be declared a compile-time error (since s can in fact be assigned).
Sep 08 2007