www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Confused about const

reply Paul D. Anderson <paul.d.removethis comcast.andthis.net> writes:
I created a struct, call it "S", and some functions that operate on S. But I'm
confused about when const is useful.

Being an old Java programmer, I use 'const' the same as I used 'final' in Java.
So most of my functions look like this:

S for(const S a, const S b) {
    S x = a;
    S y = b;
    // do some stuff
    return a;
}

I declare the parameters const and then copy them to work on them.

I get error messages about not implicitly casting const S to S. So I can make
an explicit cast:

    S x = cast(S) a;
    S y = cast(S) b;

and the error messages go away.

But I think I must have gone off the rails somewhere -- all I want is for the
caller to be sure the function doesn't alter a and b. But I end up with lots of
casts to/from const. What am I missing??

Paul
Mar 19 2010
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Paul D. Anderson:

 I created a struct, call it "S", and some functions that operate on S. But I'm
confused about when const is useful.
 
 Being an old Java programmer, I use 'const' the same as I used 'final' in
Java. So most of my functions look like this:
 
 S for(const S a, const S b) {
     S x = a;
     S y = b;
     // do some stuff
     return a;
 }
 
 I declare the parameters const and then copy them to work on them.
 
 I get error messages about not implicitly casting const S to S.
This program compiles: struct S { int x; } S foo(const S a) { S other = a; return other; } void main() {} So, what's that you actually write? When possible show programs that run! It's one of the things I have learnt from the Python groups. Bye, bearophile
Mar 19 2010
parent reply Paul D. Anderson <paul.d.removethis comcast.andthis.net> writes:
bearophile Wrote:

 Paul D. Anderson:
 
 I created a struct, call it "S", and some functions that operate on S. But I'm
confused about when const is useful.
 
 Being an old Java programmer, I use 'const' the same as I used 'final' in
Java. So most of my functions look like this:
 
 S for(const S a, const S b) {
     S x = a;
     S y = b;
     // do some stuff
     return a;
 }
 
 I declare the parameters const and then copy them to work on them.
 
 I get error messages about not implicitly casting const S to S.
This program compiles: struct S { int x; } S foo(const S a) { S other = a; return other; } void main() {} So, what's that you actually write? When possible show programs that run! It's one of the things I have learnt from the Python groups. Bye, bearophile
My struct has a dynamic array as a member -- that seems to be the problem. This code doesn't compile: struct S { int x; int[] a; } S foo(const S b) { S other = b; return other; } void main() {} FYI, I'm using DMD version 2.30.
Mar 19 2010
next sibling parent reply biozic <dransic free.fr> writes:
My guess is that:

 struct S {
   int x;
   int[] a;
 }

 S foo(const S b) {
foo makes the promise that b's members won't change: so 'b.x' is implicitely const(int) and 'b.a' is implicitely const(int[]).
      S other = b;
Here you assign 'b.x', which is const(int), to 'other.x', which is int. You can assign an const(int) to an int: after the value is copied, changing 'other.x' won't change 'b.x'. But then you assign 'b.a', which is const(int[]), to 'other.a' which is int[]. This is forbidden: this would copy the reference to the array from 'b' to 'other', and after the copy, you would be able to change the content of 'b.a' by changing the content of 'other.a', thus going against const. I'm not sure if this is completely right nor if I'm completely clear, though... :) Nicolas
Mar 19 2010
parent reply bearophile <bearophileHUGS lycos.com> writes:
biozic:
 I'm not sure if this is completely right nor if I'm completely clear, 
 though... :)
I think you are right. In practice to copy something const to something that's not const you need a deep copy function, because inside the array there can be other arrays that are const, etc. Transitive const requires transitive copy, it's easy :-) Bye, bearophile
Mar 19 2010
parent reply bearophile <bearophileHUGS lycos.com> writes:
 Transitive const requires transitive copy, it's easy :-)
