www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - const again

reply Walter Bright <newshound1 digitalmars.com> writes:
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.
Dec 06 2007
next sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
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 :-)
Dec 06 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
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 :-)

Dec 06 2007
next sibling parent Walter Bright <newshound1 digitalmars.com> writes:
Janice Caron wrote:
 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?

I think it would be, especially from the point of view of generic programming.
 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.

The less of such special casing the better <g>.
 What would & mean if one wrote:
         struct S { C c; }
         const(S)&[] a;
 ?

I had in mind that that would be a syntax error.

A special syntax for class types means that one has to know that type T is a class.
 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.

You see how pulling on that string brings us more and more special cases and problems :-( I won't say this is impossible, but it's always something we can think about later after we have more experience with const.
 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.

If one has a tail-const struct, do you call the mutable member function or the const member function?
Dec 06 2007
prev sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Walter Bright" wrote
 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; ?

What happens if you have this? const(T)*[] If T is a struct, then it's fine, but if it's a class, then what? To me the situation is just as bad. This whole problem stems from the fact that a struct declaration is a value type and a class declaration is a reference type, but they look the same. You are never going to have a consistent syntax for generic const code because you don't have a consistent syntax for normal declarations. I see one good solution. A pointer/reference declarator that acts the same for both structs and classes. class C {} struct S {} C x; // a reference to a class S x; // a struct C& x; // a reference to a class (identical to C x) S& x; // a reference to a struct (identical to S * x) S& x = new S; // heap allocated struct C& x = new C; // heap allocated class const(C) x; // a const reference to a const C const C x; // identical to const(C) const(C)& x; // a mutable reference to a const C const(C)[] x; // an array of const references to const C instances const(C)&[] x; // an array of mutable references to const C instances const(S) x; // a const S. Cannot set x or x.member const S x; // identical to const(S) const(S)[] x; // an array of const S types const(S)*[] x; // an array of mutable references to const S types const(S)&[] x; // an array of mutable references to const S types (identical to const(S)*[]) you need generic tail-const code? use const(T)& you need generic fully-const code? use const(T) or use something else instead of &, I don't care. It just seems like this notion that I can now have tail-const structs, but not tail-const classes is just as bad, if not worse, than the original problem.
 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.

const (X)* m; Please tell me how you will replace m with a 'smart' pointer type that is mutable, but the pointer contents are not. Without using a template. Because using a Template, I can do it with my regime also :)
 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).

Huh? I thought tail-const was that you could change the reference but not the members? i.e. const(S)* x, I can change x but not x.member? -Steve
Dec 06 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Steven Schveighoffer wrote:
 What happens if you have this?
 const(T)*[]

Array of pointers to constant T.
 If T is a struct, then it's fine, but if it's a class, then what?  To me the 
 situation is just as bad.

It is perfectly consistent. I don't see why it is bad.
 This whole problem stems from the fact that a struct declaration is a value 
 type and a class declaration is a reference type, but they look the same. 
 You are never going to have a consistent syntax for generic const code 
 because you don't have a consistent syntax for normal declarations.

Having distinctly different value and reference aggregates is, in my opinion, a good thing.
 const (X)* m;
 
 Please tell me how you will replace m with a 'smart' pointer type that is 
 mutable, but the pointer contents are not.

That's the way it works now. You can modify m, but not X.
 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).

Huh? I thought tail-const was that you could change the reference but not the members?

Yes, that's exactly what tail-const is.
Dec 06 2007
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Walter Bright" wrote
 Steven Schveighoffer wrote:
 What happens if you have this?
 const(T)*[]

Array of pointers to constant T.
 If T is a struct, then it's fine, but if it's a class, then what?  To me 
 the situation is just as bad.

It is perfectly consistent. I don't see why it is bad.

const(T)*[] x; x[0].member is a struct member if T is a struct, what is it if T is a class? I was under the (possibly wrong) impression that this is illegal?
 This whole problem stems from the fact that a struct declaration is a 
 value type and a class declaration is a reference type, but they look the 
 same. You are never going to have a consistent syntax for generic const 
 code because you don't have a consistent syntax for normal declarations.

