View mode: basic / threaded / horizontal-split · Log in · Help
November 30, 2007
Invariant doesn't apply to declared symbols
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.
November 30, 2007
Re: Invariant doesn't apply to declared symbols
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?
November 30, 2007
Re: Invariant doesn't apply to declared symbols
> >   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.
November 30, 2007
Re: Invariant doesn't apply to declared symbols
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.
November 30, 2007
Re: Invariant doesn't apply to declared symbols
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
November 30, 2007
Re: Invariant doesn't apply to declared symbols
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
November 30, 2007
Re: Invariant doesn't apply to declared symbols
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.
November 30, 2007
Re: Invariant doesn't apply to declared symbols
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.
November 30, 2007
Re: Invariant doesn't apply to declared symbols
> 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).
November 30, 2007
Re: Invariant doesn't apply to declared symbols
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; */
« First   ‹ Prev
1 2
Top | Discussion index | About this forum | D home