www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - The problem with const (and a solution)

reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
Const sucks again.  The underlying problem with const in D is that classes 
cannot be value types.  Since a class cannot be a value type, there is no 
need to put a pointer indicator in variable declarations.  Declaring a class 
type is the same as a class pointer. Throughout this example, I'll define 
two types:

class C {}
struct S {}

OK, so why is this such a problem?  Because it is a fairly common 
requirement to have a mutable pointer to const data.  We call this, 
tail-const.  How does one distinguish a mutable pointer to const data? 
Walter came up with the good idea of using const as a function to specify 
which part of a type declaration is const.  For structs, this is great:

const(S)* s;

Now, s is not const, but what s points to IS.  I really like this idea. 
However, classes DO NOT separate the pointer from the type.  i.e., when I 
declare a class type, it is already a pointer:

const(C) c; // can't get at the pointer part, so c is not mutable.

because the pointer is built into the type declaration, there is no way to 
take it outside the parentheses.  In a valiant effort to allow for this type 
of thing, Walter proclaimed that const(C) means that C is 'tail-const', 
meaning that the value type that this declared was mutable, but anything it 
pointed to was const.  This caused much confusion and was the cause of 
thread war I, 'const sucks' (previously known as the great thread war, 
matched only by thread war II, 'Phango').  But how do we pull out that 
pointer?  Well, if C is a pointer, then dereference the pointer.  i.e.:

const(*C)* c;

This means, the value part of C is const, but the pointer part is not.  Yes, 
it is ugly, but it is clear what the meaning is.  Note that *C should only 
be usable if you want to split out the pointer, you couldn't do:

*C c;

However, the problem still exists in generic code:

f(T)(const(T)* t)
{
}

This means something completely different depending on if t is a struct or t 
is a class.  This problem is NOT FIXABLE with the current proposed regime by 
Walter.  But with my syntax, this can be worked around in the following way:

template(T) vtype
{
  static if(is(T == class))
     alias *T vtype;
  else
     alias T vtype;
}

f(T)(const(vtype!(T))* t)
{
}

So this is even uglier.  However, I think there may be a solution that is 
not ugly, but still gives us the same usability.  If the & operator is used 
to mean 'pointer to the value type', this could be used to mean the SAME 
thing for both classes and structs.  For example:

C& c; // reference to a C class
S& s; // reference to a S struct

anything that has & in it levels the playing field.  Note that for classes, 
C& c is equivalent to C c, and S& s is equivalent to S* s.  What does this 
give us?  Now we can split out the pointer for tail-const anything:

const(T)& t;

This means a mutable pointer to a const T, where T is either a value type 
(struct, int, etc) or a class value (not a reference to a class, but the 
class data itself).  This gives us a tail-const T no matter if T is a value 
type or a class type.

This solution is by far my favorite, but I can certainly live with the *C 
notation.  However, I think tail-const for classes NEEDS TO EXIST.  Code 
that correctly uses const will be ugly and cumbersome without it, and what 
will end up happening is people will just not use const at all except for 
strings.

-Steve 
Dec 07 2007
parent reply =?ISO-8859-15?Q?S=F6nke_Ludwig?= writes:
Steven Schveighoffer wrote:
 const(*C)* c;
 

This would be a workable solution. Of course not pretty, but workable.
 C& c; // reference to a C class
 S& s; // reference to a S struct

I like the idea of unifying the different type classes. However, as far as I understand it, the semantics of structs and classes will still be different with this syntax. This may be a potential source of bugs, especially for people coming from C++: // struct references S a = S(1); S& b = a; a = S(2); assert( b == a ); // class references C a = new C(1); C& b = a; a = new C(2); assert( b == a ); // fails // .. same as C a = new C(1); C b = a; a = new C(2);
Dec 07 2007
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Sönke Ludwig" <ludwig informatik_dot_uni-luebeck.de> wrote in message 
news:fjbtgu$24dm$1 digitalmars.com...
 Steven Schveighoffer wrote:
 const(*C)* c;

This would be a workable solution. Of course not pretty, but workable.
 C& c; // reference to a C class
 S& s; // reference to a S struct

I like the idea of unifying the different type classes. However, as far as I understand it, the semantics of structs and classes will still be different with this syntax. This may be a potential source of bugs, especially for people coming from C++: // struct references S a = S(1); S& b = a;

This would not compile, a is a value type, b is a pointer type, you need: S&b = &a;
 a = S(2);
 assert( b == a );

 // class references
 C a = new C(1);
 C& b = a;
 a = new C(2);
 assert( b == a ); // fails

 // .. same as
 C a = new C(1);
 C b = a;
 a = new C(2);

Yes, but this is not equivalent to what you were saying with structs, as you are changing the underlying value of what the reference is pointing to. In this case, you are just changing what the original reference is pointing to, and so the old reference still exists intact. -Steve
Dec 07 2007
parent reply =?ISO-8859-15?Q?S=F6nke_Ludwig?= writes:
Steven Schveighoffer schrieb:
 // struct references
 S a = S(1);
 S& b = a;

This would not compile, a is a value type, b is a pointer type, you need: S&b = &a;

Hm, ok, so S& is the same as S* then? The class example should just show that it might be unexpected for a C++ programmer that the assert fails. But then again, D's classes already behave differently anyway.
Dec 07 2007
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Sönke Ludwig" wrote
 Steven Schveighoffer schrieb:
 // struct references
 S a = S(1);
 S& b = a;

This would not compile, a is a value type, b is a pointer type, you need: S&b = &a;

Hm, ok, so S& is the same as S* then?

Yes
 The class example should just show that it might be unexpected for a C++ 
 programmer that the assert fails. But then again, D's classes already 
 behave differently anyway.

Yeah, I was intending the X& meaning to be 'pointer to value', not for it to mean 'C++ reference'. This works for D because d's pointer to values already act like C++ references when using the object, but act like pointers when assigning. For example: S s1; S* sp = new S; sp = &s1; // acts like a pointer s.x = 5; // acts like a reference, sets s1.x for classes, there is an implied pointer, which is what makes this tail-const problem so hard... -Steve
Dec 07 2007
parent =?ISO-8859-15?Q?S=F6nke_Ludwig?= writes:
Steven Schveighoffer schrieb:
 "Sönke Ludwig" wrote
 Steven Schveighoffer schrieb:
 // struct references
 S a = S(1);
 S& b = a;

S&b = &a;


Yes
 The class example should just show that it might be unexpected for a C++ 
 programmer that the assert fails. But then again, D's classes already 
 behave differently anyway.

Yeah, I was intending the X& meaning to be 'pointer to value', not for it to mean 'C++ reference'. This works for D because d's pointer to values already act like C++ references when using the object, but act like pointers when assigning. For example: S s1; S* sp = new S; sp = &s1; // acts like a pointer s.x = 5; // acts like a reference, sets s1.x for classes, there is an implied pointer, which is what makes this tail-const problem so hard... -Steve

Ok, see what you mean then. But apart from that, I actually understand the type system and this very problem perfectly well :)
Dec 07 2007