Having distinctly different value and reference aggregates is, in my opinion, a good thing.

I totally agree. I don't think struct variables should become references. I was saying there should be a way to mean "reference to either struct or class" to make generic code with tail-constness easier to write
 const (X)* m;

 Please tell me how you will replace m with a 'smart' pointer type that is 
 mutable, but the pointer contents are not.

That's the way it works now. You can modify m, but not X.

Sure, but your original point was 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". I believed you were implying that this was a reason NOT to have a & operator. My question to you is, how does having a reference operator make this more difficult than not having one? A reference is a pointer. We already have those, I have no reason to believe that they are any less wrappable than a pointer?
 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).

Huh? I thought tail-const was that you could change the reference but not the members?

Yes, that's exactly what tail-const is.

OK, I am confused, isn't const(S)* tail const? And if so, how does this new scheme rid us of tail const? Couldn't I use this to call a const member function on a struct? The way I am interpreting your new const is that I can have tail-const for struct references (pointers) but not tail-const for class references. It seems to me it would be more important to have classes be const, why is your new way better? In other words, I can create a tail-const struct array via: const(S)*[] tailconst; tailconst[0] = new S; // ok to assign to tailconst[0] tailconst[0].nonconstfunc; // error, cannot call const function How do I do the same for a class? I don't want to have to use a * operator because I don't have to do that for structs. I want to make generic code. -Steve
Dec 06 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Steven Schveighoffer wrote:
 "Walter Bright" wrote
 Steven Schveighoffer wrote:
 What happens if you have this?
 const(T)*[]

 If T is a struct, then it's fine, but if it's a class, then what?  To me 
 the situation is just as bad.


const(T)*[] x; x[0].member is a struct member if T is a struct, what is it if T is a class? I was under the (possibly wrong) impression that this is illegal?

I don't understand. Exactly what is illegal?
 Sure, but your original point was 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".  I believed you were 
 implying that this was a reason NOT to have a & operator.  My question to 
 you is, how does having a reference operator make this more difficult than 
 not having one?  A reference is a pointer.

There's the problem we're having. While a class reference is implemented as a pointer 'under the hood', to the type system, it is not a pointer.
 OK, I am confused, isn't const(S)* tail const?

No. For const(int*), tail const of that construct would mean that the pointer is mutable while what it points to is not.
 And if so, how does this new 
 scheme rid us of tail const?

It would say that const(int*) would mean an immutable pointer to an immutable int.
 Couldn't I use this to call a const member 
 function on a struct?

I'm very confused as to what you're referring to.
 The way I am interpreting your new const is that I can have tail-const for 
 struct references (pointers) but not tail-const for class references.

No. The correct interpretation is that all parts of the type within the ( ) are immutable.
 In other words, I can create a tail-const struct array via:
 
 const(S)*[] tailconst;
 tailconst[0] = new S; // ok to assign to tailconst[0]
 tailconst[0].nonconstfunc; // error, cannot call const function
 
 How do I do the same for a class?

You cannot separate the class reference from the class fields.
Dec 06 2007
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Walter Bright" wrote
 Steven Schveighoffer wrote:
 "Walter Bright" wrote
 Steven Schveighoffer wrote:
 What happens if you have this?
 const(T)*[]

 If T is a struct, then it's fine, but if it's a class, then what?  To 
 me the situation is just as bad.


const(T)*[] x; x[0].member is a struct member if T is a struct, what is it if T is a class? I was under the (possibly wrong) impression that this is illegal?

I don't understand. Exactly what is illegal?

I guess it isn't :) I thought: class C { void f() {} } C c = new C; C *cp = &c; cp.f(); // I thought this was illegal, but it works! So you CAN still have tail-const. The only issue is that you need to have a reference to take the address of... It would be nice if I could just do: C * cp = new C; Then I could have generic code like: const(T)*[] x x = new const(T)*[5] for(int i = 0; i < 5; i++) x[i] = new T; // this will work for structs, not for classes then we have functionally tail-const back... I think this isn't going to be possible, because then you have no way of specifying I want a reference of a reference. Like a ref parameter. This is why I proposed that the & operator is used to mean 'pointer to'. The benefit is: T x; // for classes, this means pointer, for structs, it means value T *x; // for classes, this means pointer to pointer, for structs it means pointer. T &x; // for classes, it means pointer, for structs, it means pointer
 Sure, but your original point was 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".  I believed you were 
 implying that this was a reason NOT to have a & operator.  My question to 
 you is, how does having a reference operator make this more difficult 
 than not having one?  A reference is a pointer.

