December 06, 2007 const again | ||||
---|---|---|---|---|
| ||||
As a result of all the const comments here, and some serious semantic problems discovered (dang it's hard to think of everything in advance), it seems there needs to be another tweak of the const behavior. Every time we try to sneak in "tail-const" in some form, some semantic correctness breaks somewhere. For example, struct member functions can be const or non-const, but there is no notion of "tail-const" for struct member functions. Trying to accommodate this results in all kinds of peculiar rules and inconsistencies. The second thing that just causes endless difficulties is the idea of using const to declare manifest constants, as in: const x = 3; having the same meaning as the C: #define x 3 in that 1) x does not consume storage and 2) x is typed as int, not const(int). For example, auto i = x; one would *not* want i to be typed as const(int). So, we're going to try a new, simpler regime: const T x; is semantically identical to: const(T) x; and the type of x is const(T). That leaves what to do about manifest constants. It occurs that we already have a mechanism for them - enums. So why not: enum x = 3; enum long y = 4; ? I think that solves our problem. There's one last problem: class C { } const(C)[] a; a[3] = new C(); // error, x[3] is const does not work with this new regime. Every twist we tried to make it work caused other problems. Eventually, it just became clear that this just is not going to work. But, the following does work: a ~= new C(); a = a[1..3]; a = b; just like for strings. One can copy, concatenate, and slice such arrays (just like for strings). It's not so bad. Andrei also mentioned the possibility of using a template: TailConst!(C)[] a; which would do whatever was necessary under the hood to allow the elements of a to be rebound while still keeping the contents of the C objects const. |
December 06, 2007 Re: const again | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | May I be the first to say I am very happy with everything you've said. Thank you. :-) On 12/6/07, Walter Bright <newshound1@digitalmars.com> wrote: > There's one last problem: > class C { } > const(C)[] a; > a[3] = new C(); // error, x[3] is const > does not work with this new regime. Every twist we tried to make it work > caused other problems. I understand the problem. Have you considered the possible solution which has been suggested on this group - a special syntax for classes having mutable refs to constant data? Under that suggestion, the above code would become: class C { } const(C)&[] a; a[3] = new C(); // OK It's the ampersand that makes it OK. It tells you that the reference is not constified. I think that would work, in exactly the same way that struct S { } struct(S)*[] a; a[3] = new S(); // OK works. > Andrei also mentioned the > possibility of using a template: > TailConst!(C)[] a; > which would do whatever was necessary under the hood to allow the > elements of a to be rebound while still keeping the contents of the C > objects const. That's kind of the same thing as const(C)&[] a, except with a template instead of an ampersand. Up to you which way you like more. Anyway, like I said, I'm happy. D is brilliant :-) |
December 06, 2007 Re: const again | ||||
---|---|---|---|---|
| ||||
Posted in reply to Janice Caron | Janice Caron wrote: > I understand the problem. Have you considered the possible solution > which has been suggested on this group - a special syntax for classes > having mutable refs to constant data? Under that suggestion, the above > code would become: > > class C { } > const(C)&[] a; > a[3] = new C(); // OK > > It's the ampersand that makes it OK. It tells you that the reference > is not constified. > > I think that would work, in exactly the same way that > > struct S { } > struct(S)*[] a; > a[3] = new S(); // OK > > works. There are a couple problems with it, the worst of which is its impact on generic code: const(T)[] Would you put the & there or not? What would & mean if one wrote: struct S { C c; } const(S)&[] a; ? One principle we try to adhere to is that it should make sense to be able to wrap any type with a struct, and have it be possible for that struct to behave as if it were that member type. And finally, this suggests that & means "tail-const". Tail-const has that severe problem that there is no such thing as a tail-const member function (i.e. a member function that can modify the fields of the object, but not anything those fields refer to). > Anyway, like I said, I'm happy. I'm glad! > D is brilliant :-) |
December 06, 2007 Re: const again | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | Walter Bright wrote: > As a result of all the const comments here, and some serious semantic problems discovered (dang it's hard to think of everything in advance), it seems there needs to be another tweak of the const behavior. > > Every time we try to sneak in "tail-const" in some form, some semantic correctness breaks somewhere. For example, struct member functions can be const or non-const, but there is no notion of "tail-const" for struct member functions. Trying to accommodate this results in all kinds of peculiar rules and inconsistencies. > > The second thing that just causes endless difficulties is the idea of using const to declare manifest constants, as in: > const x = 3; > having the same meaning as the C: > #define x 3 > in that 1) x does not consume storage and 2) x is typed as int, not const(int). For example, > auto i = x; > one would *not* want i to be typed as const(int). Makes sense. > So, we're going to try a new, simpler regime: > const T x; > is semantically identical to: > const(T) x; > and the type of x is const(T). > > That leaves what to do about manifest constants. It occurs that we already have a mechanism for them - enums. So why not: > enum x = 3; > enum long y = 4; > ? I think that solves our problem. One thing that concerns me. If we assume that "enum x = 3" is simply shorthand for "enum { x = 3 }" then all such declarations would be implicitly typed as integers, which doesn't sound like you want. It seems you're suggesting "enum" be treated more like a storage class here? I'm not sure if I like that idea. > There's one last problem: > class C { } > const(C)[] a; > a[3] = new C(); // error, x[3] is const As I see it, an array of objects can have two levels of const-ness: 1. The array itself is const and may not be resized or have its contents changed. 2. The array is not const but its elements are, so the array may be resized, but only const member functions may be called on the contained objects. To me, "const(C)[]" looks like case 2, because only the element type is in parenthesis. Thus "const C[]" or "const (C[])" would both represent case 1. However, this is obviously not how things currently work. I this has something to do with the desire to shoehorn tail const into the design, but it doesn't feel natural to me. Can you explain why it works the way it does? Sean |
December 06, 2007 Re: const again | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | On 12/6/07, Walter Bright <newshound1@digitalmars.com> wrote: > There are a couple problems with it, the worst of which is its impact on > generic code: > const(T)[] > Would you put the & there or not? Good point. You'd need it for a class, but not for a struct. I hadn't thought of that. That said, would that be much of a problem? Arrays of structs are very different beasts from arrays of class references. The copy semantics alone are different enough that one might imagine that generic code would need one or two if(is(T==struct))s in there anyway. > What would & mean if one wrote: > struct S { C c; } > const(S)&[] a; > ? I had in mind that that would be a syntax error. > One principle we try to adhere to is that it should make sense to be able to wrap any type with a struct, and have it be possible for that struct to behave as if it were that member type. If we went with the & syntax, then that principle would dictate the requirement of an additional operator overload, which for want of a better name I shall temporarily call opAmpersand. (...which begs the question, which of opStar and opAmpersand has more right to be called opDeref... but let's worry about that later!) > And finally, this suggests that & means "tail-const". I prefer to think that it means "reference to", in the same way that * means "pointer to". However, this is D, not C++, so the symbol would only be allowed for types which were /already references/, in which case it could be used to indicate tail constness by placing the symbol outside the brackets. Note that const(C&) would mean exactly the same thing as const(C) - so it's not the ampersand that means tail-const, it's its placement. > Tail-const has > that severe problem that there is no such thing as a tail-const member > function (i.e. a member function that can modify the fields of the > object, but not anything those fields refer to). Forgive me - perhaps I haven't thought this through deeply enough. I don't follow why that's a problem. |
December 06, 2007 Re: const again | ||||
---|---|---|---|---|
| ||||
Posted in reply to Sean Kelly | Sean Kelly wrote: > Walter Bright wrote: >> That leaves what to do about manifest constants. It occurs that we already have a mechanism for them - enums. So why not: >> enum x = 3; >> enum long y = 4; >> ? I think that solves our problem. > > One thing that concerns me. If we assume that "enum x = 3" is simply shorthand for "enum { x = 3 }" then all such declarations would be implicitly typed as integers, which doesn't sound like you want. It would be implicitly typed to the type of its initializer, or you can give a type. Yes, it would behave like a storage class. >> There's one last problem: >> class C { } >> const(C)[] a; >> a[3] = new C(); // error, x[3] is const > > As I see it, an array of objects can have two levels of const-ness: > > 1. The array itself is const and may not be resized or have its contents changed. > 2. The array is not const but its elements are, so the array may be resized, but only const member functions may be called on the contained objects. > > To me, "const(C)[]" looks like case 2, because only the element type is in parenthesis. Thus "const C[]" or "const (C[])" would both represent case 1. However, this is obviously not how things currently work. Right, but under the new regime, that is how they would work. > I this has something to do with the desire to shoehorn tail const into the design, but it doesn't feel natural to me. Can you explain why it works the way it does? I've given up on tail const in any of its forms. The new regime has no tail const, no head const, it's just const, and fully transitive const at that. |
December 06, 2007 Re: const again | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | Walter Bright wrote:
> TailConst!(C)[] a;
> which would do whatever was necessary under the hood to allow the elements of a to be rebound while still keeping the contents of the C objects const.
The TailConst template would probably need to be a struct:
struct TailConst (T) {
static if (is (T == class) || is (T == interface)) {
// can just store a reference
} else {
// assignment mallocs some memory and copies arg onto heap
// store a ptr
}
}
And then we'd need an opDot if we wanted transparent access. It's an annoyance, but a minor one, to have to use opDeref.
But I think perhaps the problem with arrays is that there's no way to say the array is not const, but its members are. The obvious syntax would be:
const(Foo)[] foos = new const(Foo)[5];
foos[2] = new Foo(); // ok
const(Foo[]) cfoos = new Foo[5];
cfoos[3] = new Foo(); // error; cfoos is const
Then string would be an alias to invariant(char[]) rather than invariant(char)[], which makes sense. It's not a mutable array of invariant characters.
However, this would suggest:
const(const(Foo)[]) ccfoos;
Which would suggest the cfoos array is a const buffer, but if it holds reference types, they are not const. And if the array is const, you can't rebind its elements.
The solution to that mess, if you need transitive const, is to define const(T[]) to be the same as const(const(T)[]), which won't affect primitives and value types but will get the desired result for reference types.
But it doesn't make sense to consider an array const if its elements are const; that would be extremely annoying with templates. If you wanted to use arrays for anything nontrivial, you'd have to start it with:
static if (is (T == const)) {
alias TailConst!(T) ElemType;
} else {
alias T ElemType;
}
|
December 06, 2007 Re: const again | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | Walter Bright wrote:
>> 1. The array itself is const and may not be resized or have its contents changed.
>> 2. The array is not const but its elements are, so the array may be resized, but only const member functions may be called on the contained objects.
>>
>> To me, "const(C)[]" looks like case 2, because only the element type is in parenthesis. Thus "const C[]" or "const (C[])" would both represent case 1. However, this is obviously not how things currently work.
>
> Right, but under the new regime, that is how they would work.
>
>> I this has something to do with the desire to shoehorn tail const into the design, but it doesn't feel natural to me. Can you explain why it works the way it does?
>
> I've given up on tail const in any of its forms. The new regime has no tail const, no head const, it's just const, and fully transitive const at that.
So if I have:
const(Foo)* t;
the pointer is const and points to a const Foo?
What about this:
template Ptr(T) {
alias T* Ptr;
}
Ptr!(const(Foo)) t;
If I have a template method that says:
void Something (T)() {
T[] stuff = new T[5];
stuff[2] = T.init;
}
Something!(const(Foo));
Will that fail?
|
December 06, 2007 Re: const again | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | Walter Bright wrote: > Sean Kelly wrote: >> Walter Bright wrote: >>> That leaves what to do about manifest constants. It occurs that we already have a mechanism for them - enums. So why not: >>> enum x = 3; >>> enum long y = 4; >>> ? I think that solves our problem. >> >> One thing that concerns me. If we assume that "enum x = 3" is simply shorthand for "enum { x = 3 }" then all such declarations would be implicitly typed as integers, which doesn't sound like you want. > > It would be implicitly typed to the type of its initializer, or you can give a type. Yes, it would behave like a storage class. I can't say I'm terribly fond of using "enum" for this, but it is certainly closer than any other language feature to what you want. However, I am still questioning the need for a new keyword here. Is it that the user may sometimes want type inference to pick up the const qualifier and not other times? Or would they never want the const qualifier to be picked up? If it's the former, then perhaps some rule could be established where the user would never want const to be preserved? For example, I can't see ever wanting it for basic data types like integers, but I might generally want it for classes. I suppose what I'm wondering is if this may be one instance where the ideas of head and tail const apply without causing too many problems? >>> There's one last problem: >>> class C { } >>> const(C)[] a; >>> a[3] = new C(); // error, x[3] is const >> >> As I see it, an array of objects can have two levels of const-ness: >> >> 1. The array itself is const and may not be resized or have its contents changed. >> 2. The array is not const but its elements are, so the array may be resized, but only const member functions may be called on the contained objects. >> >> To me, "const(C)[]" looks like case 2, because only the element type is in parenthesis. Thus "const C[]" or "const (C[])" would both represent case 1. However, this is obviously not how things currently work. > > Right, but under the new regime, that is how they would work. Oh okay. Then I'm all for it, at least in theory. :-) >> I this has something to do with the desire to shoehorn tail const into the design, but it doesn't feel natural to me. Can you explain why it works the way it does? > > I've given up on tail const in any of its forms. The new regime has no tail const, no head const, it's just const, and fully transitive const at that. This is unfortunate, but probably for the best. Sean |
December 06, 2007 Re: const again | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | Walter Bright wrote:
> Sean Kelly wrote:
>> Walter Bright wrote:
>
>>> There's one last problem:
>>> class C { }
>>> const(C)[] a;
>>> a[3] = new C(); // error, x[3] is const
>>
>> As I see it, an array of objects can have two levels of const-ness:
>>
>> 1. The array itself is const and may not be resized or have its contents changed.
>> 2. The array is not const but its elements are, so the array may be resized, but only const member functions may be called on the contained objects.
>>
>> To me, "const(C)[]" looks like case 2, because only the element type is in parenthesis. Thus "const C[]" or "const (C[])" would both represent case 1. However, this is obviously not how things currently work.
>
> Right, but under the new regime, that is how they would work.
>
>> I this has something to do with the desire to shoehorn tail const into the design, but it doesn't feel natural to me. Can you explain why it works the way it does?
>
> I've given up on tail const in any of its forms. The new regime has no tail const, no head const, it's just const, and fully transitive const at that.
Oops, one last thing. Does the transitivity mean it would be impossible to have a const array of references to mutable data?
Sean
|
Copyright © 1999-2021 by the D Language Foundation