Jump to page: 1 2
Thread overview
Invariant doesn't apply to declared symbols
Nov 30, 2007
Jason House
Nov 30, 2007
Janice Caron
Nov 30, 2007
Janice Caron
Nov 30, 2007
Janice Caron
Nov 30, 2007
Walter Bright
Nov 30, 2007
Janice Caron
Nov 30, 2007
Walter Bright
Nov 30, 2007
Janice Caron
Nov 30, 2007
Jason House
Nov 30, 2007
Janice Caron
Nov 30, 2007
Walter Bright
Nov 30, 2007
Janice Caron
Nov 30, 2007
Jason House
Nov 30, 2007
Janice Caron
Nov 30, 2007
Janice Caron
November 30, 2007
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
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
> >   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
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
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
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
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
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
> 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
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