There's the problem we're having. While a class reference is implemented as a pointer 'under the hood', to the type system, it is not a pointer.
 OK, I am confused, isn't const(S)* tail const?

No. For const(int*), tail const of that construct would mean that the pointer is mutable while what it points to is not.

I believe I understand your confusion. To me tail-const == reference is mutable, pointed to data is not. I think of this as the semantic meaning of tail-const regardless of what the syntax says To you, tail-const is the specific syntax of const(C) for a reference type meaning C is tail-const. I agree that this is confusing, and bad for generic code. But const(C)* under the new regime is still functionally tail-const, is it not?
 And if so, how does this new scheme rid us of tail const?

It would say that const(int*) would mean an immutable pointer to an immutable int.
 Couldn't I use this to call a const member function on a struct?

I'm very confused as to what you're referring to.

I was confused also :) I think the new regime allows tail const, but makes it difficult to make tail-const references to classes.
 The way I am interpreting your new const is that I can have tail-const 
 for struct references (pointers) but not tail-const for class references.

No. The correct interpretation is that all parts of the type within the ( ) are immutable.

I understand this and agree with it.
 In other words, I can create a tail-const struct array via:

 const(S)*[] tailconst;
 tailconst[0] = new S; // ok to assign to tailconst[0]
 tailconst[0].nonconstfunc; // error, cannot call const function

 How do I do the same for a class?

You cannot separate the class reference from the class fields.

That is why I proposed a new syntax. If you want to do const in a way that is correct and backwards compatible, I think you cannot avoid this. To allow for more power for structs to easily have tail const, but not classes seems very wrong to me. -Steve
Dec 07 2007
prev sibling next sibling parent reply Sean Kelly <sean f4.ca> writes:
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
Dec 06 2007
next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
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.
Dec 06 2007
next sibling parent reply Christopher Wright <dhasenan gmail.com> writes:
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?
Dec 06 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Christopher Wright wrote:
 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?

No, it is a mutable pointer to a const Foo. A const pointer to a const Foo would be: const(Foo*) t;
 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?

Yes, because T[] will be the same thing as const(Foo)[]. Hiding it behind an alias and a template won't change that <g>.
Dec 06 2007
parent reply Christopher Wright <dhasenan gmail.com> writes:
Walter Bright wrote:
 Christopher Wright wrote:
 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?

No, it is a mutable pointer to a const Foo. A const pointer to a const Foo would be: const(Foo*) t;
 Will that fail?

Yes, because T[] will be the same thing as const(Foo)[]. Hiding it behind an alias and a template won't change that <g>.

So why do arrays take their const status from their elements when pointers don't?
Dec 06 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Christopher Wright wrote:
 Walter Bright wrote:
 Christopher Wright wrote:
 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?

No, it is a mutable pointer to a const Foo. A const pointer to a const Foo would be: const(Foo*) t;
 Will that fail?

Yes, because T[] will be the same thing as const(Foo)[]. Hiding it behind an alias and a template won't change that <g>.

So why do arrays take their const status from their elements when pointers don't?

??? I think there's a misunderstanding here on what arrays are. Arrays consist of a pointer/length pair. As far as const goes, they are just like pointers.
Dec 06 2007
parent Christopher Wright <dhasenan gmail.com> writes:
Walter Bright wrote:
 Christopher Wright wrote:
 Walter Bright wrote:
 Christopher Wright wrote:
 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?

No, it is a mutable pointer to a const Foo. A const pointer to a const Foo would be: const(Foo*) t;
 Will that fail?

Yes, because T[] will be the same thing as const(Foo)[]. Hiding it behind an alias and a template won't change that <g>.

So why do arrays take their const status from their elements when pointers don't?

