www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - One more shot at this const thing.

reply Dave <Dave_member pathlink.com> writes:
I can still hear the horse whinnying, so I thought I'd beat it with a 
slightly different club <g> And that would be taking the best of C++ 
const modified to be more in line with D goals. I don't think the exact 
same has been outlined before, so if it has I apologize.

IIRC Walter's primary (and valid IMO) arguments against C++ const are:

1) it does not guarantee anything to a code maintainer
2) it does not guarantee anything to the compiler
3) it's ugly (const sprinkled everywhere).

But, I'm beginning to think that maybe C++'s const [with roughly the 
same compiler enforcement rules but slightly different semantics and 
syntax more applicable to D] would work Ok.

Also maybe const would apply only to built-in reference types (including 
class objects) and not pointers, justifiable because D has these 
built-in whereas C++ doesn't. The same 'const' would not apply to value 
types either, for simplicity.

Consider the following C++ code:

class C
{
public:
     const char *str;
     C(const char *val): str(val)
     {
     }
     char
     const *           // const reference data return value
     foo(char          // const reference data
         const *       // const reference data
         const val     // const reference
        ) const        // const object
     {
         val = new char[100]; // error: assignment of read-only parameter
         val[0] = 'a';        // error: assignment of read-only location
         str = new char[100]; // error: assign to member of const object
         str[0] = 'a';        // error: assignment of read-only location
         return str;
     }
     int i;
     void bar(int val) { i = val; }
     int baz() const
     {
         return i;
     }
};

int main()
{
     C c("test string");
     char* str, *st2;
     st2 = c.foo(str); // error: invalid conversion const char* to char*
     const char* st3 = c.foo(str); // Ok
     return 0;
}

int bar(const C &c)
{
     c.i = 10; // error: assignment of data-member in read-only structure
     c.foo("test");
     c.bar(10); // error: discards qualifiers
     return c.baz() * 10;
}

So in D this would be:

class C
{
     const char[] str;
     this(char[] val)
     {
         str = val;
     }
     // const reference & data for return value, argument and object
     // ugly, but necessary and not nearly as ugly as C++
     const char[] foo(const char[] val) const
     {
         val = new char[100]; // error: assignment of read-only parameter
         val[0] = 'a';        // error: assignment of read-only location
         str = new char[100]; // error: assign to member of const object
         str[0] = 'a';        // error: assignment of read-only location
         return str;
     }
     int i;
     void bar(int val) { i = val; }
     int baz() const
     {
         return i;
     }
}

void main()
{
     C c = new C("test string");
     char[] str, st2;
     st2 = c.foo(str); // error: conversion from const char[] to char[]
     const char[] st3 = c.foo(str); // Ok

     // The C++ rules plus all this looks readily enforceable by the
     //  compiler to me.
     st2 = cast(char[])c.foo(str); // error: invalid cast
     st2 = st3; // error
     str[0] = st3[0]; // Ok
     st3 = st2; // error
     st3 = cast(const char[]) st2; // error: invalid cast
     st3[0] = st2[0]; // error
     const char* ptr; // error, invalid type (how much broken code?)
}

int bar(const C c)
{
     c.i = 10; // error
     c.foo("test"); // Ok
     c.bar(10); // error
     return c.baz * 10; // Ok
}

The compiler can't enforce everything you can do to a const in D. To 
make it so that const can actually be meaningful, additional language 
could be added to the spec. That language could be as simple as 
something like: "subverting const can result in undefined behavior".

This spec. language is justifiable (again) because D has built-in 
reference types that C++ doesn't, and it's done like that for numerous 
other things in D (e.g.: missing return statements and order of 
evaluation) so it's not inconsistent.

I think all of the above is enforceable for a compiler (so it's 
meaningful in the general case) and also is not as ugly or convoluted as 
C++ syntax. I believe it takes care of the issues of a) not having any 
help by the compiler on enforcing COW and b) being able to pass class 
and other reference objects into a function and 'guarantee' that the 
object not be modified inside the function.

The actual keyword used is not important.

One of the major reasons Java is slow is because of all the data 
duplication that needs to go on. D needs to avoid this... Billions of $ 
and years of research into Java GC and runtime optimizers has not solved 
the problem, leading me to believe it never will. If D gets the same rap 
as Java on the perf. issues, game over. Likewise if programmer-only 
enforced COW turns out to be a bug-ridden maintenance nightmare for 
corporate programmers out there.