Or when possible, you don't copy it, just return it. Bye, bearophile
Mar 19 2010
parent bearophile <bearophileHUGS lycos.com> writes:
So the question here is: do you know if there are important use cases for a
shallow_const attribute?

Bye,
bearophile
Mar 19 2010
prev sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Paul D. Anderson:
 My struct has a dynamic array as a member -- that seems to be the problem.
This code doesn't compile:
 
 struct S {
  int x;
  int[] a;
 }
 
 S foo(const S b) {
     S other = b;
     return other;
 }
 void main() {}
This compiles, oh joy: struct S { int x; int[] a; } S foo(const S b) { S other; other.x = b.x; other.a = b.a.dup; // the dup is necessary here return other; } void main() {} The idea now is to find a way to encapsulate that manual copying & dupping into some overloaded operator of S. But I have failed so far. Operator overloading in D2 is not an easy thing, it needs training. (That's why I have recently asked for the compiler to be strict to avoid wrong usages of the operator overloading.) Bye, bearophile
Mar 19 2010
parent reply Don <nospam nospam.com> writes:
bearophile wrote:

 Operator overloading in D2 is not an easy thing, it needs training. 
(That's why I have recently asked for the compiler to be strict to 
avoid wrong usages of the operator overloading.) I think the problem is really that it's still very buggy. In particular, opAssign seems pretty defective. More importantly, struct constructors, post blit, and struct destructors are still quite badly broken.
Mar 20 2010
parent bearophile <bearophileHUGS lycos.com> writes:
Don:
 I think the problem is really that it's still very buggy. In particular,
 opAssign seems pretty defective. More importantly, struct constructors, 
 post blit, and  struct destructors are still quite badly broken.
You are right, but in my opinion it's not just a matter of bugs. To solve real world problems (like bugs in code) you usually have to attack the problems from many sides at the same time. So once: - the op overloading design is not too much bug-prone; - such parts of D are debugged; - the compiler performs several sanity tests on the operators defined by the programmer (even if this means disallowing few legit corner cases) Then D2 op overloading can be safe enough to use :-) Bye, bearophile
Mar 20 2010
prev sibling next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Paul D. Anderson:
     S x = cast(S) a;
     S y = cast(S) b;
In Java (especially old Java) casts may be common, but in D they are something that has to be used with care, don't cast away things carelessly :-) Bye, bearophile
Mar 19 2010
parent Paul D. Anderson <paul.d.removethis comcast.andthis.net> writes:
bearophile Wrote:

 Paul D. Anderson:
     S x = cast(S) a;
     S y = cast(S) b;
In Java (especially old Java) casts may be common, but in D they are something that has to be used with care, don't cast away things carelessly :-) Bye, bearophile
I realize that casting is a last resort -- but it seemed to be the only thing that worked. That's what made me think I'm making some sort of fundamental mistake. Paul
Mar 19 2010
prev sibling next sibling parent reply Paul D. Anderson <paul.d.removethis comcast.andthis.net> writes:
bearophile Wrote:

 Paul D. Anderson:
 My struct has a dynamic array as a member -- that seems to be the problem.
This code doesn't compile:
 
 struct S {
  int x;
  int[] a;
 }
 
 S foo(const S b) {
     S other = b;
     return other;
 }
 void main() {}
This compiles, oh joy: struct S { int x; int[] a; } S foo(const S b) { S other; other.x = b.x; other.a = b.a.dup; // the dup is necessary here return other; } void main() {} The idea now is to find a way to encapsulate that manual copying & dupping into some overloaded operator of S. But I have failed so far. Operator overloading in D2 is not an easy thing, it needs training. (That's why I have recently asked for the compiler to be strict to avoid wrong usages of the operator overloading.) Bye, bearophile
Yes, thanks to you and biozic for finding the root of the problem. Here's a piece of code that demonstrates the problem: //------------- struct S { int x; int[] a; // including this postblit causes an error because the default // constructor it applies to doesn't allow const argument. /+this(this) { a = a.dup; }+/ // adding a const to the argument of this constructor allows case 2. this(const S s) { x = s.x; a = s.a.dup; } // including or excluding this constructor doesn't change // the compilability. /+this( S s) { x = s.x; a = s.a.dup; }+/ // an opAssign with a const argument allows case 3. S opAssign(const S s) { this.x = s.x; this.a = s.a.dup; return this; } // including this doesn't make a difference. // I think this is the same as the default opAssign. /+S opAssign(S s) { this.x = s.x; this.a = s.a.dup; return this; }+/ } // case 1: no const. According to the spec this is // a copy constructor in action. S foo(S b) { S other = b; return other; } // case 2: const, but assignment requires the creation of a // non-const S from before assignment. S bar(const S b) { S other = S(b); return other; } // case 3: const, but creation and assignment are separate. // this is a default construction and opAssign(const). S foo(const S b) { S other; other = b; return other; } // case 4:const. According to the spec this is // a copy constructor in action, but it goes awry. // does not compile. S foo(const S b) { S other = b; return other; } void main() {} /---------------------- So I understand better now, thanks, what is wrong. I'm a little disappointed that there is apparently no way to implement case 4 for any struct with a reference. Paul p.s. Does it make any sense to send this over to the D forum to get more eyes on the problem?
Mar 19 2010
parent reply Paul D. Anderson <paul.d.removethis. comcast.andthis.net> writes:
After further review, I now realize that the right way (for me) to do this is
to add a .dup property. So instead of 

S foo(const S s) {
    S other;
    other = s;
}

or

S foo(const S s) {
    S other = S(s);
    return other;
}

which didn't look right to me, I can write

S foo(const S s) {
    S other = s.dup;
    return other;
}

which is ultimately an equivalent operation.

Thanks again, everyone for taking the time to explain what was going on.

Paul

It just looks better to me.





Paul D. Anderson Wrote:

 bearophile Wrote:
 
 The idea now is to find a way to encapsulate that manual copying & dupping
into some overloaded operator of S. But I have failed so far. Operator
overloading in D2 is not an easy thing, it needs training. (That's why I have
recently asked for the compiler to be strict to avoid wrong usages of the
operator overloading.)
 
 Bye,
 bearophile
So I understand better now, thanks, what is wrong. I'm a little disappointed that there is apparently no way to implement case 4 for any struct with a reference. Paul
Mar 19 2010
parent reply bearophile <bearophileHUGS lycos.com> writes:
Paul D. Anderson:

 After further review, I now realize that the right way (for me) to do this is
to add a .dup property.<
Steven Schveighoffer has given you quite good answers. A dup is generally not enough, because what you dup can have other immutable references nested inside. Dup is not a deep copy. The summary of this topic is (until inout works) that if you can return a const value, then don't copy things. This is the preferred option, because one of the main purposes of const data is to not have to copy them. If you can't return a const, then don't mark the input values as const. The safety given by the not deep const of Java is only partial safety. Bye, bearophile
Mar 20 2010
parent Paul D. Anderson <paul.d.removethis.anderson comcast.andthis.net> writes:
bearophile Wrote:

 Paul D. Anderson:
 
 After further review, I now realize that the right way (for me) to do this is
to add a .dup property.<
Steven Schveighoffer has given you quite good answers. A dup is generally not enough, because what you dup can have other immutable references nested inside. Dup is not a deep copy. The summary of this topic is (until inout works) that if you can return a const value, then don't copy things. This is the preferred option, because one of the main purposes of const data is to not have to copy them. If you can't return a const, then don't mark the input values as const. The safety given by the not deep const of Java is only partial safety. Bye, bearophile
You're right that dup itself can have problems with pointers/references. My struct doesn't have 2nd-level references, so dup works fine for me. Paul
Mar 20 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 19 Mar 2010 20:58:21 -0400, Paul D. Anderson  
<paul.d.removethis comcast.andthis.net> wrote:

 I created a struct, call it "S", and some functions that operate on S.  
 But I'm confused about when const is useful.

 Being an old Java programmer, I use 'const' the same as I used 'final'  
 in Java. So most of my functions look like this:

 S for(const S a, const S b) {
     S x = a;
     S y = b;
     // do some stuff
     return a;
 }

 I declare the parameters const and then copy them to work on them.

 I get error messages about not implicitly casting const S to S. So I can  
 make an explicit cast:

     S x = cast(S) a;
     S y = cast(S) b;

 and the error messages go away.

 But I think I must have gone off the rails somewhere -- all I want is  
 for the caller to be sure the function doesn't alter a and b. But I end  
 up with lots of casts to/from const. What am I missing??
I'll try to help, const is a very complex thing to understand. However, what it provides is so valuable for interface design that it is worth learning. One thing to remember that const is transitive. This means that if S contains a pointer to something, you have to treat that pointer as if it were const too. This is a difference from Java's final. If S has a pointer or reference, then a const S cannot be copied to a mutable S because you can then change the data through the mutable reference without any casts. I'll give you an example: struct S { int *a; } void foo(const(S) s) { S s2 = s; // this fails to compile because of the possibility for the next line. *s2.a = 5; // I now just changed the value pointed to by s, which is const data! } However, if S does not have a pointer or reference, then you can copy a const S to a mutable one because then changing the mutable S does not affect the const data in the original S. example: struct S { int a; } void foo(const(S) s) { S s2 = s; // this compiles s2.a = 5; // does not change s at all. } In answer to your question, what you are missing is what const is for. When declaring a function takes a const item, you are declaring that that function will not change the argument *or* anything it references. The compiler is trying to enforce that. Casting away const breaks the compiler guarantees, so you should not do that unless you know what you are doing. So what I think is if you want to change the elements of S, or return an S parameter that is not const, then you shouldn't declare them as const parameters. One final thing -- there is a brand new feature for D2 that allows you to forward the const attributes of parameters to return values. This is under the heading "inout functions" of the documentation. This feature is not implemented properly, even in the latest compiler. However, once it does work, you can use it to declare your function like this: inout(S) foo(inout(S) a, inout(S) b) { return a; } What this means is, during the foo function, it will not modify a or b, but once it returns, the return value has the same constancy as the parameters. It basically means "the const you put in is the const you get out." -Steve
Mar 19 2010
prev sibling parent =?ISO-8859-1?Q?Pelle_M=E5nsson?= <pelle.mansson gmail.com> writes:
On 03/20/2010 01:58 AM, Paul D. Anderson wrote:
 I created a struct, call it "S", and some functions that operate on S. But I'm
confused about when const is useful.

 Being an old Java programmer, I use 'const' the same as I used 'final' in
Java. So most of my functions look like this:

 S for(const S a, const S b) {
      S x = a;
      S y = b;
      // do some stuff
      return a;
 }

 I declare the parameters const and then copy them to work on them.

 I get error messages about not implicitly casting const S to S. So I can make
an explicit cast:

      S x = cast(S) a;
      S y = cast(S) b;

 and the error messages go away.

 But I think I must have gone off the rails somewhere -- all I want is for the
caller to be sure the function doesn't alter a and b. But I end up with lots of
casts to/from const. What am I missing??

 Paul
I think you misunderstand const. If you flag an argument with const it means you're unable to ever change anything reachable from that argument, so if you have a const reference, you can not alter anything reachable from that reference. If you have this: struct S { int[] xs; } S foo(const S s) { return cast(S)s; } void main() { S s; s.xs = [1,4,5]; S fromconst = foo(s); fromconst.xs[0] = 4; //<-- there you alter something you // should not be able to alter. } The compiler protects you from this, unless you cast your constness away. If you want to copy, you'll need to implement the proper deep copying yourself. :)
Mar 20 2010