??? I think there's a misunderstanding here on what arrays are. Arrays consist of a pointer/length pair. As far as const goes, they are just like pointers.

But I can reassign a const Foo, right?
Dec 07 2007
prev sibling next sibling parent reply Sean Kelly <sean f4.ca> writes:
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
Dec 06 2007
parent Walter Bright <newshound1 digitalmars.com> writes:
Sean Kelly wrote:
 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?

Yes for that reason, and the other reason is one rarely wants storage allocated for manifest constants. windows.d has 10,000 declarations in it, who wants 40K of executable bloat from const declarations?
 Or would they never want the const 
 qualifier to be picked up?

That was my thought, but this has two fatal problems with it: 1) you have to pick up the const qualifier if it's a struct, because the struct may wrap a class reference. Thus, structs would not behave like the underlying types. 2) Andrei pointed out that C++ (which drops the 'head const') is having severe problems with template metaprogramming over this, i.e. it becomes impractical to write "forwarder templates".
 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?

Unfortunately, they do cause problems. For example, struct S1 { int x; } struct S2 { int* p; : What does const mean for S1 and S2? Should it be stripped for one and not the other?
Dec 06 2007
prev sibling parent reply Sean Kelly <sean f4.ca> writes:
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
Dec 06 2007
parent Walter Bright <newshound1 digitalmars.com> writes:
Sean Kelly wrote:
 Oops, one last thing.  Does the transitivity mean it would be impossible 
 to have a const array of references to mutable data?

Yes.
Dec 06 2007
prev sibling parent reply Ary Borenszweig <ary esperanto.org.ar> writes:
Sean Kelly wrote:
 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.

Why not macro x = 3; ?
Dec 06 2007
next sibling parent reply Sean Kelly <sean f4.ca> writes:
Ary Borenszweig wrote:
 Sean Kelly wrote:
 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.

Why not macro x = 3;

Assuming that we need a new keyword, I like "macro" better. We're going to get it in 2.0 anyway, right? Sean
Dec 06 2007
parent reply Ary Borenszweig <ary esperanto.org.ar> writes:
Sean Kelly wrote:
 Ary Borenszweig wrote:
 Sean Kelly wrote:
 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.

Why not macro x = 3;

Assuming that we need a new keyword, I like "macro" better. We're going to get it in 2.0 anyway, right?

That's the idea. ;-) There a good use right there for that keyword.
Dec 06 2007
parent Derek Parnell <derek nomail.afraid.org> writes:
On Thu, 06 Dec 2007 18:45:38 -0300, Ary Borenszweig wrote:


 Ary Borenszweig wrote:
 Why not

 macro x = 3;



 There a good use right there for that keyword.

I agree. And I would still like to be able to group such definition together such as ... macro { x = 3; y = "qwerty"; z = 61.74; } -- Derek (skype: derek.j.parnell) Melbourne, Australia 7/12/2007 11:22:25 AM
Dec 06 2007
prev sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Ary Borenszweig wrote:
 Why not
 
 macro x = 3;
 ?

It's been suggested several times. It doesn't make much aesthetic sense to do things like: macro int x = 3;
Dec 06 2007
next sibling parent Derek Parnell <derek nomail.afraid.org> writes:
On Thu, 06 Dec 2007 16:57:20 -0800, Walter Bright wrote:

 Ary Borenszweig wrote:
 Why not
 
 macro x = 3;
 ?


 It's been suggested several times. 

I definitely think that overloading 'enum' to declare definitions is quite a lot worse than overloading 'macro' or 'alias'.
 It doesn't make much aesthetic sense 
 to do things like:
 
 	macro int x = 3;

It doesn't?!? On what authority have you made that assertion? I makes perfectly good aesthetic sense to me, more so than 'enum'. enum defname = "upload.log"; is NOT an enumeration as there is no numbers involved. macro defname = "upload.log"; looks more pleasing and grokable IMNSHO. And what do you say about the idea to group together such definitions? macro { a = 3; b = "qwerty"; c = 61.74; } rather than ... macro a = 3; macro b = "qwerty"; macro c = 61.74; -- Derek (skype: derek.j.parnell) Melbourne, Australia 7/12/2007 12:16:16 PM
Dec 06 2007
prev sibling next sibling parent Ary Borenszweig <ary esperanto.org.ar> writes:
Walter Bright wrote:
 Ary Borenszweig wrote:
 Why not

 macro x = 3;
 ?

It's been suggested several times. It doesn't make much aesthetic sense to do things like: macro int x = 3;

Well, macros like that would almost always refer to primitive types: ints, bools, strings, etc. So the type will be ommited most of the time. I can't come up with an example for a constant (like #define) that needs to declare it's type (in fact, #define doesn't declare a type). If it's an enum, you would say macro foo = SomeEnum.value; If it's a constant field in a type, it's the same way. If you want to have some constant initialized in a "static this", than it can't be done with the macro syntax.
Dec 06 2007
prev sibling parent reply Sean Kelly <sean f4.ca> writes:
Walter Bright wrote:
 Ary Borenszweig wrote:
 Why not

 macro x = 3;
 ?

It's been suggested several times. It doesn't make much aesthetic sense to do things like: macro int x = 3;

So are you saying that it would be fine without the type qualifier, or that you don't like the use of "macro" here at all?
Dec 06 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Sean Kelly wrote:
 Walter Bright wrote:
 Ary Borenszweig wrote:
 Why not

 macro x = 3;
 ?

It's been suggested several times. It doesn't make much aesthetic sense to do things like: macro int x = 3;

So are you saying that it would be fine without the type qualifier, or that you don't like the use of "macro" here at all?

Since the optional type qualifier should be supported, it doesn't make sense.
Dec 06 2007
parent reply Sean Kelly <sean f4.ca> writes:
Walter Bright wrote:
 Sean Kelly wrote:
 Walter Bright wrote:
 Ary Borenszweig wrote:
 Why not

 macro x = 3;
 ?

It's been suggested several times. It doesn't make much aesthetic sense to do things like: macro int x = 3;

So are you saying that it would be fine without the type qualifier, or that you don't like the use of "macro" here at all?

Since the optional type qualifier should be supported, it doesn't make sense.

It may be stretching the desire to use "macro" too far, but casting could be used to specify the type: macro x = 5L; // long int macro y = cast(short) 2; macro z = "abc"w; Thus it wouldn't actually be treated like a storage class. That might also save some confusion for things like: enum const static int x = 7;
Dec 07 2007
parent Walter Bright <newshound1 digitalmars.com> writes:
Sean Kelly wrote:
 It may be stretching the desire to use "macro" too far, but casting 
 could be used to specify the type:
 
 macro x = 5L; // long int
 macro y = cast(short) 2;
 macro z = "abc"w;

Yes, it could be. But I just can't see it being a preferred choice to use: macro y = cast(short) 2; rather than: enum short y = 2; as the latter is how declarations work (setting aside the use of 'enum' for the moment). There is a reasonable school of thought that says that the use of casting should be minimized, and that every cast should be scrutinized (since it is a meataxe rather than a scalpel). Having too many casts in regular use will hide the questionable uses.
 Thus it wouldn't actually be treated like a storage class.  That might 
 also save some confusion for things like:
 
 enum const static int x = 7;

That wouldn't be allowed any more than: typedef static int x = 7; because it makes no sense.
Dec 08 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
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.
Dec 06 2007
prev sibling next sibling parent Christopher Wright <dhasenan gmail.com> writes:
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; }
Dec 06 2007
prev sibling next sibling parent reply Derek Parnell <derek nomail.afraid.org> writes:
On Thu, 06 Dec 2007 13:33:46 -0800, 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.

I'm glad you're flexible ;-)
 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).