Thoughts on this method of 'const' for D?

Thanks,

- Dave
Jul 27 2006
parent reply Reiner Pope <reiner.pope gmail.com> writes:
Dave wrote:
 Also maybe const would apply only to built-in reference types (including 
 class objects) and not pointers, justifiable because D has these 
 built-in whereas C++ doesn't. The same 'const' would not apply to value 
 types either, for simplicity.
This presumably has the consequence that you can't get a pointer to a const object? Otherwise it could be trivially subverted: const char[] c; char* ptr = c.ptr; *ptr = 5; // Whoops! Const violation! Pointers do seem to be one of the main sources of trouble with const, though, so it's an interesting idea.
 
 The compiler can't enforce everything you can do to a const in D. To 
 make it so that const can actually be meaningful, additional language 
 could be added to the spec. That language could be as simple as 
 something like: "subverting const can result in undefined behavior".
Wouldn't that then put the onus back on the programmer to avoid const violations? Back to square one? Unless, of course, a const violation was explicitly obvious, in which case it would be an entirely different matter. Cheers, Reiner
Jul 27 2006
next sibling parent Dave <Dave_member pathlink.com> writes:
Reiner Pope wrote:
 Dave wrote:
 Also maybe const would apply only to built-in reference types 
 (including class objects) and not pointers, justifiable because D has 
 these built-in whereas C++ doesn't. The same 'const' would not apply 
 to value types either, for simplicity.
This presumably has the consequence that you can't get a pointer to a const object? Otherwise it could be trivially subverted: const char[] c; char* ptr = c.ptr; *ptr = 5; // Whoops! Const violation!
You're right and that (c.ptr) would need to be disallowed, probably along with other things I may have forgotten.
 Pointers do seem to be one of the main sources of trouble with const, 
 though, so it's an interesting idea.
 
 The compiler can't enforce everything you can do to a const in D. To 
 make it so that const can actually be meaningful, additional language 
 could be added to the spec. That language could be as simple as 
 something like: "subverting const can result in undefined behavior".
Wouldn't that then put the onus back on the programmer to avoid const violations? Back to square one? Unless, of course, a const violation was
I don't think so because you'd have to do something pretty explicit to subvert the compile-time checks (as in the original post). I think the compile-time checks in the original post are doable because most of them have been done w/ C++ implementations for years now and the ones I added should not be too difficult either (IM0). The stuff I added was to also not allow a const to be assigned to a non-const [or vice-versa] with or without casting from one to the other: const char[] a; char[] b; ... b = a; // error b = cast(char[])a; // error ... a = b; // error a = cast(const char[])b; // error Now of course, another check would be that the .ptr property would not be allowed for const ref. types (as you caught above). What about things like multi-dim arrays of reference objects? Currently in C++: void foo(MyClass const* const* const* const arg) { // error: assignment of read-only parameter arg = new MyClass**[10]; // error: assignment of read-only location arg[0] = new MyClass*[10]; // error: assignment of read-only location arg[0][0] = new MyClass(); // error: assignment of member of read-only structure arg[0][0]->i = 10; } In D: // In D, const means "readonly" for all members of any type of // aggregate, just like it would if arg was 'const MyClass arg' // or const char[] arg. void foo(const MyClass[][] arg) { // error: assignment of read-only parameter arg = new MyClass[][10]; // error: assignment of read-only location arg[0] = new MyClass[10]; // error: assignment of read-only location arg[0][0] = new MyClass; // error: assignment of member of read-only structure arg[0][0].i = 10; } The idea is to use the error checking that already works for C++, extend it to not be easily subverted by casting or simple assignment and apply it to all members of any type of reference aggregate. Thanks, - Dave
 explicitly obvious, in which case it would be an entirely different matter.
 
 Cheers,
 
 Reiner
Jul 27 2006
prev sibling parent "Andrei Khropov" <andkhropov nospam_mtu-net.ru> writes:
Reiner Pope wrote:

Should be:

----------------------------------------------------------
   const char[] c;
   char* ptr = c.ptr; // Compilation error: cannot cast away const
   *ptr = 5; 
----------------------------------------------------------
   const char[] c;
   const char* ptr = c.ptr; // ok: pointer to const char
   *ptr = 5; 			 // Compilation error: try to assign to a const value
----------------------------------------------------------

That's how it works in C++ (and it's correct).

The only way to break this system is to use explicit casts.


-- 
AKhropov
Jul 27 2006