www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Invariant doesn't apply to declared symbols

reply Jason House <jason.james.house gmail.com> writes:
This is the one thing that really bugs me about const...

The docs say that the following is legal
  invariant(S) s;
  s = ...; //legal

This seems to add logical inconsistency...

  invaraint(int*) p;
  p = ...; //legal

  invariant(char*)** p;
  **p = ...; //illegal

It seems that invariant(T) changes based on context!  (All examples above
come from http://www.digitalmars.com/d/const3.html)

Unfortunately, it doesn't stop here for me.

It seems that "const(char) *p;" and "const(char*) p;" have identical
meanings even though the syntax is different.

Also, "const(char) p;" and "const(S) s;" have different meanings.  In the
first, assignment to p is disallowed.  In the second, assignment to s is
allowed.  When going into generic coding or upgrading to more complex data
types in code, this type of thing *has* to bite someone eventually.

Also, how I define a const point to const data?  As I understand it, this is
impossible.  "const int* p;" and "const(int*) p;" both mean a non-const
pointer to const data.  While I don't explicitly see const(T) being the
same as const T in the docs, Walter has said this on the NG.

All of this leads me to ask wonder why this style of functionality is done. 
It seems like a whole lot of ambiguity just to allow "const(S) s;" to have
constant members but a non-constant reference.

I appreciate the argument that "const(S) s" is all that is syntactially
available without any modification, but I encourage some thought into using
a novel syntax for this.  I have not come up with a good syntax for this. 
The best I've got is
  const(S) s; // can not assign to s or s.foo
  const(S)& s; // can assign to s, but not s.foo
  const(S)* s; // can assign to s, but not *s

I think the last case may be the common case for working with foreign (C?)
API's.  I will admit to not understanding the argument of "in order to use
structs as wrappers for builtin types, ...".  I don't know of a common use
case for this.
Nov 29 2007
next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On Nov 30, 2007 7:04 AM, Jason House <jason.james.house gmail.com> wrote:
 This is the one thing that really bugs me about const...

We've kind of been through all of this before. I haven't completely grokked how things are changes since the last upheaval, but for what it's worth...
   invariant(S) s;
   s = ...; //legal

I thought Walter said we'd got rid of tail-const, and now there's just const? I thought that const(T) now meant that T is fully const? But you're right. The docs say otherwise. What's the deal?
 It seems that "const(char) *p;" and "const(char*) p;" have identical
 meanings even though the syntax is different.

That was one of things we all complained about. I really thought Walter was getting rid of that one, and that, from now on, const(T) would mean "T is fully const". Apparently not. Walter, could you clarify? Is this being sorted out?
Nov 30 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
   invariant(S) s;
   s = ...; //legal

I thought Walter said we'd got rid of tail-const, and now there's just const? I thought that const(T) now meant that T is fully const? But you're right. The docs say otherwise. What's the deal?

I suppose there is still an issue which hasn't gone away, which is this: Classes are passed by reference, so they are refence objects, just like pointers. However, /unlike/ pointers, there's no existing syntax to "dereference" them and refer directly to the bytes on the heap. So there's an ambiguity, in that when you write const(C) where C is a class, are you saying (a) the bytes on the heap are const, or (b) both the reference, and the bytes on the heap, are const? Clearly, my preference would be (b), since it seems so /intuitive/ that const(T) must mean that T is const. However, this does beg the question, is there a need for a syntax to descibe (a)? I suggest const(C.) observe the dot after the C. To my mind at least, that clearly denotes "members of", hence this means "members of C are const" (but the reference itself is not). That's not the only possible syntax, of course, and I don't want to get hung up on it. The point is, it's an issue that hasn't been properly addressed, and I believe it should be, if we are ever to "unconfuse" const.
Nov 30 2007
prev sibling next sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
Let's consider how this would be done in C++. To get a class on the
heap, you'd do

    C * c = new C();

To make the data only const, you'd do

    C const * c;

(or "const C * c" but let's not go into C++'s confusing alternatives
here). To make both the data and the pointer const, you'd do

    C const * const c;

(or "const C * const c" but again, let's not go into C++'s confusing
alternatives here). If classes in D used the syntax of pointers, it
would be easy. We could say:

    const(C)* // just the data is const
    const(C*) // the data and the pointer are both const

Unfortunately, that won't fly, because we /don't/ use pointer-syntax.
The pointer is /implicit/. So the dereferencing asterisk is also
implicit. It's a nasty problem for us, because of course we still want
the declaration to read like we're declaring a variable c with type C
(with some degree of constness applied somewhere). So any syntax which
makes that NOT obvious is one we should be wary of. Suggestions like

    const(&C) c

don't work for me, because it looks like we want c have type "address of C".

Fortunately, there is an answer. Well, we may not have pointers, but C
is a reference type, right? And we do have the "ref" keyword. Thus, we
could distinguish

    const(C) // just the data is const
    const(ref C) // the data and the pointer are both const

That one /kind of/ works - but it does violate the principle that

    const(T) x;
    x = y;

should always be a compile error. (If x is fully const, how can it be
assigned?). So here's my latest and greatest idea. What about:

    const(C)ref // just the data is const
    const(C) // the data and the pointer are both const

This would give us

    const(C) c;
    const(C) ref d;

    c = new C: // Error;
    d = new C; // OK
    d.x = 100; // Error

That one works for me. It's my favorite suggestion so far. It does
what we need, /and/ it allows const to follow a general pattern.
Nov 30 2007
parent Walter Bright <newshound1 digitalmars.com> writes:
Janice Caron wrote:
 Fortunately, there is an answer. Well, we may not have pointers, but C
 is a reference type, right? And we do have the "ref" keyword. Thus, we
 could distinguish
 
     const(C) // just the data is const
     const(ref C) // the data and the pointer are both const
 
 That one /kind of/ works - but it does violate the principle that
 
     const(T) x;
     x = y;
 
 should always be a compile error. (If x is fully const, how can it be
 assigned?). So here's my latest and greatest idea. What about:
 
     const(C)ref // just the data is const
     const(C) // the data and the pointer are both const
 
 This would give us
 
     const(C) c;
     const(C) ref d;
 
     c = new C: // Error;
     d = new C; // OK
     d.x = 100; // Error
 
 That one works for me. It's my favorite suggestion so far. It does
 what we need, /and/ it allows const to follow a general pattern.

I think this suggestion is over-engineered. It's much simpler to say that a declaration, even one with const type, is rebindable unless it has a storage class of const. const C c; const(C) d; c = new C: // Error; d = new C; // OK d.x = 100; // Error
Nov 30 2007
prev sibling next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
Jason House wrote:
 This is the one thing that really bugs me about const...
 
 The docs say that the following is legal
   invariant(S) s;
   s = ...; //legal

Yes.
 This seems to add logical inconsistency...
 
   invaraint(int*) p;
   p = ...; //legal
 
   invariant(char*)** p;
   **p = ...; //illegal
 
 It seems that invariant(T) changes based on context!  (All examples above
 come from http://www.digitalmars.com/d/const3.html)

No. See my other reply to Janice, what we have here is a rebindable variable.
 Unfortunately, it doesn't stop here for me.
 
 It seems that "const(char) *p;" and "const(char*) p;" have identical
 meanings even though the syntax is different.

No, the types are different. If you take the address of p, you'll see the type difference.
 Also, "const(char) p;" and "const(S) s;" have different meanings.  In the
 first, assignment to p is disallowed.  In the second, assignment to s is
 allowed.

Not true, assignment to both is allowed. To disallow rebindable variables, give them const storage class.
 When going into generic coding or upgrading to more complex data
 types in code, this type of thing *has* to bite someone eventually.
 
 Also, how I define a const point to const data?  As I understand it, this is
 impossible.  "const int* p;" and "const(int*) p;" both mean a non-const
 pointer to const data.  While I don't explicitly see const(T) being the
 same as const T in the docs, Walter has said this on the NG.

No, the types are the same, but the storage class is different.
 All of this leads me to ask wonder why this style of functionality is done. 
 It seems like a whole lot of ambiguity just to allow "const(S) s;" to have
 constant members but a non-constant reference.

Consider const(C) where C is a class type. If declarations of type const(C) were not rebindable, they become impossible to use.
 I appreciate the argument that "const(S) s" is all that is syntactially
 available without any modification, but I encourage some thought into using
 a novel syntax for this.  I have not come up with a good syntax for this. 
 The best I've got is
   const(S) s; // can not assign to s or s.foo
   const(S)& s; // can assign to s, but not s.foo
   const(S)* s; // can assign to s, but not *s
 
 I think the last case may be the common case for working with foreign (C?)
 API's.  I will admit to not understanding the argument of "in order to use
 structs as wrappers for builtin types, ...".  I don't know of a common use
 case for this.

It's much simpler: const(S) s; // can rebind s const S s; // cannot rebind s
Nov 30 2007
next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On Nov 30, 2007 10:58 AM, Walter Bright <newshound1 digitalmars.com> wrote:
 It's much simpler:

         const(S) s; // can rebind s
         const S s;  // cannot rebind s

That's not simple. S and (S) should be the same thing.
Nov 30 2007
prev sibling next sibling parent reply Jason House <jason.james.house gmail.com> writes:
Walter Bright wrote:

 Jason House wrote:
 This is the one thing that really bugs me about const...
 <snip>

const(S) s; // can rebind s const S s; // cannot rebind s

First of all, the docs don't explicitly talk about this. That really HAS to get fixed. Also, "const(char)*a" and "const(char*)a" having different reference types confuses me. In both cases, I can assign to a, but can't assign to *a. They really look the same. This should be documented too. Probably on a related note, "const(char) a" allows assignment to a (it's "rebindable"), but as far as I can tell, value types simply get overwritten when changed. I'd argue that such a definition is useless, confusing, error-prone, and should be a compile-time error. Maybe the problem is that the storage class specification is too easily confused with other const declarations. As much as it may draw tossing of rotten fruit at me, I think we need the final keyword. Would it be safe to say that "const int* x;" and "final const(int*) x;" are the same thing? Similarly that "invariant int* y;" and "final invariant(int*) y;" are the same thing? I'd argue that const and invariant should never be a storage class. At the risk of even more rotten fruit, this aligns well with the C++ interpretation of "const char*" as a function parameter. Of course, if a function has a return type of const(char*), it makes total sense that it should be assignable to a variable. I think it all works and should, in the end, be less confusing.
Nov 30 2007
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Jason House wrote:
 I'd argue that const and invariant should never be a storage class.

But then you'd have to give up: const x = 3;
Nov 30 2007
parent Jason House <jason.james.house gmail.com> writes:
Walter Bright wrote:

 Jason House wrote:
 I'd argue that const and invariant should never be a storage class.

But then you'd have to give up: const x = 3;

I've been thinking more and I don't really like the whole storage class thing anyway. I posted in the "const ideas" thread with my latest thoughts. In whatever situation, "const x = 3;" would be syntactically invalid under any scheme, so bolting it in to be a pure constant seems just fine. Personally, I'd never use it, but no language should ever be designed to be exactly what I'd use and include nothing else :)
Nov 30 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 11/30/07, Jason House <jason.james.house gmail.com> wrote:
 Would it be safe to say that "const int* x;" and "final const(int*) x;" are
 the same thing?  Similarly that "invariant int* y;" and "final
 invariant(int*) y;" are the same thing?

Under D2.007 you would be correct. I don't know if "final" has actually been dropped yet, but I'm assuming it has. It's certainly on its way out.
 I'd argue that const and invariant should never be a storage class.

That's the conclusion I came to.
Nov 30 2007
prev sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 11/30/07, Walter Bright <newshound1 digitalmars.com> wrote:
 Jason House wrote:
 I'd argue that const and invariant should never be a storage class.

But then you'd have to give up: const x = 3;

...unless you added an extra rule to re-introduce it. That's why I threw in rule (5) in my list of proposed changes. Let const x = y; be syntactic sugar for const(typeof(y)) x = y; without having to make const an attribute. See the full list of 5 on whatever thread it's on to see how it all might hang together if you went for it.
Nov 30 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On Nov 30, 2007 11:06 AM, Walter Bright <newshound1 digitalmars.com> wrote:
 I think this suggestion is over-engineered. It's much simpler to say
 that a declaration, even one with const type, is rebindable unless it
 has a storage class of const.

       const C c;
       const(C) d;


       c = new C: // Error;
       d = new C; // OK
       d.x = 100; // Error

I don't necessarily care if you use that particular syntax that I invented or not. Feel free to invent a better one (although obviously, I like mine). However, no disrespect intended, but yours is terrible! C should mean exactly the same thing as (C). "const C" and "const (C)" should be interchangable.
Nov 30 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
 So here's my latest and greatest idea. What about:

     const(C)ref // just the data is const
     const(C) // the data and the pointer are both const

 This would give us

     const(C) c;
     const(C) ref d;

     c = new C: // Error;
     d = new C; // OK
     d.x = 100; // Error

Or... Since we like function-style type constructors in D: ref(const(C)) // just the data is const const(C) // the data and the pointer are both const In the interests of plain old fashioned common sense, the following four lines should all be exactly equivalent and interchangeable... ref const C c; ref const(C) c; ref(const C) c; ref(const(C)) c; In each case, we are declaring c to be of type C, and then saying that c is const - except for the actual reference, which isn't. Note that ref-as-a-type-constructor would naturally be forbidden for non-reference types, so ref(const(int)) n; would be a syntax error, because int isn't a reference type. (...at least, until we get references in D, at which point we will then be able to legalise it).
Nov 30 2007
prev sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On Nov 30, 2007 12:43 PM, Janice Caron <caron800 googlemail.com> wrote:
 So here's my latest and greatest idea. What about:


Scrap that last idea. I thought of a better one... I started thinking ahead, to the time, however far off it is in the future, when D has C++-style references, and I started to think about what the syntax would be, in relation to const etc. The thing about references is, unlike pointers, they don't chain. You can chain pointers indefinitely - you can have a pointer to a pointer to a pointer to... ad infinitum, and each new level of indirection creates a new type. But it doesn't work like that with references. A reference to a reference is just a reference. T& is the same type as T&&, which is the same type as T&&&&&&&&&&&&&&&&&&&, and so on. It's all pretty simple and straightforward really. For any non-reference type T, T& is a new type: a reference-to-T. But for any reference type T, T& is the same thing as T. The rest follows logically. Now let's think ahead, and /imagine/ that we have C++-style references in D. Then... int x; // x is an int int* x; // x is a pointer to int int& x; // x is a reference to int And with const thrown in... const(int)* x; // x is a pointer to const int const(int)& x; // x is a reference to const int And then it occurred to me that this notation completely solves the problem of how to make existing reference types (e.g. classes) const. It is obvious that class C { int x; } const(C)& // c is a mutable reference to a const C const(C&) // c is a const reference to a const C but the cool bit is the final step. Since C& is the same thing as C (because C is a reference type), it follows that we can simplify the second one to const(C) // c is a const reference to a const C ...which of course, is the existing syntax, albeit with a different (and more obvious) meaning. So "C" is the same thing as "C&", and "const(C)" is the same thing as "const(C&)". What this means is that we now have a simple, and future-extendable syntax for distinguishing between const references and const data. const(C) c = new C; const(C)& d = new C; c = new C; // Error d = new C; // OK d.x = 100; // Error The syntax is "future-proof", in that, one day, we might get C++ references, and if and when we do, this syntax will cover it. struct S { int x; } const(S&) s; const(S)& t; s = new S; // Error; t = new S; // OK; t.x = 100; // Error; Observe that because a struct is not a reference type, S is a different type from S&. However, S&& is exactly the same type as S&. That's just the way that references work. I'm not pushing for references in D. I hope they'll come one day, but it's not a priority for me. The point of this post is that this /syntax/ for constness-of-classes would be consistent with future expansion. Compare this with the current syntax, which is: const(C) c; /* c is a reference to const C */ const(int) n; /* n is a mutable int */ const C c; /* c is a const reference to const C */ const int n; /* n is a const int */ It seems clear to me that the proposed new syntax would be better const(C) c; /* c is a const reference to const C */ const(C)& c; /* c is a mutable reference to const C */ const(int) n; /* n is a const int */ const C c; /* same as const(C) c; */ const int n; /* same as const(int) n; */
Nov 30 2007