Which means for reference types, 'x' is modifiable but what it references is not modifiable. And for non-reference types, 'x' is not modifiable. So in some cases 'x' can be changed but in some cases it can't. Is that going to be a problem? const int x; // Can't change 'x' const int[] y; // Can change 'y' but not y[n]
 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.

An additional requirement, I guess, was to avoid creating a new keyword. In that case, the 'alias' keyword could also be overloaded to perform this role. alias x = 3; alias long y = 4; alias real pi = 3.14159265358979; especially in syntax forms that 'enum' would have problems with ... alias { x = 3; long y = 4; real pi = 3.14159265358979; }
 There's one last problem:
 	class C { }
 	const(C)[] a;
 	a[3] = new C();  // error, x[3] is const

I see, but in the new regime, doesn't this mean that both 'a' and what it refers to are const? Or would that be written ... const ( (C)[]) a; and thus what does this mean ... const (C[]) a; To summarize, we can write ... const C[] a; const (C)[] a; const (C[]) a; const ((C)[]) a; What will each of these forms signify? -- Derek (skype: derek.j.parnell) Melbourne, Australia 7/12/2007 9:44:43 AM
Dec 06 2007
parent Walter Bright <newshound1 digitalmars.com> writes:
Derek Parnell wrote:
 On Thu, 06 Dec 2007 13:33:46 -0800, 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.

