March 20, 2010
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
March 20, 2010
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.
March 20, 2010
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
March 20, 2010
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. :)
1 2
Next ›   Last »