I'm glad you're flexible ;-)

My overriding concern with const is to have a useful, correct, and workable system!
 
 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).

Which means for reference types, 'x' is modifiable but what it references is not modifiable.

No, it means whatever part of the type is inside the () is not modifiable.
 And for non-reference types, 'x' is not modifiable. So
 in some cases 'x' can be changed but in some cases it can't. Is that going
 to be a problem?
 
    const int   x;  // Can't change 'x'
    const int[] y;  // Can change 'y' but not y[n]

With these, neither x, y, nor y[n] are mutable.
   const C[] a;

a is a const array of const C.
   const (C)[] a;

a is an array of const C.
   const (C[]) a;

a is a const array of const C.
   const ((C)[]) a;

a is a const array of const C.
Dec 06 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 12/6/07, Walter Bright <newshound1 digitalmars.com> wrote:
 A special syntax for class types means that one has to know that type T
 is a class.

and
 If one has a tail-const struct, do you call the mutable member function
 or the const member function?

Got it. I don't have a solution yet, but at least I know what to think about. If we can't have mutable-ref-to-const-class at all, as you say, it's not so bad. I'd say we can live without that. There are always other ways to code things. But I'll keep thinking. Thanks.
Dec 06 2007
prev sibling next sibling parent reply BCS <ao pathlink.com> writes:
totally random though: I'm designing a program and ran across a place where 
I want to say "this pointer shall never be dereferenced, it shall only be 
used as an identity" the idea being that I want to be sure that the code 
has no dependencies on anything under it. It would be used somthing like 
a key in a hash table but where the test is "same thing" not "equal things". 
What should this be implemented as? Does this have any place in the const 
system? Is there an existing clean solution?

I'm putting this out more as food for though than as a serious suggestion 
or request for thoughts on how to implement stuff in my case.
Dec 06 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
BCS wrote:
 totally random though: I'm designing a program and ran across a place 
 where I want to say "this pointer shall never be dereferenced, it shall 
 only be used as an identity" the idea being that I want to be sure that 
 the code has no dependencies on anything under it. It would be used 
 somthing like a key in a hash table but where the test is "same thing" 
 not "equal things". What should this be implemented as? Does this have 
 any place in the const system? Is there an existing clean solution?

Since the point of a pointer is to dereference, perhaps a pointer is the wrong thing. Perhaps it should be a struct. --------------------------------------------- Doolittle: "Bomb, what is your purpose?" Bomb #20: "To explode, of course" -- Dark Star
Dec 06 2007
parent BCS <BCS pathlink.com> writes:
Walter Bright wrote:
 BCS wrote:
 
 totally random though: I'm designing a program and ran across a place 
 where I want to say "this pointer shall never be dereferenced, it 
 shall only be used as an identity" the idea being that I want to be 
 sure that the code has no dependencies on anything under it. It would 
 be used somthing like a key in a hash table but where the test is 
 "same thing" not "equal things". What should this be implemented as? 
 Does this have any place in the const system? Is there an existing 
 clean solution?

Since the point of a pointer is to dereference, perhaps a pointer is the wrong thing. Perhaps it should be a struct.

In my case I have a table of info that I want to get things from, the things to be got are associated with object that are available to the caller. If I don't use the object pointers, then I need to add some process unique ID to the objects as handles. The pointer is good enough except that I want to drop the implies functional dependency. I might try typedefs of void* but that seems hackish.
Dec 07 2007
prev sibling parent reply sambeau <no-spam-for-sambeau mac.com> writes:
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.

Rather than re-using enum (and confusing me) why not use 'define' eg:- define x = 3; define long y = 4; You can then a) emphasise 'definite' in the docs: "Having distinct limits", "Indisputable; certain", "Clearly defined; explicitly precise" while b) comforting C users.
Dec 06 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
sambeau wrote:
 Rather than re-using enum (and confusing me) why not use 'define' eg:-
 
 define x = 3;
 define long y = 4;

'define' has been proposed before. I'd prefer to stay away from it because of the negative connotations of C's preprocessor.
Dec 06 2007
parent reply sambeau <please-dont-spam-sambeau mac.com> writes:
Walter Bright Wrote:

 'define' has been proposed before. I'd prefer to stay away from it 
 because of the negative connotations of C's preprocessor.

How about 'let' then? It works for FP.. Or just 'def': it is a good short version of 'definite' in the way that 'int' is short for 'integer' and 'const' is short for 'constant'. However, surely anyone can see that without a '#' in front 'define' is very different to '#define'. s
Dec 06 2007
next sibling parent reply sambeau <please-dont-spam-sambeau mac.com> writes:
sambeau Wrote:

 Walter Bright Wrote:
 
 I'd prefer to stay away from it 
 because of the negative connotations of C's preprocessor.


On reflection, I don't think that's a good reason at all. The principal of least surprise is, I feel, more important. 'enum' was, in my opinion, always a slightly dodgily named construct at the best of times. Enumeration, to me, implies more than one thing. I feel, with all due respect (and a lot of deference) :-), that you are being a language implementor, looking at the guts of your implementation and asking "what else is this like?" rather than asking yourself (as a language designer) and saying "how best can I express this concept using terms most people will understand". Enum is just weird in this respect. It feels wrong.
Dec 06 2007
parent reply Christopher Wright <dhasenan gmail.com> writes:
sambeau wrote:
 sambeau Wrote:
 
 Walter Bright Wrote:

 I'd prefer to stay away from it 
 because of the negative connotations of C's preprocessor.


On reflection, I don't think that's a good reason at all. The principal of least surprise is, I feel, more important. 'enum' was, in my opinion, always a slightly dodgily named construct at the best of times. Enumeration, to me, implies more than one thing. I feel, with all due respect (and a lot of deference) :-), that you are being a language implementor, looking at the guts of your implementation and asking "what else is this like?" rather than asking yourself (as a language designer) and saying "how best can I express this concept using terms most people will understand". Enum is just weird in this respect. It feels wrong.

Well, there are anonymous enums, right? /me hasn't used enums in quite some time. So it's just letting you use all primitives with an enum, not just arithmetic types.
Dec 06 2007
parent Walter Bright <newshound1 digitalmars.com> writes:
Christopher Wright wrote:
 Well, there are anonymous enums, right?

Right. We already have: enum { x = 3 }
Dec 06 2007
prev sibling parent Leandro Lucarella <llucax gmail.com> writes:
sambeau, el  6 de diciembre a las 20:53 me escribiste:
 Walter Bright Wrote:
 
 'define' has been proposed before. I'd prefer to stay away from it 
 because of the negative connotations of C's preprocessor.

How about 'let' then? It works for FP..

Even when I find 'let' appealing, I think 'macro' is the way to go. It doesn't introduce a new keyword ('macro' is a keyword since DMD 1.011) and is something it got rewriten/processed by the compiler at compile-time. -- Leandro Lucarella (luca) | Blog colectivo: http://www.mazziblog.com.ar/blog/ ---------------------------------------------------------------------------- GPG Key: 5F5A8D05 (F8CD F9A7 BF00 5431 4145 104C 949E BFB6 5F5A 8D05) ---------------------------------------------------------------------------- ¿Qué será lo que hace que una brújula siempre marque el norte? - Ser aguja, nada más, y cumplir su misión. -- Ricardo Vaporeso
Dec 07 2007